O acesso a SharedPreferences deve ser feito fora do UI Thread?

113

Com o lançamento do Gingerbread, tenho feito experiências com algumas das novas APIs, sendo uma delas o StrictMode .

Percebi que um dos avisos é para getSharedPreferences().

Este é o aviso:

StrictMode policy violation; ~duration=1949 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=23 violation=2

e está sendo fornecido para uma getSharedPreferences()chamada sendo feita no thread de interface do usuário.

O SharedPreferencesacesso e as alterações devem realmente ser feitos fora do thread da IU?

CottonBallPaws
fonte
Sempre fiz minhas operações de preferência no thread de interface do usuário. Embora eu ache que faz sentido, já que é uma operação IO
Falmarri

Respostas:

184

Estou feliz que você já está brincando com ele!

Algumas coisas a serem observadas: (na forma de marcador preguiçoso)

  • se este for o pior dos seus problemas, seu aplicativo provavelmente está em uma boa situação. :) As gravações são geralmente mais lentas que as leituras, portanto, certifique-se de usar SharedPreferenced $ Editor.apply () em vez de commit (). apply () é novo em GB e assíncrono (mas sempre seguro, cuidado com as transições do ciclo de vida). Você pode usar a reflexão para chamar condicionalmente apply () no GB + e commit () no Froyo ou abaixo. Farei uma postagem de blog com um código de amostra de como fazer isso.

Em relação ao carregamento, porém ...

  • uma vez carregados, SharedPreferences são singletons e armazenados em cache em todo o processo. portanto, você deseja carregá-lo o mais cedo possível para mantê-lo na memória antes de precisar dele. (presumindo que seja pequeno, como deveria ser se você estiver usando SharedPreferences, um arquivo XML simples ...) Você não quer culpá-lo no futuro, se algum usuário clicar em um botão.

  • mas sempre que você chamar context.getSharedPreferences (...), o arquivo XML de apoio é estatizado para ver se ele mudou, então você vai querer evitar essas estatísticas durante os eventos da IU de qualquer maneira. Um stat normalmente deve ser rápido (e frequentemente armazenado em cache), mas yaffs não tem muita concorrência (e muitos dispositivos Android rodam em yaffs ... Droid, Nexus One, etc.) então se você evitar o disco , você evita ficar preso atrás de outras operações de disco pendentes ou em andamento.

  • então provavelmente você desejará carregar o SharedPreferences durante seu onCreate () e reutilizar a mesma instância, evitando o stat.

  • mas se você não precisa de suas preferências durante onCreate (), esse tempo de carregamento está atrasando a inicialização do seu aplicativo desnecessariamente, então geralmente é melhor ter algo como uma subclasse FutureTask <SharedPreferences> que inicia um novo thread para .set () o valor das subclasses de FutureTask. Em seguida, basta consultar o membro FutureTask <SharedPreferences> sempre que precisar e .get (). Pretendo tornar isso gratuito nos bastidores do Honeycomb, de forma transparente. Tentarei lançar algum código de amostra que mostre as melhores práticas nesta área.

Verifique o blog de desenvolvedores Android para as próximas postagens sobre assuntos relacionados ao StrictMode nas próximas semanas.

Brad Fitzpatrick
fonte
Uau, não esperava obter uma resposta tão clara diretamente da fonte! Muito obrigado!
cottonBallPaws de
9
Para o benefício dos novos leitores desta postagem maravilhosa, encontre abaixo o link para a postagem do blog mencionada acima por @Brad Fitzpatrick: postagem do blog do desenvolvedor do Android no modo estrito de Brad . A postagem também tem um link para um código de amostra para usar aplicar (do gingerbread em diante) ou commit (froyo) com base na versão do Android para armazenar preferências compartilhadas: [usar condicionalmente aplicar ou confirmar] ( code.google.com/p/zippy-android / source / browse / trunk / examples /… )
tony m
4
Isso ainda é relevante no ICS \ JB?
ekatz de
5

O acesso às preferências compartilhadas pode levar algum tempo porque elas são lidas do armazenamento flash. Você lê muito? Talvez você possa usar um formato diferente, por exemplo, um banco de dados SQLite.

Mas não conserte tudo que você encontrar usando StrictMode. Ou para citar a documentação:

Mas não se sinta obrigado a consertar tudo o que StrictMode encontrar. Em particular, muitos casos de acesso ao disco são frequentemente necessários durante o ciclo de vida normal da atividade. Use StrictMode para encontrar coisas que você fez por acidente. As solicitações de rede no thread de interface do usuário quase sempre são um problema.

Mreichelt
fonte
6
Mas o SQLite não é também um arquivo que deve ser lido do armazenamento flash - mas um arquivo maior e mais complicado em comparação com um arquivo de preferências. Tenho presumido que, para a quantidade de dados associados às preferências, um arquivo de preferências será muito mais rápido do que um banco de dados SQLite.
Tom,
Está correto. Como Brad já mencionou, isso quase sempre não é problema - e ele também menciona que é uma boa ideia carregar as SharedPreferences uma vez (talvez até em um Thread usando uma FutureTask) e mantê-las para qualquer acesso possível à instância única.
mreichelt,
5

Uma sutileza sobre a resposta de Brad: mesmo se você carregar o SharedPreferences em onCreate (), provavelmente ainda deve ler os valores no thread de segundo plano porque getString () etc. bloqueia até ler a preferência de arquivo compartilhado em termina (em um thread de segundo plano):

public String getString(String key, String defValue) {
    synchronized (this) {
        awaitLoadedLocked();
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}

edit () também bloqueia da mesma maneira, embora apply () pareça ser seguro no thread de primeiro plano.

(Aliás, sinto muito por colocar isso aqui. Eu teria colocado isso como um comentário à resposta de Brad, mas acabei de entrar e não tenho reputação suficiente para fazer isso.)

Tom O'Neill
fonte
1

Eu sei que esta é uma questão antiga, mas quero compartilhar minha abordagem. Tive longos tempos de leitura e usei uma combinação de preferências compartilhadas e a classe de aplicativo global:

ApplicationClass:

public class ApplicationClass extends Application {

    private LocalPreference.Filter filter;

    public LocalPreference.Filter getFilter() {
       return filter;
    }

    public void setFilter(LocalPreference.Filter filter) {
       this.filter = filter;
    }
}

LocalPreference:

public class LocalPreference {

    public static void saveLocalPreferences(Activity activity, int maxDistance, int minAge,
                                            int maxAge, boolean showMale, boolean showFemale) {

        Filter filter = new Filter();
        filter.setMaxDistance(maxDistance);
        filter.setMinAge(minAge);
        filter.setMaxAge(maxAge);
        filter.setShowMale(showMale);
        filter.setShowFemale(showFemale);

        BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
        babysitApplication.setFilter(filter);

        SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
        securePreferences.edit().putInt(Preference.FILER_MAX_DISTANCE.toString(), maxDistance).apply();
        securePreferences.edit().putInt(Preference.FILER_MIN_AGE.toString(), minAge).apply();
        securePreferences.edit().putInt(Preference.FILER_MAX_AGE.toString(), maxAge).apply();
        securePreferences.edit().putBoolean(Preference.FILER_SHOW_MALE.toString(), showMale).apply();
        securePreferences.edit().putBoolean(Preference.FILER_SHOW_FEMALE.toString(), showFemale).apply();
    }

    public static Filter getLocalPreferences(Activity activity) {

        BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
        Filter applicationFilter = babysitApplication.getFilter();

        if (applicationFilter != null) {
            return applicationFilter;
        } else {
            Filter filter = new Filter();
            SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
            filter.setMaxDistance(securePreferences.getInt(Preference.FILER_MAX_DISTANCE.toString(), 20));
            filter.setMinAge(securePreferences.getInt(Preference.FILER_MIN_AGE.toString(), 15));
            filter.setMaxAge(securePreferences.getInt(Preference.FILER_MAX_AGE.toString(), 50));
            filter.setShowMale(securePreferences.getBoolean(Preference.FILER_SHOW_MALE.toString(), true));
            filter.setShowFemale(securePreferences.getBoolean(Preference.FILER_SHOW_FEMALE.toString(), true));
            babysitApplication.setFilter(filter);
            return filter;
        }
    }

    public static class Filter {
        private int maxDistance;
        private int minAge;
        private int maxAge;
        private boolean showMale;
        private boolean showFemale;

        public int getMaxDistance() {
            return maxDistance;
        }

        public void setMaxDistance(int maxDistance) {
            this.maxDistance = maxDistance;
        }

        public int getMinAge() {
            return minAge;
        }

        public void setMinAge(int minAge) {
            this.minAge = minAge;
        }

        public int getMaxAge() {
            return maxAge;
        }

        public void setMaxAge(int maxAge) {
            this.maxAge = maxAge;
        }

        public boolean isShowMale() {
            return showMale;
        }

        public void setShowMale(boolean showMale) {
            this.showMale = showMale;
        }

        public boolean isShowFemale() {
            return showFemale;
        }

        public void setShowFemale(boolean showFemale) {
            this.showFemale = showFemale;
        }
    }

}

MainActivity (atividade que é chamada primeiro em seu aplicativo):

LocalPreference.getLocalPreferences(this);

Etapas explicadas:

  1. A atividade principal chama getLocalPreferences (this) -> isso lerá suas preferências, definirá o objeto de filtro em sua classe de aplicativo e o retornará.
  2. Quando você chama a função getLocalPreferences () novamente em algum outro lugar do aplicativo, ela primeiro verifica se não está disponível na classe do aplicativo, o que é muito mais rápido.

NOTA: SEMPRE verifique se uma variável ampla do aplicativo é diferente de NULL, motivo -> http://www.developerphil.com/dont-store-data-in-the-application-object/

O objeto do aplicativo não ficará na memória para sempre, ele será eliminado. Ao contrário da crença popular, o aplicativo não será reiniciado do zero. O Android criará um novo objeto Aplicativo e iniciará a atividade onde o usuário estava antes para dar a ilusão de que o aplicativo nunca foi encerrado em primeiro lugar.

Se eu não tivesse verificado em null, permitiria que um nullpointer fosse lançado ao chamar, por exemplo, getMaxDistance () no objeto de filtro (se o objeto do aplicativo foi deslocado da memória pelo Android)

Jdruwe
fonte
0

A classe SharedPreferences faz algumas leituras e gravações em arquivos XML no disco, portanto, como qualquer outra operação de IO, ela pode estar bloqueando. A quantidade de dados armazenados atualmente em SharedPreferences afeta o tempo e os recursos consumidos pelas chamadas de API. Para quantidades mínimas de dados, é uma questão de alguns milissegundos (às vezes até menos de um milissegundo) para obter / colocar os dados. Mas, do ponto de vista de um especialista, pode ser importante melhorar o desempenho fazendo chamadas de API em segundo plano. Para um SharedPreferences assíncrono, sugiro verificar a biblioteca Datum .

navid
fonte