Para responder a essa pergunta, você precisa digitar o LoaderManager
código. Embora a documentação para o próprio LoaderManager não seja clara o suficiente (ou não haveria essa pergunta), a documentação para o LoaderManagerImpl, uma subclasse do abstrato LoaderManager, é muito mais esclarecedora.
initLoader
Ligue para inicializar um ID específico com um carregador. Se esse ID já tiver um Loader associado, ele permanecerá inalterado e os retornos de chamada anteriores serão substituídos pelos recém-fornecidos. Se não houver atualmente um Loader para o ID, um novo será criado e iniciado.
Essa função geralmente deve ser usada quando um componente está sendo inicializado, para garantir que um Loader em que ele se baseia seja criado. Isso permite reutilizar os dados de um Carregador existente, se já houver um, de modo que, por exemplo, quando uma Atividade é recriada após uma alteração na configuração, ela não precisa recriar seus carregadores.
restartLoader
Ligue para recriar o Loader associado a um ID específico. Se houver atualmente um Loader associado a esse ID, ele será cancelado / parado / destruído conforme apropriado. Um novo Loader com os argumentos fornecidos será criado e seus dados entregues a você assim que estiverem disponíveis.
[...] Depois de chamar esta função, qualquer Carregador anterior associado a este ID será considerado inválido e você não receberá mais atualizações de dados.
Existem basicamente dois casos:
- O carregador com o ID não existe: ambos os métodos criarão um novo carregador, portanto não haverá diferença
- O carregador com o ID já existe:
initLoader
substituirá apenas os retornos de chamada passados como parâmetro, mas não cancelará ou interromperá o carregador. Para um CursorLoader
que significa que o cursor permanece aberto e ativo (se foi esse o caso antes da initLoader
chamada). `restartLoader, por outro lado, irá cancelar, parar e destruir o carregador (e fechar a fonte de dados subjacente como um cursor) e criar um novo carregador (que também criaria um novo cursor e executaria novamente a consulta se o carregador estiver um CursorLoader).
Aqui está o código simplificado para os dois métodos:
initLoader
LoaderInfo info = mLoaders.get(id);
if (info == null) {
// Loader doesn't already exist -> create new one
info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
// Loader exists -> only replace callbacks
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
restartLoader
LoaderInfo info = mLoaders.get(id);
if (info != null) {
LoaderInfo inactive = mInactiveLoaders.get(id);
if (inactive != null) {
// does a lot of stuff to deal with already inactive loaders
} else {
// Keep track of the previous instance of this loader so we can destroy
// it when the new one completes.
info.mLoader.abandon();
mInactiveLoaders.put(id, info);
}
}
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
Como podemos ver, caso o carregador não exista (info == null), ambos os métodos criarão um novo carregador (info = createAndInstallLoader (...)). Caso o carregador já exista, initLoader
substitua apenas os retornos de chamada (info.mCallbacks = ...) enquanto restartLoader
inativa o carregador antigo (ele será destruído quando o novo carregador concluir seu trabalho) e, em seguida, criará um novo.
Assim dito, agora está claro quando usar initLoader
e quando usar restartLoader
e por que faz sentido ter os dois métodos.
initLoader
é usado para garantir que haja um carregador inicializado. Se nenhum existir, um novo será criado, se já existir, será reutilizado. Sempre usamos esse método, a menos que seja necessário um novo carregador, porque a consulta a ser executada mudou (não os dados subjacentes, mas a consulta real, como na instrução SQL para um CursorLoader), caso em que chamaremos restartLoader
.
O ciclo de vida da atividade / fragmento não tem nada a ver com a decisão de usar um ou outro método (e não há necessidade de acompanhar as chamadas usando um sinalizador único, como sugeriu Simon)! Esta decisão é tomada exclusivamente com base na "necessidade" de um novo carregador. Se queremos executar a mesma consulta que usamos initLoader
, se queremos executar uma consulta diferente, usamos restartLoader
.
Nós sempre poderíamos usar, restartLoader
mas isso seria ineficiente. Após uma rotação da tela ou se o usuário sair do aplicativo e retornar posteriormente para a mesma atividade, geralmente queremos mostrar o mesmo resultado da consulta e, assim restartLoader
, recriar desnecessariamente o carregador e dispensar o resultado da consulta subjacente (potencialmente caro).
É muito importante entender a diferença entre os dados carregados e a "consulta" para carregar esses dados. Vamos supor que usamos um CursorLoader consultando uma tabela para pedidos. Se um novo pedido for adicionado a essa tabela, o CursorLoader usa onContentChanged () para informar a interface do usuário para atualizar e mostrar o novo pedido (não é necessário usar restartLoader
neste caso). Se queremos exibir apenas pedidos em aberto, precisamos de uma nova consulta e restartLoader
usaríamos para retornar um novo CursorLoader refletindo a nova consulta.
Existe alguma relação entre os dois métodos?
Eles compartilham o código para criar um novo Loader, mas fazem coisas diferentes quando um carregador já existe.
Ligar restartLoader
sempre chama initLoader
?
Não, nunca faz.
Posso ligar restartLoader
sem ter que ligar initLoader
?
Sim.
É seguro ligar initLoader
duas vezes para atualizar os dados?
É seguro ligar initLoader
duas vezes, mas nenhum dado será atualizado.
Quando devo usar um dos dois e por quê ?
Espero que isso fique claro depois das minhas explicações acima.
Mudanças na configuração
Um LoaderManager mantém seu estado através de alterações na configuração (incluindo alterações de orientação), assim você acha que não há mais nada a fazer. Pense de novo...
Antes de tudo, um LoaderManager não retém os retornos de chamada; portanto, se você não fizer nada, não receberá chamadas para seus métodos de retorno de chamada, como assim por diante, onLoadFinished()
e isso provavelmente interromperá seu aplicativo.
Portanto, temos que chamar pelo menos initLoader
para restaurar os métodos de retorno de chamada (a restartLoader
é, é claro, possível também). A documentação declara:
Se no momento da chamada o chamador estiver em seu estado inicial e o carregador solicitado já existir e tiver gerado seus dados, o retorno de chamada onLoadFinished(Loader, D)
será chamado imediatamente (dentro desta função) [...].
Isso significa que, se ligarmos initLoader
após uma mudança de orientação, receberemos uma onLoadFinished
ligação imediatamente porque os dados já estão carregados (assumindo que foi o caso antes da mudança). Embora isso pareça simples, pode ser complicado (nem todos gostamos do Android ...).
Temos que distinguir entre dois casos:
- Lida com as alterações na configuração: este é o caso de Fragmentos que usam setRetainInstance (true) ou de uma Atividade com a
android:configChanges
tag correspondente no manifesto. Esses componentes não receberão uma chamada onCreate após, por exemplo, uma rotação de tela, portanto, lembre-se de chamar
initLoader/restartLoader
outro método de retorno de chamada (por exemplo, in
onActivityCreated(Bundle)
). Para poder inicializar o (s) carregador (es), os IDs do carregador precisam ser armazenados (por exemplo, em uma lista). Como o componente é retido nas alterações de configuração, podemos simplesmente fazer um loop sobre os IDs e chamadas existentes do carregador initLoader(loaderid,
...)
.
- Não lida com as alterações de configuração: nesse caso, os Carregadores podem ser inicializados no onCreate, mas precisamos manter manualmente os IDs do carregador ou não seremos capazes de fazer as chamadas necessárias initLoader / restartLoader. Se os IDs estiverem armazenados em um ArrayList, faremos um
outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)
em onSaveInstanceState e restauramos os IDs em onCreate:
loaderIdsArray =
savedInstanceState.getIntegerArrayList(loaderIdsKey)
antes de fazermos as chamadas initLoader.
initLoader
(e todos os retornos de chamada tiverem terminado, o Loader estiver ocioso) após uma rotação, você não receberá umonLoadFinished
retorno de chamada, mas, se o usar,restartLoader
irá?