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á?Chamar
initLoader
quando o Loader já tiver sido criado (isso normalmente ocorre após alterações na configuração, por exemplo) informa ao LoaderManager para entregar os dados mais recentes do LoaderonLoadFinished
imediatamente. Se o Loader ainda não tiver sido criado (quando a atividade / fragmento for iniciado, por exemplo), a chamada parainitLoader
informa ao LoaderManageronCreateLoader
para criar o novo Loader.A chamada
restartLoader
destrói um carregador já existente (assim como quaisquer dados existentes associados a ele) e pede ao LoaderManager para ligaronCreateLoader
para criar o novo carregador e iniciar uma nova carga.A documentação também é bastante clara sobre isso:
initLoader
garante que um carregador seja inicializado e ativo. Se o carregador ainda não existir, um será criado e (se a atividade / fragmento estiver iniciado no momento) inicia o carregador. Caso contrário, o último carregador criado será reutilizado.restartLoader
inicia um novo ou reinicia um carregador existente neste gerenciador, registra os retornos de chamada para ele e (se a atividade / fragmento estiver iniciado no momento) começa a carregá-lo. Se um carregador com o mesmo ID tiver sido iniciado anteriormente, ele será destruído automaticamente quando o novo carregador concluir seu trabalho. O retorno de chamada será entregue antes que o carregador antigo seja destruído.fonte
initLoader()
emonCreate()
/onActivityCreated()
quando a atividade / fragmento é inicializada. Dessa maneira, quando o usuário abrir uma atividade pela primeira vez, o carregador será criado pela primeira vez ... mas em qualquer alteração subsequente na configuração em que toda a atividade / fragmento deva ser destruída, a seguinte chamada parainitLoader()
retornará a antiga emLoader
vez de criando um novo. Geralmente você usarestartLoader()
quando precisa alterar aLoader
consulta do (ou seja, deseja obter dados filtrados / classificados, etc.).Recentemente, encontrei um problema com vários gerenciadores de carregadores e alterações na orientação da tela e gostaria de dizer que, após várias tentativas e erros, o seguinte padrão funciona para mim em Atividades e Fragmentos:
(em outras palavras, defina um sinalizador para que o initLoader seja sempre executado uma vez e o restartLoader seja executado na segunda e subsequente passagem pelo onResume )
Além disso, lembre-se de atribuir IDs diferentes para cada um dos seus carregadores em uma Atividade (o que pode ser um pouco problemático para fragmentos dessa atividade, se você não tomar cuidado com a numeração)
Tentei usar apenas o initLoader .... não parecia funcionar de maneira eficaz.
Tentei o initLoader no onCreate com argumentos nulos (os documentos dizem que isso está ok) e restartLoader (com argumentos válidos) no onResume .... os documentos estão errados e o initLoader lança uma exceção nullpointer.
Tentou restartLoader única ... obras para um tempo, mas golpes no 5º ou 6º tela re-orientação.
Tentei initLoader in onResume ; novamente funciona por um tempo e depois sopra. (especificamente o erro "Chamado doRetain quando não iniciado:" ...)
Tentei o seguinte: (trecho de uma classe de capa que possui o ID do carregador passado para o construtor)
(que eu encontrei em algum lugar no Stack-Overflow)
Novamente, isso funcionou por um tempo, mas ainda gerava uma falha ocasional.
Pelo que posso descobrir durante a depuração, acho que há algo a ver com o estado da instância de salvar / restaurar que requer que initLoader (s) seja executado na parte onCreate do ciclo de vida para sobreviver a uma rotação do ciclo . ( Eu posso estar errado.)
no caso de gerentes que não podem ser iniciados até que os resultados retornem de outro gerente ou tarefa (ou seja, não podem ser inicializados no onCreate ), eu uso apenas o initLoader . (Posso não estar correto nisso, mas parece funcionar. Esses carregadores secundários não fazem parte do estado imediato da instância; portanto, o uso do initLoader pode estar correto neste caso)
Olhando para os diagramas e documentos, eu pensaria que o initLoader deveria entrar no onCreate e restartLoader no onRestart for Activities, mas isso deixa os Fragments usando algum padrão diferente e não tive tempo de investigar se isso é realmente estável. Alguém mais pode comentar se eles obtiveram sucesso com esse padrão de atividades?
fonte
initLoader
reutilizará os mesmos parâmetros se o carregador já existir. Ele retorna imediatamente se os dados antigos já estiverem carregados, mesmo se você os chamar com novos parâmetros. Idealmente, o carregador deve notificar automaticamente a atividade de novos dados. Se a tela girasse,initLoader
seria chamada novamente e os dados antigos seriam exibidos imediatamente.restartLoader
é para quando você deseja forçar uma recarga e alterar os parâmetros também. Se você fizesse uma tela de login usando carregadores, ligaria apenasrestartLoader
toda vez que o botão fosse clicado. (O botão pode ser clicado várias vezes devido a credenciais incorretas, etc.). Você só ligariainitLoader
ao restaurar o estado da instância salva da atividade caso a tela fosse girada enquanto um logon estava em andamento.fonte
Se o carregador já existir, restartLoader irá parar / cancelar / destruir o antigo, enquanto o initLoader apenas o inicializará com o retorno de chamada fornecido. Não consigo descobrir o que os antigos retornos de chamada fazem nesses casos, mas acho que eles serão abandonados.
Digitalizei através do http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/LoaderManager.java, mas não consigo descobrir qual é o valor exato A diferença é que, além disso, os métodos fazem coisas diferentes. Então, eu diria, use initLoader pela primeira vez e reinicie pelos seguintes horários, embora eu não possa dizer com certeza o que cada um deles fará exatamente.
fonte
initLoader
neste caso?O carregador de inicialização na primeira inicialização usa o método loadInBackground (); na segunda inicialização, ele será omitido. Então, na minha opinião, a melhor solução é:
//////////////////////////////////////////////////// ///////////////////////////
Gastei muito tempo para encontrar esta solução - restartLoader (...) não funcionou corretamente no meu caso. O único forceLoad () permite concluir o encadeamento de carregamento anterior sem retorno de chamada (para que todas as transações de banco de dados sejam concluídas corretamente) e inicie novamente um novo encadeamento. Sim, exige um tempo extra, mas é mais estável. Somente o último encadeamento iniciado receberá retorno de chamada. Portanto, se você quiser fazer testes com a interrupção de suas transações de banco de dados - de nada, tente reiniciar o Loader (...), caso contrário forceLoad (). A única conveniência do restartLoader (...) é fornecer novos dados iniciais, quero dizer parâmetros. E não esqueça de destruir o carregador no método onDetach () do fragmento adequado neste caso. Lembre-se também de que, algumas vezes, quando você tem uma atividade e, digamos, 2 fragmentos com o Loader, cada uma das atividades inclusivas - você alcançará apenas 2 Loader Managers, portanto o Activity compartilha seu LoaderManager com o (s) fragmento (s), que é mostrado primeiro na tela durante o carregamento. Tente LoaderManager.enableDebugLogging (true); para ver os detalhes em cada caso específico.
fonte
getLoader(0)
em atry { ... } catch (Exception e) { ... }
.