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

Carregadores

Introduzidos no Android 3.0, os carregadores facilitam o carregamento assíncrono de dados em uma atividade ou fragmento. Os carregadores têm as seguintes características:

  • Eles estão disponíveis para cada Activity e Fragment.
  • Eles fornecem carregamento assíncrono de dados.
  • Eles monitoram a origem dos dados e fornecem novos resultados quando o conteúdo é alterado.
  • Eles se reconectam automaticamente ao cursor do último carregador quando são recriados após uma alteração de configuração. Portanto, eles não precisam reconsultar os dados.

Resumo da Loader API

Há várias classes e interfaces que podem ser envolvidas no uso de carregadores em um aplicativo. Elas são resumidas nesta tabela:

Classe/interface Descrição
LoaderManager Uma classe abstrata associada a Activity ou Fragment para gerenciar uma ou mais instâncias de Loader. Isso ajuda um aplicativo a gerenciar operações executadas por longos períodos juntamente com o ciclo de vida de Activity ou Fragment; o uso mais comum é com CursorLoader. No entanto, os aplicativos têm a liberdade de criar os próprios carregadores para outros tipos de dados.

Há apenas um LoaderManager por atividade ou fragmento. No entanto, um LoaderManager pode ter vários carregadores.
LoaderManager.LoaderCallbacks Uma interface de retorno de chamada para um cliente interagir com LoaderManager. Por exemplo, usa-se o método de retorno de chamada onCreateLoader() para criar um novo carregador.
Loader Uma classe abstrata que realiza carregamento assíncrono de dados. Essa é a classe de base de um carregador. Geralmente, você usaria CursorLoader, mas é possível implementar subclasse própria. Enquanto os carregadores estiverem ativos, devem monitorar a origem dos dados e fornecer novos resultados quando o conteúdo for alterado.
AsyncTaskLoader Um carregador abstrato que fornece uma AsyncTask para realizar o trabalho.
CursorLoader Uma subclasse de AsyncTaskLoader que consulta ContentResolver e retorna um Cursor. Essa classe implementa o protocolo Loader de forma padrão para cursores de consulta, desenvolvendo AsyncTaskLoader para realizar a consulta de cursor em um encadeamento de segundo plano para que a IU do aplicativo não seja bloqueada. Usar esse carregador é a melhor maneira de carregar dados de forma assíncrona de um ContentProvider, em vez de realizar uma consulta gerenciada pelas APIs da atividade ou do fragmento.

As classes e interfaces na tabela acima são os componentes essenciais que você usará para implementar um carregador no aplicativo. Você não precisará de todos eles para cada carregador criado, mas sempre precisará de uma referência a LoaderManager para inicializar um carregador e uma implementação de uma classe Loader, como CursorLoader. As seguintes seções mostram como usar essas classes e interfaces em um aplicativo.

Uso de carregadores em um aplicativo

Esta seção descreve como usar os carregadores em um aplicativo do Android. Um aplicativo que usa os carregadores geralmente inclui o seguinte:

Início de um carregador

O LoaderManager gerencia uma ou mais instâncias de Loader dentro de uma Activity ou um Fragment. Há apenas um LoaderManager por atividade ou fragmento.

Geralmente, inicializa-se um Loader dentro do método onCreate() da atividade, ou dentro do método onActivityCreated() do fragmento. Faça isso da seguinte maneira:

// Prepare the loader.  Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);

O método initLoader() recebe os seguintes parâmetros:

  • Um ID único que identifica o carregador. Nesse exemplo, o ID é 0.
  • Argumentos opcionais para fornecer ao carregador em construção (null nesse exemplo).
  • Uma implementação de LoaderManager.LoaderCallbacks, que LoaderManager chama para relatar eventos do carregador. Nesse exemplo, a classe local implementa a interface de LoaderManager.LoaderCallbacks, para que ela passe uma referência para si, this.

A chamada de initLoader() garante que o carregador foi inicializado e que está ativo. Ela tem dois possíveis resultados:

  • Se o carregador especificado pelo ID já existir, o último carregador criado será usado novamente.
  • Se o carregador especificado pelo ID não existir, initLoader() ativará o método LoaderManager.LoaderCallbacks onCreateLoader(). É aqui que você implementa o código para instanciar e retornar um novo carregador. Para obter mais informações, consulte a seção onCreateLoader.

Em qualquer um dos casos, a implementação de LoaderManager.LoaderCallbacks fornecida é associada ao carregador e será chamada quando o estado do carregador mudar. Se, no momento dessa chamada, o autor dela estiver no estado inicializado e o carregador solicitado já existir e tiver gerado seus dados, o sistema chamará onLoadFinished() imediatamente (durante initLoader()), você deverá estar preparado para tais situações. Consulte onLoadFinished para obter mais informações sobre esse retorno de chamada

Observe que o método initLoader() retorna o Loader que é criado, mas você não precisará capturar uma referência para ele. O LoaderManager gerencia a vida do carregador automaticamente. O LoaderManager inicia e interrompe o carregamento quando necessário, além de manter o estado do carregador e do conteúdo associado. À medida que isso ocorre, você raramente interage com os carregadores diretamente (para ver um exemplo de métodos para aprimorar o comportamento de um carregador, consulte o exemplo de LoaderThrottle). Os métodos LoaderManager.LoaderCallbacks são mais comumente usados para intervir no processo de carregamento quando eventos específicos ocorrem. Para obter mais informações sobre esse assunto, consulte Uso dos retornos de chamada de LoaderManager.

Reinício de um carregador

Ao usar initLoader(), como mostrado acima, ele usará um carregador existente com o ID especificado, se houver. Caso contrário, um carregador será criado. No entanto, às vezes, você quer descartar os dados antigos e começar do início.

Para descartar os dados antigos, use restartLoader(). Por exemplo, essa implementação de SearchView.OnQueryTextListener reinicia o carregador quando a consulta do usuário é alterada. O carregador precisa ser reiniciado para que possa usar o filtro de busca revisado para realizar uma nova consulta:

public boolean onQueryTextChanged(String newText) {
    // Called when the action bar search text has changed.  Update
    // the search filter, and restart the loader to do a new query
    // with this filter.
    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
    getLoaderManager().restartLoader(0, null, this);
    return true;
}

Uso dos retornos de chamada de LoaderManager

LoaderManager.LoaderCallbacks é uma interface de retorno de chamada que permite que um cliente interaja com o LoaderManager.

Carregadores, em determinado CursorLoader, devem reter os dados após serem interrompidos. Isso permite que os aplicativos mantenham os dados dos métodos onStop() e onStart() do fragmento ou da atividade para que, quando os usuários voltarem a um aplicativo, não tenham que esperar o recarregamento dos dados. Você usa os métodos LoaderManager.LoaderCallbacks para saber quando deve criar um novo carregador e para dizer ao aplicativo quando deve interromper o uso dos dados de um carregador.

LoaderManager.LoaderCallbacks inclui esses métodos:

  • onLoadFinished() — chamado quando um carregador anteriormente criado termina seu carregamento.
  • onLoaderReset() — chamado quando um carregador anteriormente criado é reiniciado, tornando os dados indisponíveis.

Esses métodos são descritos com mais informações nas seguintes seções.

onCreateLoader

Ao tentar acessar um carregador (por exemplo, por meio de initLoader()), ele verifica se o carregador especificado pelo código existe. Caso contrário, é acionado o método LoaderManager.LoaderCallbacks onCreateLoader(). É aqui que você pode criar um novo carregador. Normalmente, o carregador será um CursorLoader, nas você pode implementar sua própria subclasse Loader.

Nesse exemplo, o método de retorno de chamada onCreateLoader() cria um CursorLoader. Você deve compilar CursorLoader usando o método construtor, que exige um conjunto completo de informações necessárias para realizar uma consulta ao ContentProvider. Especificamente, ele precisa de:

  • uri — a URI do conteúdo a ser recuperado.
  • projection — uma lista de quais colunas devem ser retornadas. Passar null retornará todas as colunas, o que é ineficiente.
  • selection — um filtro que declara quais linhas devem retornar, formatado por uma cláusula SQL WHERE (excluindo WHERE). Passar null retornará todas as linhas da URI em questão.
  • selectionArgs — é possível incluir interrogações na seleção, que serão substituídas pelos valores de selectionArgs, na ordem em que aparecem na seleção. Os valores serão vinculados como Strings.
  • sortOrder — como ordenar as linhas, formatadas em uma cláusula SQL ORDER BY (excluindo ORDER BY). Passar null usará a ordem de classificação padrão, que pode ser desordenada.

Por exemplo:

 // If non-null, this is the current filter the user has provided.
String mCurFilter;
...
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    // This is called when a new Loader needs to be created.  This
    // sample only has one Loader, so we don't care about the ID.
    // First, pick the base URI to use depending on whether we are
    // currently filtering.
    Uri baseUri;
    if (mCurFilter != null) {
        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                  Uri.encode(mCurFilter));
    } else {
        baseUri = Contacts.CONTENT_URI;
    }

    // Now create and return a CursorLoader that will take care of
    // creating a Cursor for the data being displayed.
    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
            + Contacts.DISPLAY_NAME + " != '' ))";
    return new CursorLoader(getActivity(), baseUri,
            CONTACTS_SUMMARY_PROJECTION, select, null,
            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}

onLoadFinished

Esse método é chamado quando um carregador anteriormente criado terminar seu carregamento. Esse método certamente será chamado antes da liberação dos últimos dados que forem fornecidos por esse carregador. Neste ponto, você deve remover todo o uso dos dados antigos (já que serão liberados em breve), mas não deve fazer a liberação dos dados, já que pertencem ao carregador e ele lidará com isso.

O carregador liberará os dados quando souber que o aplicativo não está mais usando-os. Por exemplo: se os dados forem um cursor de um CursorLoader, você não deve chamar close() por conta própria. Se o cursor estiver sendo colocado em um CursorAdapter, você deve usar o método swapCursor() para que o antigo Cursor não seja fechado. Por exemplo:

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter; ... public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in.  (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data); }

onLoaderReset

Esse método é chamado quando um carregador anteriormente criado é reiniciado, tornando os dados indisponíveis. Esse retorno de chamada permite que você descubra quando os dados estão prestes a serem liberados para que seja possível remover a referência a eles.  

Essa implementação chama swapCursor() com um valor de null:

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
...

public void onLoaderReset(Loader<Cursor> loader) {
    // This is called when the last Cursor provided to onLoadFinished()
    // above is about to be closed.  We need to make sure we are no
    // longer using it.
    mAdapter.swapCursor(null);
}

Exemplo

Como exemplo, a seguir há uma implementação completa de um Fragment que exibe uma ListView contendo os resultados de uma consulta aos provedores de conteúdo de contatos. Ela usa um CursorLoader para gerenciar a consulta no provedor.

Para um aplicativo acessar os contatos de um usuário, como neste exemplo, o manifesto deverá incluir a permissão READ_CONTACTS.

public static class CursorLoaderListFragment extends ListFragment
        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {

    // This is the Adapter being used to display the list's data.
    SimpleCursorAdapter mAdapter;

    // If non-null, this is the current filter the user has provided.
    String mCurFilter;

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

        // Give some text to display if there is no data.  In a real
        // application this would come from a resource.
        setEmptyText("No phone numbers");

        // We have a menu item to show in action bar.
        setHasOptionsMenu(true);

        // Create an empty adapter we will use to display the loaded data.
        mAdapter = new SimpleCursorAdapter(getActivity(),
                android.R.layout.simple_list_item_2, null,
                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
        setListAdapter(mAdapter);

        // Prepare the loader.  Either re-connect with an existing one,
        // or start a new one.
        getLoaderManager().initLoader(0, null, this);
    }

    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // Place an action bar item for searching.
        MenuItem item = menu.add("Search");
        item.setIcon(android.R.drawable.ic_menu_search);
        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        SearchView sv = new SearchView(getActivity());
        sv.setOnQueryTextListener(this);
        item.setActionView(sv);
    }

    public boolean onQueryTextChange(String newText) {
        // Called when the action bar search text has changed.  Update
        // the search filter, and restart the loader to do a new query
        // with this filter.
        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
        getLoaderManager().restartLoader(0, null, this);
        return true;
    }

    @Override public boolean onQueryTextSubmit(String query) {
        // Don't care about this.
        return true;
    }

    @Override public void onListItemClick(ListView l, View v, int position, long id) {
        // Insert desired behavior here.
        Log.i("FragmentComplexList", "Item clicked: " + id);
    }

    // These are the Contacts rows that we will retrieve.
    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
        Contacts._ID,
        Contacts.DISPLAY_NAME,
        Contacts.CONTACT_STATUS,
        Contacts.CONTACT_PRESENCE,
        Contacts.PHOTO_ID,
        Contacts.LOOKUP_KEY,
    };
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created.  This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        Uri baseUri;
        if (mCurFilter != null) {
            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                    Uri.encode(mCurFilter));
        } else {
            baseUri = Contacts.CONTENT_URI;
        }

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + Contacts.DISPLAY_NAME + " != '' ))";
        return new CursorLoader(getActivity(), baseUri,
                CONTACTS_SUMMARY_PROJECTION, select, null,
                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // Swap the new cursor in.  (The framework will take care of closing the
        // old cursor once we return.)
        mAdapter.swapCursor(data);
    }

    public void onLoaderReset(Loader<Cursor> loader) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed.  We need to make sure we are no
        // longer using it.
        mAdapter.swapCursor(null);
    }
}

Mais exemplos

Há alguns exemplos variados na ApiDemos que ilustra o uso de carregadores:

  • LoaderCursor — uma versão completa do snippet exibido acima.
  • LoaderThrottle — um exemplo de como usar o regulador para reduzir o número de consultas que o provedor de conteúdo realiza quando os dados são alterados.

Para obter mais informações sobre o download e a instalação de exemplos de SDK, consulte Obtenção dos exemplos.