Para responder a esta pregunta, debe profundizar en el LoaderManager
código. Si bien la documentación de LoaderManager en sí misma no es lo suficientemente clara (o no habría esta pregunta), la documentación de LoaderManagerImpl, una subclase del LoaderManager abstracto, es mucho más esclarecedora.
initLoader
Llame para inicializar una identificación particular con un cargador. Si este ID ya tiene un cargador asociado, no se modifica y las devoluciones de llamada anteriores se reemplazan por las recién proporcionadas. Si actualmente no hay un cargador para la ID, se crea y se inicia uno nuevo.
Esta función generalmente se debe usar cuando un componente se está inicializando, para garantizar que se cree un cargador en el que se basa. Esto le permite reutilizar los datos de un cargador existente si ya hay uno, de modo que, por ejemplo, cuando se vuelve a crear una actividad después de un cambio de configuración, no es necesario volver a crear sus cargadores.
restartLoader
Llame para volver a crear el cargador asociado con una ID en particular. Si actualmente hay un cargador asociado con esta ID, se cancelará / detendrá / destruirá según corresponda. Se creará un nuevo cargador con los argumentos dados y se le entregarán sus datos una vez que estén disponibles.
[...] Después de llamar a esta función, cualquier Cargador anterior asociado con esta ID se considerará inválido y no recibirá más actualizaciones de datos de ellos.
Básicamente hay dos casos:
- El cargador con la identificación no existe: ambos métodos crearán un nuevo cargador, por lo que no hay diferencia allí
- El cargador con el ID ya existe:
initLoader
solo reemplazará las devoluciones de llamada pasadas como parámetro, pero no cancelará ni detendrá el cargador. Para a CursorLoader
eso significa que el cursor permanece abierto y activo (si ese fue el caso antes de la initLoader
llamada). `restartLoader, por otro lado, cancelará, detendrá y destruirá el cargador (y cerrará la fuente de datos subyacente como un cursor) y creará un nuevo cargador (que también crearía un nuevo cursor y volvería a ejecutar la consulta si el cargador es un CursorLoader).
Aquí está el código simplificado para ambos 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 en caso de que el cargador no exista (info == nulo) ambos métodos crearán un nuevo cargador (info = createAndInstallLoader (...)). En caso de que el cargador ya exista, initLoader
solo reemplaza las devoluciones de llamada (info.mCallbacks = ...) mientras restartLoader
inactiva el cargador anterior (se destruirá cuando el nuevo cargador complete su trabajo) y luego cree uno nuevo.
Por lo tanto, ahora está claro cuándo usar initLoader
y cuándo usar restartLoader
y por qué tiene sentido tener los dos métodos.
initLoader
se utiliza para garantizar que haya un cargador inicializado. Si no existe ninguno, se crea uno nuevo, si ya existe, se reutiliza. Siempre usamos este método A MENOS QUE necesitemos un nuevo cargador porque la consulta a ejecutar ha cambiado (no los datos subyacentes sino la consulta real como en la instrucción SQL para un CursorLoader), en cuyo caso llamaremos restartLoader
.
¡El ciclo de vida de la Actividad / Fragmento no tiene nada que ver con la decisión de usar uno u otro método (y no hay necesidad de hacer un seguimiento de las llamadas usando una bandera de un solo disparo como lo sugirió Simon)! Esta decisión se toma únicamente en función de la "necesidad" de un nuevo cargador. Si queremos ejecutar la misma consulta que utilizamos initLoader
, si queremos ejecutar una consulta diferente que utilizamos restartLoader
.
Siempre podríamos usar, restartLoader
pero eso sería ineficiente. Después de una rotación de pantalla o si el usuario se aleja de la aplicación y regresa más tarde a la misma Actividad, generalmente queremos mostrar el mismo resultado de la consulta y, por lo tanto, restartLoader
volvería a crear innecesariamente el cargador y descartaría el resultado de la consulta subyacente (potencialmente costosa).
Es muy importante comprender la diferencia entre los datos que se cargan y la "consulta" para cargar esos datos. Supongamos que usamos un CursorLoader que consulta una tabla para pedidos. Si se agrega un nuevo pedido a esa tabla, el CursorLoader usa onContentChanged () para informar a la IU que actualice y muestre el nuevo pedido (no es necesario usarlo restartLoader
en este caso). Si queremos mostrar solo las órdenes abiertas, necesitamos una nueva consulta y la restartLoader
usaríamos para devolver un nuevo CursorLoader que refleje la nueva consulta.
¿Hay alguna relación entre los dos métodos?
Comparten el código para crear un nuevo cargador, pero hacen cosas diferentes cuando ya existe un cargador.
¿ restartLoader
Llamar siempre llama initLoader
?
No, nunca lo hace.
¿Puedo llamar restartLoader
sin tener que llamar initLoader
?
Si.
¿Es seguro llamar initLoader
dos veces para actualizar los datos?
Es seguro llamar initLoader
dos veces, pero no se actualizarán los datos.
¿Cuándo debo usar uno de los dos y por qué ?
Eso debería (con suerte) quedar claro después de mis explicaciones anteriores.
Cambios de configuración
Un LoaderManager conserva su estado a través de los cambios de configuración (incluidos los cambios de orientación), por lo que pensaría que no nos queda nada por hacer. Piensa otra vez...
En primer lugar, un LoaderManager no retiene las devoluciones de llamada, por lo que si no hace nada, no recibirá llamadas a sus métodos de devolución de llamada, onLoadFinished()
y similares, y eso probablemente interrumpirá su aplicación.
Por lo tanto, DEBEMOS llamar al menos initLoader
para restaurar los métodos de devolución de llamada (a restartLoader
, por supuesto, también es posible). La documentación dice:
Si en el punto de la llamada, la persona que llama está en su estado iniciado, y el cargador solicitado ya existe y ha generado sus datos, onLoadFinished(Loader, D)
se llamará inmediatamente a la devolución de llamada (dentro de esta función) [...].
Eso significa que si llamamos initLoader
después de un cambio de orientación, recibiremos una onLoadFinished
llamada de inmediato porque los datos ya están cargados (suponiendo que ese fuera el caso antes del cambio). Si bien eso parece sencillo, puede ser complicado (no todos amamos Android ...).
Tenemos que distinguir entre dos casos:
- Maneja cambios en la configuración: este es el caso de los Fragmentos que usan setRetainInstance (true) o para una Actividad con la
android:configChanges
etiqueta correspondiente en el manifiesto. Estos componentes no recibirán una llamada onCreate después de, por ejemplo, una rotación de pantalla, así que tenga en cuenta llamar
initLoader/restartLoader
a otro método de devolución de llamada (por ejemplo, en
onActivityCreated(Bundle)
). Para poder inicializar el (los) Cargador (es), los identificadores del cargador deben almacenarse (por ejemplo, en una Lista). Debido a que el componente se retiene a través de los cambios de configuración, simplemente podemos recorrer los identificadores del cargador existentes y llamar initLoader(loaderid,
...)
.
- No maneja los cambios de configuración por sí mismo: en este caso, los cargadores se pueden inicializar en onCreate pero necesitamos retener manualmente los identificadores del cargador o no podremos realizar las llamadas necesarias initLoader / restartLoader. Si los identificadores se almacenan en una ArrayList, haríamos un
outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)
en onSaveInstanceState y restauraríamos los identificadores en onCreate:
loaderIdsArray =
savedInstanceState.getIntegerArrayList(loaderIdsKey)
antes de hacer la (s) llamada (s) initLoader.
initLoader
(y todas las devoluciones de llamada han finalizado, Loader está inactivo) después de una rotación, no recibirá unaonLoadFinished
devolución de llamada, pero si lo usa,restartLoader
¿lo hará?Llamar
initLoader
cuando el Loader ya se ha creado (por lo general, esto ocurre después de los cambios de configuración, por ejemplo) le dice al LoaderManager que entregue los datos más recientes del Loader deonLoadFinished
inmediato. Si el cargador no se ha creado (por ejemplo, cuando se inicia la actividad / fragmento por primera vez), la llamada ainitLoader
le indica al LoaderManager que llameonCreateLoader
para crear el nuevo cargador.Las llamadas
restartLoader
destruyen un cargador ya existente (así como cualquier dato existente asociado con él) y le dice al LoaderManager que llameonCreateLoader
para crear el nuevo cargador e iniciar una nueva carga.La documentación también es bastante clara sobre esto:
initLoader
asegura que un cargador esté inicializado y activo. Si el cargador aún no existe, se crea uno y (si la actividad / fragmento se ha iniciado actualmente) inicia el cargador. De lo contrario, se reutiliza el último cargador creado.restartLoader
inicia un nuevo o reinicia un cargador existente en este administrador, registra las devoluciones de llamada y (si la actividad / fragmento está actualmente iniciado) comienza a cargarlo. Si se ha iniciado previamente un cargador con la misma identificación, se destruirá automáticamente cuando el nuevo cargador complete su trabajo. La devolución de llamada se entregará antes de que se destruya el cargador antiguo.fuente
initLoader()
enonCreate()
/onActivityCreated()
cuando la actividad / fragmento se inicia por primera vez. De esta manera, cuando el usuario abre una actividad por primera vez, se creará el cargador por primera vez ... pero en cualquier cambio de configuración posterior en el que deba destruirse toda la actividad / fragmento, la siguiente llamadainitLoader()
simplemente devolverá la antigua enLoader
lugar de creando uno nuevo. Usualmente lo usarestartLoader()
cuando necesita cambiar laLoader
consulta de (es decir, desea obtener datos filtrados / ordenados, etc.).Recientemente me topé con un problema con varios administradores de cargadores y cambios en la orientación de la pantalla y me gustaría decir que después de muchas pruebas y errores, el siguiente patrón me funciona tanto en Actividades como en Fragmentos:
(en otras palabras, establecer algún indicador para que initLoader está siempre ejecuta una vez y que restartLoader se ejecuta en el segundo y subsiguientes pases a través onResume )
Además, recuerde asignar diferentes identificadores para cada uno de sus cargadores dentro de una Actividad (que puede ser un problema con los fragmentos dentro de esa actividad si no tiene cuidado con su numeración)
Intenté usar initLoader solo ... no parecía funcionar de manera efectiva.
Intentado initLoader en onCreate con argumentos nulos (docs dicen que esto está bien) y restartLoader (con argumentos válidos) en onResume .... documentos están equivocados y initLoader una excepción NullPointer.
Intentado restartLoader única ... funciona por un tiempo, pero el 5 de golpes o de la pantalla 6 de reorientación.
Probamos initLoader en onResume ; nuevamente funciona por un tiempo y luego sopla. (específicamente el "Llamado doRetain cuando no se inició:" ... error)
Intenté lo siguiente: (extracto de una clase de cubierta que tiene el id del cargador pasado al constructor)
(que encontré en algún lugar de Stack-Overflow)
Una vez más, esto funcionó por un tiempo, pero aún arrojó la falla ocasional.
Por lo que puedo descubrir durante la depuración, creo que hay algo que ver con el estado de instancia de guardar / restaurar que requiere que initLoader (/ s) se ejecuten en la parte onCreate del ciclo de vida si van a sobrevivir a un giro del ciclo . ( Puedo estar equivocado.)
En el caso de los administradores que no pueden iniciarse hasta que los resultados vuelvan de otro administrador o tarea (es decir, no pueden inicializarse en onCreate ), solo uso initLoader . (Puede que no sea correcto en esto, pero parece funcionar. Estos cargadores secundarios no son parte del estado de instancia inmediato, por lo que usar initLoader puede ser correcto en este caso)
Mirando los diagramas y documentos, habría pensado que initLoader debería ir en onCreate & restartLoader en onRestart for Activities pero eso deja a Fragments usando un patrón diferente y no he tenido tiempo de investigar si esto es realmente estable. ¿Alguien más puede comentar si tienen éxito con este patrón de actividades?
fuente
initLoader
reutilizará los mismos parámetros si el cargador ya existe. Regresa inmediatamente si los datos antiguos ya están cargados, incluso si lo llama con nuevos parámetros. Lo ideal es que el cargador notifique automáticamente la actividad de nuevos datos. Si la pantalla girara, seinitLoader
volvería a llamar y los datos antiguos se mostrarían de inmediato.restartLoader
es para cuando quieres forzar una recarga y cambiar los parámetros también. Si tuviera que hacer una pantalla de inicio de sesión con cargadores, llamaría solorestartLoader
cada vez que se haga clic en el botón. (Se puede hacer clic en el botón varias veces debido a credenciales incorrectas, etc.). Solo llamaríainitLoader
al restaurar el estado de instancia guardada de la actividad en caso de que la pantalla se girara mientras se iniciaba una sesión.fuente
Si el cargador ya existe, restartLoader detendrá / cancelará / destruirá el antiguo, mientras que initLoader solo lo inicializará con la devolución de llamada dada. No puedo averiguar qué hacen las antiguas devoluciones de llamada en estos casos, pero supongo que simplemente serán abandonadas.
Escaneé a través de http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/LoaderManager.java pero no puedo averiguar cuál es exactamente La diferencia es que, aparte de eso, los métodos hacen cosas diferentes. Entonces diría que use initLoader la primera vez y reinicie las siguientes veces, aunque no puedo decir con certeza qué hará exactamente cada uno de ellos.
fuente
initLoader
en este caso?El cargador de inicio en el primer inicio usa el método loadInBackground (), en el segundo inicio se omitirá. Entonces, mi opinión, la mejor solución es:
//////////////////////////////////////////////////// //////////////////////////
Pasé mucho tiempo para encontrar esta solución: restartLoader (...) no funcionó correctamente en mi caso. El único forceLoad () permite finalizar el subproceso de carga anterior sin devolución de llamada (por lo que tendrá todas las transacciones de db finalizadas correctamente) y comienza de nuevo nuevo subproceso. Sí, exige algo de tiempo extra, pero es más estable. Solo el último subproceso iniciado tendrá devolución de llamada. Por lo tanto, si desea realizar pruebas con la interrupción de sus transacciones de db, de nada, intente reiniciarLoader (...), de lo contrario forceLoad (). La única conveniencia de restartLoader (...) es entregar nuevos datos iniciales, es decir, parámetros. Y no olvide destruir el cargador en el método onDetach () del Fragmento adecuado en este caso. También tenga en cuenta que algunas veces, cuando tiene una actividad y, déjenles decir, 2 fragmentos con el cargador cada actividad inclusiva: llegarás a solo 2 gestores del cargador, por lo que Activity comparte su LoaderManager con los fragmentos, que se muestran primero en la pantalla durante la carga. Pruebe LoaderManager.enableDebugLogging (true); para ver los detalles en cada caso determinado.
fuente
getLoader(0)
atry { ... } catch (Exception e) { ... }
.