java - initloader - loadermanager loadercallbacks
Diferencia entre `initLoader` y` restartLoader` en `LoaderManager` (6)
Estoy completamente perdido con respecto a las diferencias entre las initLoader
y restartLoader
del LoaderManager
:
- Ambos tienen la misma firma.
-
restartLoader
también crea un cargador, si no existe ("Inicia un nuevo o reinicia un cargador existente en este administrador").
¿Hay alguna relación entre los dos métodos? ¿ restartLoader
a restartLoader
siempre llama a initLoader
? ¿Puedo llamar a restartLoader
sin tener que llamar a initLoader
? ¿Es initLoader
llamar a initLoader
dos veces para actualizar los datos? Cuándo debería usar uno de los dos y (¡importante!) ¿Por qué?
Init loader en el primer inicio utiliza el método loadInBackground (), en el segundo inicio se omite. Entonces, mi opinión, la mejor solución es:
Loader<?> loa;
try {
loa = getLoaderManager().getLoader(0);
} catch (Exception e) {
loa = null;
}
if (loa == null) {
getLoaderManager().initLoader(0, null, this);
} else {
loa.forceLoad();
}
////////////////////////////////////////////////// /////////////////////////
protected SimpleCursorAdapter mAdapter;
private abstract class SimpleCursorAdapterLoader
extends AsyncTaskLoader <Cursor> {
public SimpleCursorAdapterLoader(Context context) {
super(context);
}
@Override
protected void onStartLoading() {
if (takeContentChanged() || mAdapter.isEmpty()) {
forceLoad();
}
}
@Override
protected void onStopLoading() {
cancelLoad();
}
@Override
protected void onReset() {
super.onReset();
onStopLoading();
}
}
Pasé mucho tiempo buscando esta solución: restartLoader (...) no funcionó correctamente en mi caso. El único forceLoad () permite terminar el hilo de carga anterior sin devolución de llamada (por lo que tendrá todas las transacciones de DB finalizadas correctamente) y comienza de nuevo el nuevo hilo. Sí, exige algo de tiempo extra, pero es más estable. Solo el último hilo iniciado tendrá devolución de llamada. Por lo tanto, si desea realizar pruebas con la interrupción de sus transacciones de db, su bienvenida, intente restartLoader (...), de lo contrario forceLoad (). La única conveniencia de restartLoader (...) es entregar nuevos datos iniciales, me refiero a los parámetros. Y por favor no olvides 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, por decirlo así, 2 fragmentos con la actividad de Cargador de cada uno incluido, solo alcanzará 2 Administradores de Cargador, por lo que Activity comparte su LoaderManager con Fragmento (es). se muestra en la pantalla primero durante la carga. Pruebe LoaderManager.enableDebugLogging (true); para ver los detalles en cada caso en particular
Llamar a initLoader
cuando el Loader ya se ha creado (esto suele ocurrir después de los cambios de configuración, por ejemplo) le dice al LoaderManager que entregue los datos más recientes del Loader a onLoadFinished
inmediatamente. Si el Loader no se ha creado aún (cuando la actividad / fragmento se inicia por primera vez, por ejemplo), la llamada a initLoader
le dice al LoaderManager que llame onCreateLoader
para crear el nuevo Loader.
Llamar a restartLoader
destruye un Loader ya existente (así como también cualquier información existente asociada con él) y le dice al LoaderManager que llame onCreateLoader
para crear el nuevo Loader e iniciar una nueva carga.
La documentación es bastante clara sobre esto también:
initLoader
asegura que un cargador esté inicializado y activo. Si el cargador todavía no existe, se crea uno y (si la actividad / fragmento se inicia actualmente) inicia el cargador. De lo contrario, se reutiliza el último cargador creado.restartLoader
inicia una nueva 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 un cargador con el mismo ID se ha iniciado previamente, se destruirá automáticamente cuando el nuevo cargador finalice su trabajo. La devolución de llamada se entregará antes de que se destruya el cargador anterior.
Para responder a esta pregunta, debe profundizar en el código de LoaderManager. Si bien la documentación de LoaderManager no es lo suficientemente clara (o no existiría esta pregunta), la documentación de LoaderManagerImpl, una subclase del LoaderManager abstracto, es mucho más esclarecedora.
initLoader
Llamar para inicializar una identificación particular con un cargador. Si esta ID ya tiene un cargador asociado, no se modifica y las devoluciones de llamada anteriores se reemplazan por las nuevas. Si actualmente no hay un cargador para la identificación, se crea e inicia uno nuevo.
Esta función generalmente se debe usar cuando un componente se está inicializando, para garantizar que se cree un Loader 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 una actividad se vuelve a crear 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 identificación particular. Si actualmente hay un cargador asociado con este ID, será cancelado / detenido / destruido según corresponda. Se creará un nuevo cargador con los argumentos dados y se le entregarán los datos una vez que estén disponibles.
[...] Después de llamar a esta función, cualquier cargador anterior asociado con este ID se considerará no válido y no recibirá más actualizaciones de datos de ellos.
Básicamente hay dos casos:
- El cargador con el ID no existe: ambos métodos crearán un nuevo cargador para que no haya diferencia
- El cargador con el ID ya existe: initLoader solo reemplazará las devoluciones pasadas como parámetro, pero no cancelará ni detendrá el cargador. Para un CursorLoader eso significa que el cursor permanece abierto y activo (si ese fuera el caso antes de la llamada a initLoader). restartLoader por otro lado cancelará, detendrá y destruirá el cargador (y cerrará el origen 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 == null), 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 que restartLoader desactiva el cargador anterior (se destruirá cuando el nuevo cargador finalice su trabajo) y luego crea 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 uno ya existe se reutiliza. Siempre utilizamos este método A MENOS que necesitemos un nuevo cargador porque la consulta para ejecutar ha cambiado (no los datos subyacentes, sino la consulta real, como en la declaración SQL para un CursorLoader), en cuyo caso llamaremos a restartLoader.
El ciclo de vida 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 única como sugirió Simon)! Esta decisión se basa únicamente en la "necesidad" de un nuevo cargador. Si queremos ejecutar la misma consulta, usamos initLoader, si queremos ejecutar una consulta diferente usamos restartLoader. Siempre podríamos usar restartLoader, pero eso sería ineficiente. Después de una rotación de pantalla o si el usuario navega fuera de la aplicación y regresa luego a la misma actividad, normalmente queremos mostrar el mismo resultado de consulta y así el restartLoader recrearía innecesariamente el cargador y descartaría el resultado de la consulta subyacente (potencialmente costoso) .
Es muy importante comprender la diferencia entre los datos que se cargan y la "consulta" para cargar esos datos. Supongamos que usamos un CursorLoader consultando una tabla para pedidos. Si se agrega un nuevo pedido a esa tabla, el CursorLoader usa onContentChanged () para informar a la interfaz de usuario que actualice y muestre el nuevo pedido (no es necesario usar el archivo restartLoader en este caso). Si queremos mostrar solo las órdenes abiertas, necesitamos una nueva consulta y usaríamos restartLoader 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.
¿Llamar a restartLoader siempre llama a initLoader?
No, nunca lo hace.
¿Puedo llamar a restartLoader sin tener que llamar a initLoader?
Sí.
¿Es seguro llamar a initLoader dos veces para actualizar los datos?
Es seguro llamar a initLoader dos veces, pero no se actualizarán los datos.
Cuándo debería usar uno de los dos y (¡importante!) ¿Por qué?
Eso debería (con suerte) ser claro después de mis explicaciones anteriores.
Cambios de configuración
Un LoaderManager conserva su estado en todos los cambios de configuración (incluidos los cambios de orientación), por lo que podría pensar 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 como onLoadFinished () y similares, y eso probablemente romperá su aplicación. Por lo tanto, TENEMOS que llamar al menos a initLoader para restaurar los métodos de devolución de llamada (también es posible un restartLoader). La documentation dice:
Si en el momento de la llamada la persona que llama está en su estado de inicio, y el cargador solicitado ya existe y ha generado sus datos, se llamará de inmediato la devolución de llamada en carga final (cargador, D) (dentro de esta función) [...].
Eso significa que si llamamos a initLoader después de un cambio de orientación, recibiremos una llamada onLoadFinished de inmediato porque los datos ya están cargados (suponiendo que ese fuera el caso antes del cambio). Si bien eso suena directo, puede ser complicado (no nos encanta a Android ...).
Tenemos que distinguir entre dos casos:
- La configuración de controles se cambia a sí misma: este es el caso de los fragmentos que usan setRetainInstance (verdadero) o para una actividad con la etiqueta android: configChanges correspondiente en el manifiesto. Estos componentes no recibirán una llamada onCreate después de, por ejemplo, una rotación de pantalla, así que recuerde llamar a initLoader / restartLoader en otro método de devolución de llamada (por ejemplo, en onActivityCreated (Bundle)). Para poder inicializar el cargador (es), los identificadores del cargador deben almacenarse (por ejemplo, en una lista). Debido a que el componente se conserva a través de los cambios de configuración, podemos simplemente pasar por encima de los identificadores de cargador existentes y llamar a initLoader (loaderid, ...).
- No maneja los cambios de configuración por sí mismo: en este caso, los cargadores se pueden inicializar en onCreate, pero tenemos que retener manualmente los id. Del cargador o no podremos realizar las llamadas necesarias de initLoader / restartLoader. Si los ID se almacenan en una ArrayList, haríamos un
outState.putIntegerArrayList (loaderIdsKey, loaderIdsArray) en onSaveInstanceState y restaurar los ids en onCreate: loaderIdsArray = savedInstanceState.getIntegerArrayList (loaderIdsKey) antes de realizar las llamadas a initLoader.
Recientemente me tocó un problema con múltiples gestores de cargadores y cambios en la orientación de la pantalla y me gustaría decir que después de un montón de prueba y error, el siguiente patrón me funciona tanto en actividades como en fragmentos:
onCreate: call initLoader(s)
set a one-shot flag
onResume: call restartLoader (or later, as applicable) if the one-shot is not set.
unset the one-shot in either case.
(En otras palabras, establezca un marcador para que initLoader siempre se ejecute una vez y que restartLoader se ejecute en el segundo y pase subsiguiente en Reanudar )
Además, recuerde asignar diferentes identificadores para cada uno de sus cargadores dentro de una Actividad (lo cual puede ser un problema con los fragmentos dentro de esa actividad si no tiene cuidado con su numeración)
Intenté usar initLoader solamente ... no parecía funcionar con eficacia.
Intentó initLoader en onCreate con args nulos (docs dicen que esto está bien) y restartLoader (con args válidos) en onResume .... los documentos son incorrectos y initLoader arroja una excepción nullpointer.
Trató de restartLoader solamente ... funciona por un tiempo pero funciona en la 5ª o 6ª reorientación de la pantalla.
Intentó initLoader en onResume ; nuevamente funciona por un tiempo y luego sopla. (específicamente el "Llamado doRetain when not started:" ... error)
Intenté lo siguiente: (extracto de una clase de portada que tiene el ID del cargador pasado al constructor)
/**
* start or restart the loader (why bother with 2 separate functions ?) (now I know why)
*
* @param manager
* @param args
* @deprecated use {@link #restart(LoaderManager, Bundle)} in onResume (as appropriate) and {@link #initialise(LoaderManager, Bundle)} in onCreate
*/
@Deprecated
public void start(LoaderManager manager, Bundle args) {
if (manager.getLoader(this.id) == null) {
manager.initLoader(this.id, args, this);
} else {
manager.restartLoader(this.id, args, this);
}
}
(que encontré en algún lugar de Stack-Overflow)
De nuevo, esto funcionó durante un tiempo pero aún arrojó el error ocasional.
De lo que puedo deducir durante la depuración, creo que hay algo que ver con el estado de la instancia de guardar / restaurar que requiere que los inicLoader (/ s) se ejecuten en la parte onCreate del ciclo de vida si van a sobrevivir un giro del ciclo . ( Puedo estar equivocado.)
en el caso de los gerentes que no se pueden iniciar hasta que los resultados regresen de otro administrador o tarea (es decir, no se pueden inicializar en onCreate ), solo uso initLoader . (Puede que no sea correcto en esto, pero parece que funciona. Estos cargadores secundarios no son parte del estado de instancia inmediato, por lo que usar initLoader puede ser correcto en este caso)
Al mirar los diagramas y documentos, pensé que initLoader debería entrar en Crear y reiniciar el cargador 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?
Si el cargador ya existe, restartLoader detendrá / cancelará / destruirá el anterior, mientras que initLoader lo inicializará con la devolución de llamada dada. No puedo averiguar qué hacen los callbacks antiguos en estos casos, pero supongo que serán abandonados.
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. Así que diría, use initLoader la primera vez y reinicie para los siguientes tiempos, aunque no puedo decir con certeza qué hará exactamente cada uno de ellos.
initLoader
reutilizará los mismos parámetros si el cargador ya existe. Vuelve inmediatamente si los datos viejos ya están cargados, incluso si lo llamas con nuevos parámetros. El cargador idealmente debería notificar automáticamente la actividad de los datos nuevos. Si la pantalla initLoader
, se volvería a llamar a initLoader
y los datos anteriores se mostrarían inmediatamente.
restartLoader
es para cuando quieres forzar una recarga y cambiar los parámetros también. Si restartLoader
que crear una pantalla de inicio de sesión con cargadores, solo llamaría a restartLoader
cada vez que se restartLoader
clic en el botón. (Es posible que se haga clic en el botón varias veces debido a credenciales incorrectas, etc.). Solo se podría llamar a initLoader
al restaurar el estado de la instancia guardada de la actividad en el caso de que se initLoader
la pantalla mientras se initLoader
el inicio de sesión.