Os carregadores foram descontinuados no Android 9 (nível 28 da API). A opção recomendada para
para processar o carregamento de dados e os ciclos de vida de Activity
e Fragment
é usar uma
combinação de objetos ViewModel
e LiveData
.
Os modelos de visualização sobrevivem a mudanças de configuração, como os carregadores, mas com
menos código boilerplate. O LiveData
fornece uma maneira de carregar dados com reconhecimento de ciclo de vida que pode ser reutilizado
vários modelos de visualização. Também é possível combinar LiveData
usando
MediatorLiveData
.
Todas as consultas observáveis, como as de um
O banco de dados do Room (link em inglês) pode ser usado para observar mudanças.
aos dados.
ViewModel
e LiveData
também estão disponíveis quando você não tem acesso
ao LoaderManager
, como em um
Service
. Usar os dois em
acoplado oferece uma maneira fácil de acessar os dados de que seu aplicativo precisa sem ter que lidar com a interface
no ciclo de vida de ML. Para saber mais sobre LiveData
, consulte a
Visão geral de LiveData
. Para saber mais sobre
ViewModel
, consulte a visão geral de ViewModel
.
A API Loader permite carregar dados de um
provedor de conteúdo
ou outra fonte de dados para exibição em um FragmentActivity
ou Fragment
.
Sem os carregadores, alguns dos problemas que você pode encontrar incluem:
- Se você buscar os dados diretamente na atividade ou no fragmento, seus usuários sofrem com a falta de capacidade de resposta devido a um desempenho potencialmente lento consultas da linha de execução de interface.
- Se você buscar os dados em outra linha de execução, talvez com
AsyncTask
, você será responsável por gerenciar a linha de execução e a linha de execução de IU por vários eventos de ciclo de vida de atividades ou fragmentos, comoonDestroy()
e mudanças de configuração.
Os carregadores resolvem esses problemas e incluem outros benefícios:
- Os carregadores são executados em linhas de execução separadas para evitar uma interface lenta ou sem resposta.
- Os carregadores simplificam o gerenciamento de linhas de execução fornecendo métodos de callback quando os eventos antes que ocorram mudanças.
- Os carregadores persistem e armazenam em cache os resultados nas alterações de configuração para evitar consultas duplicadas.
- Os carregadores podem implementar um observador para monitorar alterações no
fonte de dados. Por exemplo,
CursorLoader
automaticamente registra umContentObserver
para acionar uma atualização quando os dados mudam.
Resumo da API Loader
Há várias classes e interfaces que podem estar envolvidas no uso carregadores em um app. Elas estão resumidas na tabela a seguir:
Classe/interface | Descrição |
---|---|
LoaderManager |
Uma classe abstrata associada a um FragmentActivity ou
Fragment para gerenciar um ou mais
Loader instâncias. Há apenas um
LoaderManager por atividade ou fragmento, mas uma
LoaderManager pode gerenciar vários carregadores.
Para receber um Para começar a carregar dados de um carregador, chame
|
LoaderManager.LoaderCallbacks |
Essa interface contém métodos de callback que são chamados quando
eventos do carregador. A interface define três métodos de callback:
initLoader() ou
restartLoader()
|
Loader |
Os carregadores executam o carregamento de dados. Essa classe é abstrata e disponibiliza
como classe de base para todos os carregadores. É possível criar subclasses diretamente
Loader ou use uma das seguintes opções
para simplificar a implementação:
|
As seções a seguir mostram como usar essas classes e interfaces em um aplicativo.
Usar carregadores em um aplicativo
Esta seção descreve como usar os carregadores em um aplicativo do Android. Um aplicativos que usam carregadores geralmente incluem o seguinte:
FragmentActivity
ouFragment
- Uma instância do
LoaderManager
. - Um
CursorLoader
para carregar dados com suporte de umContentProvider
. Como alternativa, é possível implementar a própria subclasse deLoader
ouAsyncTaskLoader
a carregar dados de outra fonte. - Uma implementação de
LoaderManager.LoaderCallbacks
. É aqui que você cria novos carregadores e gerencia suas referências a carregadores. - Uma maneira de exibir os dados do carregador, como um
SimpleCursorAdapter
. - Uma fonte de dados, como
ContentProvider
, ao usar umaCursorLoader
.
Iniciar um carregador
O LoaderManager
gerencia uma ou mais instâncias de Loader
em um FragmentActivity
ou
Fragment
. Há apenas um LoaderManager
por atividade ou fragmento.
Você normalmente
inicializar um Loader
no método onCreate()
da atividade ou no
onCreate()
. Você
faça o seguinte:
Kotlin
supportLoaderManager.initLoader(0, null, this)
Java
// Prepare the loader. Either re-connect with an existing one, // or start a new one. getSupportLoaderManager().initLoader(0, null, this);
O método initLoader()
usa
os seguintes parâmetros:
- Um código exclusivo que identifica o carregador. Neste exemplo, o ID é
0
. - Argumentos opcionais para fornecer ao carregador em
construção (
null
neste exemplo). - Uma implementação de
LoaderManager.LoaderCallbacks
, que as chamadasLoaderManager
para relatar eventos do carregador. Neste exemplo, a classe local implementa a interfaceLoaderManager.LoaderCallbacks
, para que ela transmita uma referência para si mesmo,this
.
A chamada initLoader()
garante que um carregador
foi inicializado e está ativo. Ela tem dois possíveis resultados:
- Se o carregador especificado pelo ID já existir, o último carregador criado é reutilizado.
- Se o carregador especificado pelo ID não existir,
initLoader()
aciona o MétodoLoaderManager.LoaderCallbacks
onCreateLoader()
. É aqui que você implementa o código para instanciar e retornar um novo carregador. Para mais discussões, consulte a seção sobreonCreateLoader
.
Em ambos os casos, o LoaderManager.LoaderCallbacks
fornecido
implementação é associada ao carregador e é chamada quando o
o estado do carregador muda. Se, no momento da chamada, o autor da chamada estiver na
estado iniciado e o carregador solicitado já existe e gerou seu
dados, o sistema chamará onLoadFinished()
imediatamente durante initLoader()
. Você precisa estar preparado para que isso aconteça. Para mais informações sobre esse retorno de chamada, consulte a seção sobre
onLoadFinished
.
O método initLoader()
retorna o Loader
que é criado,
mas você não precisa capturar uma referência a ele. O LoaderManager
gerencia
a vida útil do carregador automaticamente. O LoaderManager
inicia e para de carregar quando necessário e mantém o estado do carregador
e o conteúdo associado a ela.
Dessa forma, você raramente interage com os carregadores
diretamente.
Os métodos LoaderManager.LoaderCallbacks
são mais usados para intervir no carregamento
quando determinados eventos ocorrem. Para ver mais discussões sobre esse tópico, consulte a seção Uso dos callbacks de LoaderManager.
Reiniciar um carregador
Quando você usa initLoader()
,
mostrado na seção anterior, ele usa um carregador existente com o ID especificado, se houver.
Caso contrário, uma conta será criada. Mas, às vezes, você quer descartar seus dados antigos
e começar de novo.
Para descartar seus dados antigos, use restartLoader()
. Por exemplo, os seguintes
implementação de SearchView.OnQueryTextListener
reinicializações
no carregador quando a consulta do usuário é alterada. É necessário reiniciar o carregador para
que ele pode usar o filtro de pesquisa revisado para fazer uma nova consulta.
Kotlin
fun onQueryTextChanged(newText: String?): Boolean { // 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. curFilter = if (newText?.isNotEmpty() == true) newText else null supportLoaderManager.restartLoader(0, null, this) return true }
Java
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. curFilter = !TextUtils.isEmpty(newText) ? newText : null; getSupportLoaderManager().restartLoader(0, null, this); return true; }
Usar os callbacks de LoaderManager
LoaderManager.LoaderCallbacks
é uma interface de callback.
que permite que um cliente interaja com o LoaderManager
.
Os carregadores, em especial o CursorLoader
, devem
conseguem reter os dados após serem interrompidos. Isso permite que os aplicativos mantenham
dados nos métodos onStop()
e onStart()
da atividade ou do fragmento, de modo que
quando os usuários retornam a um aplicativo, eles não precisam esperar que os dados
atualizar.
Use os métodos LoaderManager.LoaderCallbacks
para saber quando criar um novo carregador e informar ao aplicativo quando ele é
parar de usar os dados do carregador.
LoaderManager.LoaderCallbacks
inclui estes
métodos:
onCreateLoader()
: instancia e retorna um novoLoader
para o ID fornecido.
-
onLoadFinished()
: chamado quando um carregador criado anteriormente termina seu carregamento.
onLoaderReset()
: chamado quando um carregador anteriormente criado é redefinido, tornando a indisponibilidade dos dados.
Esses métodos são descritos com mais detalhes nas seções seguintes.
onCreateLoader
Quando você tenta acessar um carregador, como por meio de initLoader()
, ele verifica se
o carregador especificado pelo ID existe. Caso contrário, o método onCreateLoader()
do LoaderManager.LoaderCallbacks
será acionado. Isso
é onde você cria um novo carregador. Normalmente, isso é uma CursorLoader
, mas você pode implementar sua própria subclasse Loader
.
No exemplo abaixo, o onCreateLoader()
método de callback cria um CursorLoader
usando o método construtor, que
requer o conjunto completo de informações necessárias para realizar uma consulta ao ContentProvider
. Especificamente, ele precisa do seguinte:
- uri: o URI do conteúdo a ser recuperado.
- projeção: uma lista de quais colunas retornar. Em aprovação
null
retorna todas as colunas, o que é ineficiente. - selection: um filtro que declara quais linhas retornar,
formatada como uma cláusula WHERE do SQL (excluindo a própria WHERE). Em aprovação
null
retorna todas as linhas do URI especificado. - selectionArgs: se você incluir "?"s na seleção, eles são substituídos pelos valores de selectionArgs na ordem em que aparecem no a seleção. Os valores são vinculados como strings.
- sortOrder: como ordenar as linhas, formatadas como um SQL
cláusula ORDER BY (excluindo ORDER BY). Aprovando
null
usa a ordem de classificação padrão, que pode ser desordenada.
Kotlin
// If non-null, this is the current filter the user has provided. private var curFilter: String? = null ... override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { // 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. val baseUri: Uri = if (curFilter != null) { Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, Uri.encode(curFilter)) } else { ContactsContract.Contacts.CONTENT_URI } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. val select: String = "((${Contacts.DISPLAY_NAME} NOTNULL) AND (" + "${Contacts.HAS_PHONE_NUMBER}=1) AND (" + "${Contacts.DISPLAY_NAME} != ''))" return (activity as? Context)?.let { context -> CursorLoader( context, baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, "${Contacts.DISPLAY_NAME} COLLATE LOCALIZED ASC" ) } ?: throw Exception("Activity cannot be null") }
Java
// If non-null, this is the current filter the user has provided. String curFilter; ... 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 (curFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(curFilter)); } 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 criado anteriormente termina seu carregamento. Esse método será chamado antes da liberação dos últimos dados fornecido para esse carregador. Neste ponto, remova todo o uso de os dados antigos, já que serão lançados. Mas não libere os dados por conta própria: o carregador é proprietário e cuida disso.
O carregador libera os dados quando sabe que o aplicativo não está mais
usá-lo. Por exemplo, se os dados forem um cursor de uma CursorLoader
,
não chame close()
. Se o cursor estiver sendo
colocado em um CursorAdapter
, use o método swapCursor()
para que o
o Cursor
antigo não é fechado, conforme mostrado no exemplo a seguir:
Kotlin
private lateinit var adapter: SimpleCursorAdapter ... override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor?) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) adapter.swapCursor(data) }
Java
// This is the Adapter being used to display the list's data. SimpleCursorAdapter adapter; ... 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.) adapter.swapCursor(data); }
onLoaderReset
Esse método é chamado quando um carregador criado anteriormente é redefinido tornando os dados indisponíveis. Com esse callback, você descobre quando os dados prestes a ser lançado para que você possa remover sua referência a ele.
Essa implementação chama
swapCursor()
com um valor de null
:
Kotlin
private lateinit var adapter: SimpleCursorAdapter ... override fun onLoaderReset(loader: Loader<Cursor>) { // 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. adapter.swapCursor(null) }
Java
// This is the Adapter being used to display the list's data. SimpleCursorAdapter adapter; ... 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. adapter.swapCursor(null); }
Exemplo
Como exemplo, esta é a implementação completa de um Fragment
que exibe um ListView
contendo
os resultados de uma consulta no provedor de conteúdo de contatos. Ele usa um CursorLoader
para gerenciar a consulta no provedor.
Como este exemplo é de um aplicativo para acessar os contatos de um usuário, seu
manifesto deve incluir a permissão
READ_CONTACTS
:
Kotlin
private val CONTACTS_SUMMARY_PROJECTION: Array<String> = arrayOf( Contacts._ID, Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS, Contacts.CONTACT_PRESENCE, Contacts.PHOTO_ID, Contacts.LOOKUP_KEY ) class CursorLoaderListFragment : ListFragment(), SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { // This is the Adapter being used to display the list's data. private lateinit var mAdapter: SimpleCursorAdapter // If non-null, this is the current filter the user has provided. private var curFilter: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Prepare the loader. Either re-connect with an existing one, // or start a new one. loaderManager.initLoader(0, null, this) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, 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 = SimpleCursorAdapter(activity, android.R.layout.simple_list_item_2, null, arrayOf(Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS), intArrayOf(android.R.id.text1, android.R.id.text2), 0 ) listAdapter = mAdapter } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { // Place an action bar item for searching. menu.add("Search").apply { setIcon(android.R.drawable.ic_menu_search) setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM) actionView = SearchView(activity).apply { setOnQueryTextListener(this@CursorLoaderListFragment) } } } override fun onQueryTextChange(newText: String?): Boolean { // 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. curFilter = if (newText?.isNotEmpty() == true) newText else null loaderManager.restartLoader(0, null, this) return true } override fun onQueryTextSubmit(query: String): Boolean { // Don't care about this. return true } override fun onListItemClick(l: ListView, v: View, position: Int, id: Long) { // Insert desired behavior here. Log.i("FragmentComplexList", "Item clicked: $id") } override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { // 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. val baseUri: Uri = if (curFilter != null) { Uri.withAppendedPath(Contacts.CONTENT_URI, Uri.encode(curFilter)) } else { Contacts.CONTENT_URI } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. val select: String = "((${Contacts.DISPLAY_NAME} NOTNULL) AND (" + "${Contacts.HAS_PHONE_NUMBER}=1) AND (" + "${Contacts.DISPLAY_NAME} != ''))" return (activity as? Context)?.let { context -> CursorLoader( context, baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, "${Contacts.DISPLAY_NAME} COLLATE LOCALIZED ASC" ) } ?: throw Exception("Activity cannot be null") } override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data) } override fun onLoaderReset(loader: Loader<Cursor>) { // 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) } }
Java
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 curFilter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); } @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, 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); } @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. curFilter = !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 (curFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(curFilter)); } 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
Os exemplos a seguir ilustram como usar os carregadores:
- LoaderCursor: uma versão completa do snippet anterior.
- Recuperar uma lista de contatos:
um tutorial que usa um
CursorLoader
para recuperar dados do provedor de contatos. - LoaderThrottle: um exemplo de como usar a limitação para reduzir o número de consultas que um provedor de conteúdo realiza quando seus dados são alterados.