lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

Configurações

Geralmente os aplicativos contêm configurações que permitem aos usuários modificar características e comportamentos do aplicativo. Por exemplo, alguns aplicativos permitem aos usuários especificar se as notificações estão ativadas ou especificar a frequência com que o aplicativo sincroniza dados com a nuvem.

Para fornecer configurações ao aplicativo, é preciso usar as APIs Preference do Android para programar uma interface coerente com a experiência do usuário em outros aplicativos Android (inclusive as configurações do sistema). Esse documento descreve como programar as configurações do aplicativo por meio de APIs Preference.

Projeto de configurações

Para obter mais informações sobre o projeto de configurações, leia o guia de projeto Configurações.

Figura 1. Capturas de tela das configurações do aplicativo Mensagens do Android. A seleção de um item definido por uma Preference abre uma interface para alterar a configuração.

Visão geral

Em vez de usar objetos View para criar a interface do usuário, as configurações são criadas por meio de várias subclasses da classe Preference declaradas em um arquivo XML.

Os objetos Preference são as peças fundamentais de uma única configuração. Cada Preference aparece como um item em uma lista e oferece a IU adequada para que os usuários modifiquem a configuração. Por exemplo: CheckBoxPreference cria um item de lista que exibe uma caixa de seleção e ListPreference cria um item que abre uma caixa de diálogo com uma lista de opções.

Cada Preference adicionada tem um par de valor-chave correspondente que o sistema usa para salvar a configuração em um arquivo SharedPreferences padrão para as configurações do aplicativo. Quando o usuário altera uma configuração, o sistema atualiza o valor correspondente no arquivo SharedPreferences. O único momento em que se deve interagir diretamente com o arquivo SharedPreferences associado é no momento de ler o valor para determinar o comportamento do aplicativo com base na configuração do usuário.

O valor salvo em SharedPreferences para cada configuração pode ser um dos seguintes tipos de dados:

  • Boolean
  • Float
  • Int
  • Long
  • String
  • String Set

Como a IU de configurações do aplicativo é criada com objetos Preference em vez de objetos View, é preciso usar uma subclasse Activity ou Fragment especializada para exibir as configurações de lista:

  • Se o aplicativo for compatível com versões do Android anteriores à 3.0 (nível da API 10 ou anterior), será necessário criar a atividade como uma extensão da classe PreferenceActivity.
  • No Android 3.0 ou versões posteriores, deve-se usar um Activity tradicional que hospeda um PreferenceFragment que exige as configurações do aplicativo. No entanto, pode-se também usar PreferenceActivity para criar um layout de dois painéis para telas maiores quando há vários grupos de configurações.

Veja como configurar PreferenceActivity e instâncias de PreferenceFragment nas seções sobre a Criação de uma atividade de preferência e Uso de fragmentos de preferência.

Preferências

Toda a configuração do aplicativo é representada por uma subclasse específica da classe Preference. Cada subclasse contém um conjunto de propriedades essenciais que permitem especificar itens como o título da configuração e o valor padrão. Cada subclasse também oferece suas propriedades e interface do usuário especializadas. Por exemplo, a figura 1 ilustra uma captura de tela das configurações do aplicativo Mensagens. Cada item de lista na tela de configurações tem, como fundo, um objeto Preference.

Eis algumas das preferências mais comuns:

CheckBoxPreference
Exibe um item com uma caixa de seleção para uma configuração que esteja ativada ou desativada. O valor salvo é um booleano (true se estiver selecionada).
ListPreference
Abre uma caixa de diálogo com uma lista de botões de opção. O valor salvo pode ser qualquer um dos tipos de valor compatíveis (listados acima).
EditTextPreference
Abre uma caixa de diálogo com um widget EditText. O valor salvo é um String.

Consulte a classe Preference para ver uma lista de todas as outras subclasses e as propriedades correspondentes.

É claro que as classes embutidas não acomodam todas as necessidades e o aplicativo pode exigir algo mais especializado. Por exemplo: a plataforma atualmente não fornece nenhuma classe Preference para selecionar um número ou data. Portanto, pode ser necessário definir a sua subclasse Preference. Veja mais informações na seção sobre Composição de uma preferência personalizada.

Definição de preferências em XML

Embora se possa instanciar novos objetos Preference em tempo de execução, deve-se definir uma lista de configurações no XML com uma hierarquia de objetos Preference. Recomenda-se o uso de um arquivo XML para definir a coleção de configurações porque o arquivo oferece uma estrutura fácil de ler e simples de atualizar. Além disso, as configurações do aplicativo geralmente são predeterminadas, embora ainda seja possível modificar a coleção em tempo de execução.

Cada subclasse Preference pode ser declarada com um elemento XML correspondente ao nome da classe, como <CheckBoxPreference>.

É preciso salvar o arquivo XML no diretório res/xml/. Embora seja possível nomear livremente o arquivo, é mais frequente vê-lo com o nome preferences.xml. Geralmente só é necessário um arquivo porque as ramificações na hierarquia (que abrem sua própria lista de configurações) são declaradas por meio de instâncias aninhadas de PreferenceScreen.

Observação: Se você deseja criar um layout de vários painéis para as configurações, serão necessários arquivos XML separados para cada fragmento.

O nó raiz do arquivo XML deve ser um elemento <PreferenceScreen>. É dentro desse elemento que se adiciona cada Preference. Cada filho adicionado dentro do elemento <PreferenceScreen> é exibido com um item único na lista de configurações.

Por exemplo:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <CheckBoxPreference
        android:key="pref_sync"
        android:title="@string/pref_sync"
        android:summary="@string/pref_sync_summ"
        android:defaultValue="true" />
    <ListPreference
        android:dependency="pref_sync"
        android:key="pref_syncConnectionType"
        android:title="@string/pref_syncConnectionType"
        android:dialogTitle="@string/pref_syncConnectionType"
        android:entries="@array/pref_syncConnectionTypes_entries"
        android:entryValues="@array/pref_syncConnectionTypes_values"
        android:defaultValue="@string/pref_syncConnectionTypes_default" />
</PreferenceScreen>

Nesse exemplo, existe um CheckBoxPreference e um ListPreference. Os dois itens contêm estes três atributos:

android:key
Esse atributo é necessário para preferências que persistem a um valor de dados. Ele especifica a chave única (uma string) que o sistema usa ao salvar o valor dessa configuração em SharedPreferences.

Esse atributo somente é dispensável quando a preferência é um PreferenceCategory ou PreferenceScreen, ou quando a preferência especifica um Intent para invocar (com um elemento <intent>) ou um Fragment para exibir (com um atributo android:fragment).

android:title
Fornece à configuração um nome visível ao usuário.
android:defaultValue
Especifica o valor inicial que o sistema deve definir no arquivo SharedPreferences. Deve-se fornecer um valor padrão para todas as configurações.

Para obter informações sobre todos os outros atributos compatíveis, consulte a documentação Preference.

Figura 2. Definição de categorias com títulos.
1. A categoria é especificada pelo elemento <PreferenceCategory>.
2. O título é especificado com o atributo android:title.

Quando a lista de configurações excede cerca de 10 itens, pode ser necessário adicionar títulos para definir grupos de configurações ou exibir esses grupos em uma tela separada. Essas opções são descritas nas seções a seguir.

Criação de grupos de configuração

Se você apresentar uma lista de 10 ou mais configurações, os usuários poderão ter dificuldade em percorrê-las, compreendê-las e processá-las. Para solucionar isso, pode-se dividir algumas ou todas as configurações em grupos, transformando uma longa lista em várias listas mais curtas. Um grupo de configurações relacionadas pode ser apresentado de uma das seguintes formas:

Pode-se usar uma ou ambas as técnicas de agrupamento para organizar as configurações do aplicativo. Ao decidir qual delas usar e como dividir as configurações, deve-se seguir as diretrizes do guia Configurações de Projeto do Android.

Uso de títulos

Para usar divisores com cabeçalhos entre grupos de configurações (como ilustrado na figura 2), coloque cada grupo de objetos Preference dentro de uma PreferenceCategory.

Por exemplo:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory
        android:title="@string/pref_sms_storage_title"
        android:key="pref_key_storage_settings">
        <CheckBoxPreference
            android:key="pref_key_auto_delete"
            android:summary="@string/pref_summary_auto_delete"
            android:title="@string/pref_title_auto_delete"
            android:defaultValue="false"... />
        <Preference
            android:key="pref_key_sms_delete_limit"
            android:dependency="pref_key_auto_delete"
            android:summary="@string/pref_summary_delete_limit"
            android:title="@string/pref_title_sms_delete"... />
        <Preference
            android:key="pref_key_mms_delete_limit"
            android:dependency="pref_key_auto_delete"
            android:summary="@string/pref_summary_delete_limit"
            android:title="@string/pref_title_mms_delete" ... />
    </PreferenceCategory>
    ...
</PreferenceScreen>

Uso de subtelas

Para usar grupos de configurações em uma subtela (como ilustrado na figura 3), coloque o grupo de objetos Preference dentro de uma PreferenceScreen.

Figura 3. Configuração de subtelas. O elemento <PreferenceScreen> cria um item que, quando selecionado, abre uma lista separada para exibir as configurações aninhadas.

Por exemplo:

<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- opens a subscreen of settings -->
    <PreferenceScreen
        android:key="button_voicemail_category_key"
        android:title="@string/voicemail"
        android:persistent="false">
        <ListPreference
            android:key="button_voicemail_provider_key"
            android:title="@string/voicemail_provider" ... />
        <!-- opens another nested subscreen -->
        <PreferenceScreen
            android:key="button_voicemail_setting_key"
            android:title="@string/voicemail_settings"
            android:persistent="false">
            ...
        </PreferenceScreen>
        <RingtonePreference
            android:key="button_voicemail_ringtone_key"
            android:title="@string/voicemail_ringtone_title"
            android:ringtoneType="notification" ... />
        ...
    </PreferenceScreen>
    ...
</PreferenceScreen>

Uso de intents

Em alguns casos, pode ser necessário que um item de preferência abra em um atividade diferente e não na tela de configuração, como um navegador da Web para exibir uma página da Web. Para invocar um Intent quando o usuário seleciona um item de preferência, adicione um elemento <intent> como filho do elemento <Preference> correspondente.

Por exemplo, eis como usar um item de preferência para abrir uma página da Web:

<Preference android:title="@string/prefs_web_page" >
    <intent android:action="android.intent.action.VIEW"
            android:data="http://www.example.com" />
</Preference>

É possível criar intents implícitos e explícitos usando os seguintes atributos:

android:action
A ação a atribuir, conforme o método setAction().
android:data
Os dados a atribuir, conforme o método setData().
android:mimeType
O tipo MIME atribuir, conforme o método setType().
android:targetClass
A parte de classe do nome do componente, conforme o método setComponent().
android:targetPackage
A parte de pacote do nome do componente conforme o método setComponent().

Criação de uma atividade de preferência

Para exibir as configurações em uma atividade, estenda a classe PreferenceActivity. É uma extensão da classe tradicional Activity que exibe uma lista de configurações com base em uma hierarquia de objetos Preference. A PreferenceActivity persiste automaticamente as configurações associadas a cada Preference quando o usuário faz uma alteração.

Observação: Ao desenvolver um aplicativo para Android 3.0 ou superior, deve-se usar o PreferenceFragment. Consulte a próxima seção sobre o Uso de fragmentos de preferência.

O mais importante é não carregar nenhum layout de exibições durante o retorno de chamada onCreate(). Em vez disso, chame addPreferencesFromResource() para adicionar à atividade as preferências declaradas em um arquivo XML. Por exemplo, abaixo há o código mínimo necessário para uma PreferenceActivity funcional:

public class SettingsActivity extends PreferenceActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);
    }
}

Na verdade, esse código é suficiente para alguns aplicativos porque, assim que o usuário modifica uma preferência, o sistema salva as alterações em um arquivo padrão SharedPreferences que os componentes do outro aplicativo poderá ler quando for necessário verificar as configurações do usuário. No entanto, muitos aplicativos exigem um pouco mais de código para escutar as alterações que ocorrem nas preferências. Para informações sobre a escuda de alterações no arquivo SharedPreferences, consulte a seção sobre Leitura de preferências.

Uso de fragmentos de preferência

Ao desenvolver para Android 3.0 (API de nível 11) ou versões posteriores, use um PreferenceFragment para exibir a lista de objetos Preference. Pode-se adicionar um PreferenceFragment a qualquer atividade — não é necessário usar PreferenceActivity.

Os fragmentos permitem uma arquitetura mais flexível para o aplicativo em comparação com o uso de apenas atividades para qualquer tipo de atividade criada. Assim, sugerimos usar PreferenceFragment para controlar a exibição das configurações em vez de PreferenceActivity sempre que possível.

A implementação de PreferenceFragment pode ser tão simples quanto definir o método onCreate() para carregar um arquivo de preferências com addPreferencesFromResource(). Por exemplo:

public static class SettingsFragment extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Load the preferences from an XML resource
        addPreferencesFromResource(R.xml.preferences);
    }
    ...
}

É possível adicionar esse fragmento a um Activity como se faria com qualquer outro Fragment. Por exemplo:

public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Display the fragment as the main content.
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, new SettingsFragment())
                .commit();
    }
}

Observação: Um PreferenceFragment não tem o próprio objeto Context. Se for necessário um objeto Context , é possível chamar getActivity(). No entanto, tome cuidado para chamar getActivity() somente quando o fragmento estiver anexado a uma atividade. Se o fragmento ainda não estiver anexado ou se tiver sido desvinculado durante o fim do seu ciclo de vida, getActivity() retornará nulo.

Configuração de valores padrão

As preferências criadas provavelmente definem alguns comportamentos importantes do aplicativo, portanto, é necessário inicializar o arquivo SharedPreferences associado com os valores padrão de cada Preference quando o usuário abre o aplicativo pela primeira vez.

A primeira coisa a fazer é especificar o valor padrão de cada objeto Preference no arquivo XML com o atributo android:defaultValue. O valor pode ser qualquer tipo de dados apropriado para o objeto Preference correspondente. Por exemplo:

<!-- default value is a boolean -->
<CheckBoxPreference
    android:defaultValue="true"
    ... />

<!-- default value is a string -->
<ListPreference
    android:defaultValue="@string/pref_syncConnectionTypes_default"
    ... />

Em seguida, no método onCreate() na atividade principal do aplicativo — e em qualquer outra atividade pela qual o usuário possa entrar no aplicativo pela primeira vez —, chame setDefaultValues():

PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);

Essa chamada durante onCreate() garante que o aplicativo seja adequadamente inicializado com as configurações padrão, que o aplicativo pode precisar ler para determinar alguns comportamentos (se, por exemplo, baixará dados enquanto estiver em uma rede de celular).

Esse método usa três argumentos:

  • O Context do aplicativo.
  • O ID de recurso do arquivo XML de preferências para o qual você deseja definir os valores padrão.
  • Booleano que indica se os valores padrão devem ser definidos mais de uma vez.

    Quando false, o sistema define os valores parão somente se esse método nunca tiver sido chamado (ou se KEY_HAS_SET_DEFAULT_VALUES no arquivo de preferências compartilhadas do valor padrão for falso).

Enquanto o terceiro argumento estiver definido como false, pode-se chamar esse método com segurança toda vez que a atividade iniciar sem substituir as preferências salvas do usuário redefinindo-as para os padrões. No entanto, se ele for definido como true, todos os valores anteriores serão substituídos pelos padrões.

Uso de cabeçalhos de preferência

Em casos raros, pode ser necessário projetar as configurações de forma que a primeira tela exiba somente uma lista de subtelas (como as do aplicativo Configurações conforme ilustrado nas figuras 4 e 5). Ao desenvolver um projeto desse tipo para Android 3.0 ou posterior, use o recurso “cabeçalhos” em vez de criar subtelas com elementos PreferenceScreen aninhados.

Para criar as configurações com cabeçalhos, é preciso:

  1. Separar cada grupo de configurações em instâncias separadas de PreferenceFragment. Ou seja, cada grupo de configurações precisa de um arquivo XML separado.
  2. Criar um arquivo XML de cabeçalhos que lista cada grupo de configurações e declara que fragmento contém a lista correspondente de configurações.
  3. Estender a classe PreferenceActivity para hospedar as configurações.
  4. Implemente o retorno de chamada onBuildHeaders() para especificar o arquivo de cabeçalhos.

Um grande benefício de usar esse modelo é que PreferenceActivity automaticamente apresenta o layout de dois painéis ilustrado na figura 4 ao executar em telas grandes.

Mesmo se o aplicativo é compatível com versões de Android anteriores à 3.0, é possível programar o aplicativo para usar PreferenceFragment para uma apresentação em dois painéis em dispositivos mais novos e ser compatível com a hierarquia tradicional multitelas em dispositivos mais antigos (veja a seção sobre Compatibilidade de versões mais antigas com cabeçalhos de preferência).

Figura 4. Layout de dois painéis com cabeçalhos.
1. Os cabeçalhos são definidos com um arquivo XML de cabeçalhos.
2. Cada grupo de configurações é definido por um PreferenceFragment especificado por um elemento <header> no arquivo de cabeçalhos.

Figura 5. Dispositivo celular com cabeçalhos de configuração. Quando um item é selecionado o PreferenceFragment associado substitui os cabeçalhos.

Criação do arquivo de cabeçalhos

Cada grupo de configurações na lista de cabeçalhos é especificado por um único elemento <header> dentro de um elemento raiz <preference-headers>. Por exemplo:

<?xml version="1.0" encoding="utf-8"?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
    <header
        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne"
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one" />
    <header
        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo"
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" >
        <!-- key/value pairs can be included as arguments for the fragment. -->
        <extra android:name="someKey" android:value="someHeaderValue" />
    </header>
</preference-headers>

Com o atributo android:fragment, cada cabeçalho declara uma instância de PreferenceFragment que deve abrir quando o usuário selecionar o cabeçalho.

O elemento <extras> permite passar os pares chave-valor para o fragmento em um Bundle. O fragmento pode recuperar os argumentos chamando getArguments(). Há vários motivos para passar argumentos ao fragmento, mas um bom motivo é reutilizar a mesma subclasse de PreferenceFragment para cada grupo e usar o argumento para especificar o arquivo XML de preferências que o fragmento deve carregar.

Por exemplo, eis um fragmento que pode ser reutilizado em vários grupos de configurações, em que cada cabeçalho define um argumento <extra> com a chave "settings":

public static class SettingsFragment extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String settings = getArguments().getString("settings");
        if ("notifications".equals(settings)) {
            addPreferencesFromResource(R.xml.settings_wifi);
        } else if ("sync".equals(settings)) {
            addPreferencesFromResource(R.xml.settings_sync);
        }
    }
}

Exibição de cabeçalhos

Para exibir os cabeçalhos de preferência, é preciso implementar o método de retorno de chamada onBuildHeaders() e chamar loadHeadersFromResource(). Por exemplo:

public class SettingsActivity extends PreferenceActivity {
    @Override
    public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.preference_headers, target);
    }
}

Quando o usuário seleciona um item de uma lista de cabeçalhos, o sistema abre o PreferenceFragment associado.

Observação: Ao usar cabeçalhos de preferência, a subclasse de PreferenceActivity não precisa implementar o método onCreate() porque a única tarefa necessária para a atividade é carregar os cabeçalhos.

Compatibilidade de versões mais antigas com cabeçalhos de preferência

Se o aplicativo for compatível com versões de Android anteriores à 3.0, ainda será possível usar cabeçalhos para fornecer um layout em dois painéis ao executar no Android 3.0 e versões posteriores. Basta criar um arquivo XML de preferências adicional que usa elementos básicos <Preference> que se comportam como os itens de cabeçalho (para uso das versões mais antigas do Android).

No entanto, em vez de abrir um novo PreferenceScreen, cada elemento <Preference> envia um Intent ao PreferenceActivity que especifica que arquivo XML de preferências carregar.

Por exemplo, veja a seguir um arquivo XML de cabeçalhos de preferência usado no Android 3.0 e versões posteriores (res/xml/preference_headers.xml):

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
    <header
        android:fragment="com.example.prefs.SettingsFragmentOne"
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one" />
    <header
        android:fragment="com.example.prefs.SettingsFragmentTwo"
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" />
</preference-headers>

E este é um arquivo de preferências que fornece os mesmos cabeçalhos para versões de Android mais antigas que a 3.0 (res/xml/preference_headers_legacy.xml):

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <Preference
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one"  >
        <intent
            android:targetPackage="com.example.prefs"
            android:targetClass="com.example.prefs.SettingsActivity"
            android:action="com.example.prefs.PREFS_ONE" />
    </Preference>
    <Preference
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" >
        <intent
            android:targetPackage="com.example.prefs"
            android:targetClass="com.example.prefs.SettingsActivity"
            android:action="com.example.prefs.PREFS_TWO" />
    </Preference>
</PreferenceScreen>

Como a compatibilidade com <preference-headers> foi adicionada no Android 3.0, o sistema chama onBuildHeaders() em seu PreferenceActivity somente ao executar em Android 3.0 ou versão posterior. Para carregar o arquivo de cabeçalhos “legado” (preference_headers_legacy.xml), é preciso verificar a versão do Android e, se a versão for mais antiga que o Android 3.0 (HONEYCOMB), chamar addPreferencesFromResource()} para carregar o arquivo de cabeçalho legado. Por exemplo:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        // Load the legacy preferences headers
        addPreferencesFromResource(R.xml.preference_headers_legacy);
    }
}

// Called only on Honeycomb and later
@Override
public void onBuildHeaders(List<Header> target) {
   loadHeadersFromResource(R.xml.preference_headers, target);
}

Depois só resta processar o Intent passado para a atividade para identificar que arquivo de preferências carregar. Portanto, para recuperar a ação do intent e compará-lo com strings de ações conhecidas usadas nas tags <intent> do XML de preferências:

final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE";
...

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    String action = getIntent().getAction();
    if (action != null && action.equals(ACTION_PREFS_ONE)) {
        addPreferencesFromResource(R.xml.preferences);
    }
    ...

    else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        // Load the legacy preferences headers
        addPreferencesFromResource(R.xml.preference_headers_legacy);
    }
}

Observe que chamadas consecutivas addPreferencesFromResource() empilharão todas as preferências em uma única lista, portanto, certifique-se de que seja chamada somente uma vez, encadeando as condições com instruções else-if.

Leitura de preferências

Por padrão, todas as preferências do aplicativo são salvas em um arquivo acessível de qualquer lugar dentro do aplicativo chamando o método estático PreferenceManager.getDefaultSharedPreferences(). Isso retorna o objeto SharedPreferences que contém todos os pares chave-valor associados aos objetos Preference usados em PreferenceActivity.

Por exemplo, eis como ler um dos valores de preferência de outra atividade no aplicativo:

SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");

Escuta de alterações de preferência

Há alguns motivos pelos quais pode ser necessário ser notificado assim que o usuário altera uma das preferências. Para receber um retorno de chamada quando acontece uma alteração em alguma preferência, implemente a interface SharedPreference.OnSharedPreferenceChangeListener e registre o ouvinte para o objeto SharedPreferences chamando {@linkandroid.content.SharedPreferences#registerOnSharedPreferenceChangeListener registerOnSharedPreferenceChangeListener()}.

A interface tem apenas um método de retorno de chamada, onSharedPreferenceChanged(), e pode ser mais fácil implementá-la como parte da atividade. Por exemplo:

public class SettingsActivity extends PreferenceActivity
                              implements OnSharedPreferenceChangeListener {
    public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
    ...

    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
        String key) {
        if (key.equals(KEY_PREF_SYNC_CONN)) {
            Preference connectionPref = findPreference(key);
            // Set summary to be the user-description for the selected value
            connectionPref.setSummary(sharedPreferences.getString(key, ""));
        }
    }
}

Nesse exemplo, o método verifica se a configuração alterada se destina a uma chave de preferência conhecida. Ele chama findPreference() para obter o objeto Preference alterado para que possa modificar o sumário do item como uma descrição da seleção do usuário. Ou seja, quando a configuração é uma ListPreference ou outra configuração de múltipla escolha, chame setSummary() quando a configuração mudar para exibir o status atual (como a configuração Sleep mostrada na figura 5).

Observação: Conforme descrito no documento do Projeto para Android sobre Configurações, recomendamos atualizar o sumário de ListPreference a cada vez que o usuário alterar a preferência para descrever a configuração atual.

Para um gerenciamento adequado do ciclo de vida na atividade, recomendamos registrar e remover o registro de SharedPreferences.OnSharedPreferenceChangeListener durante os retornos de chamada de onResume() e onPause() respectivamente:

@Override
protected void onResume() {
    super.onResume();
    getPreferenceScreen().getSharedPreferences()
            .registerOnSharedPreferenceChangeListener(this);
}

@Override
protected void onPause() {
    super.onPause();
    getPreferenceScreen().getSharedPreferences()
            .unregisterOnSharedPreferenceChangeListener(this);
}

Atenção: Ao chamar {@linkandroid.content.SharedPreferences#registerOnSharedPreferenceChangeListener registerOnSharedPreferenceChangeListener()}, o gerenciador de preferências não armazena atualmente nenhuma referência ao ouvinte. É preciso armazenar uma referência forte à escuta, senão ela será suscetível à coleta de lixo. Recomendamos manter uma referência à escuta nos dados de instância de um objeto que existirá enquanto a escuta for necessária.

Por exemplo, no código a seguir, o autor da chamada não mantém nenhuma referência à escuta. Como resultado, a escuta estará sujeita à coleta de lixo e falhará em algum momento indeterminado no futuro:

prefs.registerOnSharedPreferenceChangeListener(
  // Bad! The listener is subject to garbage collection!
  new SharedPreferences.OnSharedPreferenceChangeListener() {
  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
    // listener implementation
  }
});

Em vez disso, armazene uma referência à escuta nos dados de instância de um objeto que existirá enquanto a escuta for necessária:

SharedPreferences.OnSharedPreferenceChangeListener listener =
    new SharedPreferences.OnSharedPreferenceChangeListener() {
  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
    // listener implementation
  }
};
prefs.registerOnSharedPreferenceChangeListener(listener);

Gerenciamento de uso de rede

Desde o Android 4.0, o aplicativo Configurações do sistema permite aos usuários ver o quanto de dados de rede os aplicativos usam em primeiro e segundo plano. Portanto, os usuários podem desativar os dados em segundo plano de aplicativos individuais. Para evitar que os usuários desativem o acesso do aplicativo a dados em segundo plano, deve-se usar a conexão de dados de forma eficiente e permitir aos usuários refinar o uso de dados do aplicativo por meio das configurações do aplicativo.

Por exemplo, deve-se permitir ao usuário controlar a frequência de sincronização dos dados do aplicativo para uploads/downloads somente quando estiver em Wi-Fi, o aplicativo usar dados em deslocamento etc. Com esses controles disponíveis para eles, é bem menos provável que os usuários desativem o acesso do aplicativo a dados quando eles se aproximam dos limites que definem nas Configurações do sistema porque, em vez disso, podem controlar precisamente a quantidade de dados que o aplicativo usa.

Depois de adicionadas as preferências necessárias em PreferenceActivity para controlar os hábitos de dados do aplicativo, adicione um filtro de intents para ACTION_MANAGE_NETWORK_USAGE no arquivo de manifesto. Por exemplo:

<activity android:name="SettingsActivity" ... >
    <intent-filter>
       <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
       <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Esse filtro de intents indica ao sistema que se trata da atividade que controla o uso de dados do aplicativo. Assim, quando o usuário inspeciona a quantidade de dados que o aplicativo está usando no aplicativo Configurações do sistema, um botão View application settings fica disponível e inicia PreferenceActivity para que o usuário refine a quantidade de dados que o aplicativo usa.

Composição de uma preferência personalizada

A estrutura de trabalho do Android contém uma variedade de subclasses Preference que permitem criar uma IU com diferentes tipos de configurações. No entanto, pode ser necessário descobrir uma configuração para a qual não há nenhuma solução incorporada, como um seletor de números ou seletor de datas. Nesse caso, será preciso criar uma preferência personalizada, estendendo a classe Preference ou uma das outras subclasses.

Ao estender a classe Preference, há algumas coisas importantes a fazer:

  • Especificar a interface do usuário exibida quando o usuário seleciona as configurações.
  • Salvar os valores da configuração conforme apropriado.
  • Inicializar Preference com o valor atual (ou padrão) quando ela é exibida.
  • Fornecer o valor padrão quando solicitado pelo sistema.
  • Se Preference fornece a própria IU (como uma caixa de diálogo, por exemplo), salve e restaure o estado para processar alterações de ciclo de vida (como quando o usuário gira a tela).

As seções a seguir descrevem como executar cada uma dessas tarefas.

Especificação da interface do usuário

Se a classe Preference for estendida, será preciso implementar onClick() para definir a ação que ocorre quando o usuário a seleciona. No entanto, a maioria das configurações personalizadas estendem DialogPreference para exibir uma caixa de diálogo, o que simplifica o procedimento. Ao estender DialogPreference, é preciso chamar setDialogLayoutResourcs() na classe do construtor para especificar o layout da caixa de diálogo.

Por exemplo, este é o construtor de um DialogPreference personalizado que declara o layout e especifica o texto dos botões padrão da caixa de diálogo positivo e negativo:

public class NumberPickerPreference extends DialogPreference {
    public NumberPickerPreference(Context context, AttributeSet attrs) {
        super(context, attrs);

        setDialogLayoutResource(R.layout.numberpicker_dialog);
        setPositiveButtonText(android.R.string.ok);
        setNegativeButtonText(android.R.string.cancel);

        setDialogIcon(null);
    }
    ...
}

Salvamento do valor da configuração

Para salvar um valor para a configuração a qualquer momento, chame um dos métodos persist*() da classe Preference, como persistInt() se o valor da configuração for um inteiro ou persistBoolean() para salvar um booleano.

Observação: Cada Preference pode salvar somente um tipo de dados, portanto, é preciso usar o método persist*() adequado para o tipo de dados usado pela Preference personalizada.

A opção por persistir a configuração pode depender da classe Preference estendida. Se estender DialogPreference, você deve persistir o valor somente quando a caixa de diálogo fechar devido a um resultado positivo (o usuário seleciona o botão “OK”).

Quando uma DialogPreference fecha, o sistema chama o método onDialogClosed(). O método contém um argumento booleano que especifica se o resultado do usuário é "positivo" — se o valor é true, o usuário selecionou o botão positivo e você deve salvar o novo valor. Por exemplo:

@Override
protected void onDialogClosed(boolean positiveResult) {
    // When the user selects "OK", persist the new value
    if (positiveResult) {
        persistInt(mNewValue);
    }
}

Nesse exemplo, mNewValue é um membro da classe que retém o valor atual da configuração. A chamada de persistInt() salva o valor no arquivo SharedPreferences (usando automaticamente a chave especificada no arquivo XML dessa Preference).

Inicialização do valor atual

Quando o sistema adiciona o Preference à tela, ele chama onSetInitialValue() para notificar se a configuração tem um valor persistido. Se não houver valor persistido, essa chamada fornece o valor padrão.

O método onSetInitialValue() passa um booleano, restorePersistedValue, para indicar se um valor já foi persistido para a configuração. Se for true, deve-se recuperar o valor persistido chamando um dos métodos getPersisted*() da classe Preference, como getPersistedInt() para um valor inteiro. Geralmente se recupera o valor persistido para atualizar adequadamente a IU de forma a refletir o valor salvo anteriormente.

Se restorePersistedValue for false, deve-se usar o valor padrão passado no segundo argumento.

@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
    if (restorePersistedValue) {
        // Restore existing state
        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
    } else {
        // Set default state from the XML attribute
        mCurrentValue = (Integer) defaultValue;
        persistInt(mCurrentValue);
    }
}

Cada método getPersisted*() recebe um argumento que especifica o valor padrão a usar se não houver nenhum valor persistido ou se a chave não existir. No exemplo acima, uma constante local é usada para especificar o valor padrão se getPersistedInt() não puder retornar nenhum valor persistido.

Atenção: Não é possível usar defaultValue como valor padrão no método getPersisted*() porque seu valor é sempre nulo quando restorePersistedValue é true.

Fornecimento de um valor padrão

Se a instância da classe Preference especificar um valor padrão (com o atributo android:defaultValue), o sistema chamará onGetDefaultValue() quando instanciar o objeto para recuperar o valor. É preciso implementar esse método para que o sistema salve o valor padrão em SharedPreferences. Por exemplo:

@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
    return a.getInteger(index, DEFAULT_VALUE);
}

Os argumentos do método oferecem todo o necessário — a matriz de atributos e a posição do índice do android:defaultValue, que é preciso recuperar. É preciso implementar esse método para extrair o valor padrão do atributo porque deve-se especificar um valor padrão local para o atributo caso o valor seja indefinido.

Salvamento e restauração do estado da preferência

Como um View em um layout, a subclasse Preference é responsável por salvar e restaurar seu estado caso a atividade ou o fragmento seja reiniciado (como ocorre quando o usuário gira a tela). Para salvar e restaurar adequadamente o estado da classe Preference, é preciso implementar os métodos de retorno de chamada do ciclo de vida onSaveInstanceState() e onRestoreInstanceState().

O estado de Preference é definido por um objeto que implementa a interface Parcelable. A estrutura de trabalho do Android fornece esse objeto como um ponto inicial para definir o objeto de estado: a classe Preference.BaseSavedState.

Para definir como a classe Preference salva seu estado, deve-se estender a classe Preference.BaseSavedState. É preciso substituir alguns métodos e definir o objeto CREATOR.

Na maioria dos aplicativos, é possível copiar a implementação a seguir e simplesmente alterar as linhas que processam o value se a subclasse Preference salvar um tipo de dados que não seja um inteiro.

private static class SavedState extends BaseSavedState {
    // Member that holds the setting's value
    // Change this data type to match the type saved by your Preference
    int value;

    public SavedState(Parcelable superState) {
        super(superState);
    }

    public SavedState(Parcel source) {
        super(source);
        // Get the current preference's value
        value = source.readInt();  // Change this to read the appropriate data type
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        // Write the preference's value
        dest.writeInt(value);  // Change this to write the appropriate data type
    }

    // Standard creator object using an instance of this class
    public static final Parcelable.Creator<SavedState> CREATOR =
            new Parcelable.Creator<SavedState>() {

        public SavedState createFromParcel(Parcel in) {
            return new SavedState(in);
        }

        public SavedState[] newArray(int size) {
            return new SavedState[size];
        }
    };
}

Com a implementação acima de Preference.BaseSavedState adicionada ao aplicativo (geralmente como uma subclasse da subclasse Preference), é preciso implementar os métodos onSaveInstanceState() e onRestoreInstanceState() da subclasse Preference.

Por exemplo:

@Override
protected Parcelable onSaveInstanceState() {
    final Parcelable superState = super.onSaveInstanceState();
    // Check whether this Preference is persistent (continually saved)
    if (isPersistent()) {
        // No need to save instance state since it's persistent,
        // use superclass state
        return superState;
    }

    // Create instance of custom BaseSavedState
    final SavedState myState = new SavedState(superState);
    // Set the state's value with the class member that holds current
    // setting value
    myState.value = mNewValue;
    return myState;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    // Check whether we saved the state in onSaveInstanceState
    if (state == null || !state.getClass().equals(SavedState.class)) {
        // Didn't save the state, so call superclass
        super.onRestoreInstanceState(state);
        return;
    }

    // Cast state to custom BaseSavedState and pass to superclass
    SavedState myState = (SavedState) state;
    super.onRestoreInstanceState(myState.getSuperState());

    // Set this Preference's widget to reflect the restored state
    mNumberPicker.setValue(myState.value);
}