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á?