Щоб відповісти на це запитання, вам потрібно зануритися в LoaderManagerкод. Хоча сама документація для LoaderManager недостатньо чітка (або не виникало б цього питання), документація для LoaderManagerImpl, підкласу абстрактного LoaderManager, є набагато просвітнішою.
initLoader
Зателефонуйте ініціалізувати певний ідентифікатор за допомогою навантажувача. Якщо для цього ідентифікатора вже є асоційований навантажувач, він залишається незмінним і будь-які попередні зворотні дзвінки замінюються на щойно надані. Якщо наразі немає навантажувача для ідентифікатора, створюється і запускається новий.
Ця функція, як правило, повинна використовуватися при ініціалізації компонента, щоб забезпечити створення завантажувача, на який він покладається. Це дозволяє йому повторно використовувати дані наявних навантажувачів, якщо такі вже є, так що, наприклад, коли активність заново створюється після зміни конфігурації, їй не потрібно повторно створювати завантажувачі.
restartLoader
Заклик повторно створити навантажувач, пов’язаний з певним ідентифікатором. Якщо в даний час навантажувач пов'язаний з цим ідентифікатором, він буде скасований / зупинений / знищений, як це доречно. Буде створений новий навантажувач із заданими аргументами, і його дані будуть доставлені вам, коли вони стануть доступними.
[...] Після виклику цієї функції будь-які попередні навантажувачі, пов’язані з цим ідентифікатором, будуть вважатися недійсними, і ви більше не отримуватимете від них оновлення даних.
В основному є два випадки:
- Навантажувач з ідентифікатором не існує: обидва способи створять новий завантажувач, тому різниці там немає
- Навантажувач з ідентифікатором вже існує:
initLoaderзамінить лише зворотні дзвінки, передані як параметр, але не скасовує та не зупиняє завантажувач. Для CursorLoaderцього означає, що курсор залишається відкритим і активним (якщо це було так до initLoaderвиклику). `restartLoader, з іншого боку, скасує, зупинить і знищить завантажувач (і закриє базове джерело даних, як курсор) і створить новий завантажувач (який також створить новий курсор і повторно запустить запит, якщо завантажувач буде CursorLoader).
Ось спрощений код для обох методів:
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);
Як ми бачимо у випадку, якщо завантажувача не існує (info == null), обидва способи створять новий завантажувач (info = createAndInstallLoader (...)). Якщо завантажувач вже існує, initLoaderвін замінює лише зворотні дзвінки (info.mCallbacks = ...), тоді як restartLoaderінактивує старий завантажувач (він буде знищений, коли новий завантажувач завершить свою роботу), а потім створить новий.
Таким чином, сказано, що зараз зрозуміло, коли використовувати, initLoaderколи використовувати restartLoaderта чому має сенс використовувати два методи.
initLoaderвикористовується для ініціалізації завантажувача. Якщо немає, створюється новий, якщо він уже існує, він буде повторно використаний. Ми завжди використовуємо цей метод, БІЛЬШЕ нам потрібен новий завантажувач, оскільки запит для запуску змінився (не основні дані, а фактичний запит, як у SQL-операторі для CursorLoader), і в цьому випадку ми зателефонуємо restartLoader.
Життєвий цикл " Діяльність / фрагмент " не має нічого спільного з рішенням про використання того чи іншого методу (і немає необхідності відслідковувати дзвінки, використовуючи прапор одного пострілу, як запропонував Саймон)! Це рішення приймається виключно виходячи з "потреби" нового навантажувача. Якщо ми хочемо запустити той самий запит, який ми використовуємо initLoader, якщо ми хочемо запустити інший запит, який ми використовуємо restartLoader.
Ми завжди могли використовувати, restartLoaderале це було б неефективно. Після обертання екрана або якщо користувач відходить від програми та повертається пізніше до тієї ж діяльності, ми зазвичай хочемо показати той самий результат запиту, і тому ми restartLoaderзайво створимо завантажувач та відкинемо базовий (потенційно дорогий) результат запиту.
Дуже важливо зрозуміти різницю між завантаженими даними та "запитом" для завантаження цих даних. Припустимо, ми використовуємо CursorLoader, який запитує таблицю для замовлень. Якщо до цієї таблиці додано нове замовлення, CursorLoader використовує onContentChanged (), щоб повідомити інтерфейс користувача про оновлення та показати нове замовлення (не потрібно використовувати restartLoaderв цьому випадку). Якщо ми хочемо відображати лише відкриті замовлення, нам потрібен новий запит, і ми будемо використовувати restartLoaderдля повернення нового CursorLoader, що відображає новий запит.
Чи існує якесь співвідношення між двома методами?
Вони діляться кодом, щоб створити новий навантажувач, але вони роблять різні речі, коли завантажувач вже існує.
Чи дзвонить restartLoaderзавжди initLoader?
Ні, це ніколи не відбувається.
Чи можу я зателефонувати restartLoaderбез дзвінка initLoader?
Так.
Чи безпечно дзвонити initLoaderдвічі, щоб оновити дані?
Безпечно дзвонити initLoaderдвічі, але дані не будуть оновлені.
Коли я повинен використовувати одне з двох і чому ?
Це повинно бути (сподіваюся) зрозумілим після моїх пояснень вище.
Зміни конфігурації
LoaderManager зберігає свій стан через зміни конфігурації (включаючи зміни орієнтації), так що ви можете подумати, що нам нічого не залишається. Подумати ще раз...
Перш за все, LoaderManager не зберігає зворотні дзвінки, тому, якщо ви нічого не зробите, ви не отримуватимете дзвінки на ваші способи зворотного дзвінка, як onLoadFinished()тощо, і це, ймовірно, зламає вашу програму.
Тому ми МАЄМО закликати принаймні initLoaderдля відновлення методів зворотного виклику ( restartLoaderце, звичайно, теж можливо). У документації зазначено:
Якщо в точці виклику абонент перебуває у запущеному стані, а запитуваний завантажувач вже існує та генерував свої дані, тоді зворотний виклик onLoadFinished(Loader, D)буде викликаний негайно (всередині цієї функції) [...].
Це означає, що якщо ми зателефонуємо initLoaderпісля зміни орієнтації, ми отримаємо onLoadFinishedдзвінок одразу, оскільки дані вже завантажені (припускаючи, що це було до зміни). Хоча це звучить прямо вперед, це може бути складним (чи не всі ми любимо Android ...).
Ми маємо розрізняти два випадки:
- Конфігурація керує самою зміною: це стосується фрагментів, які використовують setRetainInstance (true), або для діяльності з
android:configChangesтегом відповідно до маніфесту. Ці компоненти не отримуватимуть дзвінок onCreate після, наприклад, обертання екрана, тому майте на увазі, щоб викликати
initLoader/restartLoaderінший метод зворотного виклику (наприклад, у
onActivityCreated(Bundle)). Щоб мати можливість ініціалізувати навантажувач (и), ідентифікатори завантажувача потрібно зберігати (наприклад, у списку). Оскільки компонент зберігається через зміни конфігурації, ми можемо просто перевести цикл на існуючі ідентифікатори завантажувача та викликати initLoader(loaderid,
...).
- Не обробляє самих змін конфігурації: у цьому випадку завантажувачі можуть бути ініціалізовані в onCreate, але нам потрібно зберегти ідентифікатори завантажувача вручну або ми не зможемо здійснити необхідні дзвінки initLoader / restartLoader. Якщо ідентифікатори зберігаються в ArrayList, ми зробимо
outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)в onSaveInstanceState і відновимо ідентифікатори в onCreate:
loaderIdsArray =
savedInstanceState.getIntegerArrayList(loaderIdsKey)перед тим, як здійснити виклик initLoader.
initLoader(і всі зворотні дзвінки завершені, навантажувач не працює) після обертання ви не отримаєтеonLoadFinishedзворотний дзвінок, але якщо ви будете використовувати його,restartLoaderви будете?