Лоадеры (loaders) в Android. Для чего нужны. Конспект.
Краткое описание. Создание. Использование.
Глобальная концепция: разработаны для работы с данными: загрузки (сохранения) данных (создание отдельного потока загрузки, загрузка, обработка ошибок, оповещение о состоянии загрузки). Нас интересует (в данный момент) одно частное применение лоадеров, а именно использование при пересоздании Activity (при изменении конфигурации) для сохранения состояния Activity
Лоадер – это компонент Android, который через класс LoaderManager связан с жизненным циклом Activity и Fragment. Это позволяет использовать их без опасения, что данные будут утрачены при закрытии приложения или результат вернется не в тот коллбэк. Разберем простейший пример (который хоть и простейший, но требует немало кода, это один из недостатков лоадеров). Создаем класс лоадера (для простоты он не будет грузить данные с сервера, а лишь имитировать загрузку):
public class StubLoader extends AsyncTaskLoader<Integer> {
public StubLoader(Context context) {
super(context);
}
@Override
protected void onStartLoading() {
super.onStartLoading();
forceLoad();
}
@Override
public Integer loadInBackground() {
// emulate long-running operation
SystemClock.sleep(2000);
return 5;
}
}
В методе loadInBackground() - мы должны загрузить данные Integer - в данном примере это контейнер для данных (int), которые загружает наш лоадер.
В отличие от AsyncTask-а лоадер не нужно запускать вручную, это делается неявным образом через класс LoaderManager. У этого класса есть два метода с одинаковой сигнатурой:
public abstract <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback);
public abstract <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback);
- int id - идентификатор лоадера для различения их между собой;
- Bundle args - класс Bundle, с помощью которого передаем аргументы для создания лоадера;
- LoaderManager.LoaderCallbacks
callback - Callback для создания лоадера и получения от него результата работы:
public interface LoaderCallbacks<D> {
public Loader<D> onCreateLoader(int id, Bundle args);
public void onLoadFinished(Loader<D> loader, D data);
public void onLoaderReset(Loader<D> loader);
}
- onCreateLoader(int id, Bundle args) - необходимо вернуть нужный лоадер в зависимости от id, используя параметры из Bundle. Вызывается автоматически, при создании лоадера;
- onLoadFinished(Loader
loader, D data) - в параметре D мы получим результат работы лоадера. Вызывается после того, как лоадер загрузит даннные. Для определения этого момента менеджер регистрирует слушатель OnLoadCompleteListener , который прослушивает события; - onLoaderReset(Loader
loader) - очищаем все данные, чвязанные с данным лоадером.
Создадим экземпляр LoaderCallback для нашего StubLoader’a:
private class StubLoaderCallbacks implements LoaderManager.LoaderCallbacks<Integer> {
@Override
public Loader<Integer> onCreateLoader(int id, Bundle args) {
if (id == R.id.stub_loader_id) {
return new StubLoader(WeatherActivity.this);
}
return null;
}
@Override
public void onLoadFinished(Loader<Integer> loader, Integer data) {
if (loader.getId() == R.id.stub_loader_id) {
Toast.makeText(WeatherActivity.this, R.string.load_finished, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onLoaderReset(Loader<Integer> loader) {
// Do nothing
}
}
И теперь запускаем лоадер:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
getSupportLoaderManager().initLoader(R.id.stub_loader_id, Bundle.EMPTY, new StubLoaderCallbacks());
}
Результат: через две секунды после запуска Activity покажется Toast. Теперь, если мы изменим конфигурацию Activity, например, перевернём устройство, то работа лоадера должна начаться заново и Toast должен показаться через 2 секунды. Но он показывается мнгновенно! В чём магия?
Лоадер запускается в методе initLoader класса LoaderManager. Лоадер создается при первом запуске Activity, но при последующих запусках он не пересоздаётся. Вместо этого LoaderManager заменяет экземпляр LoaderCallbacks на новый переданный, и если данные загрузились, они передаются в onLoadFinished.
Таким образом нам не нужно беспокоиться об обновлении данных и создании нового лоадера, но если данные необходимо перезагрузить, можно выполнить restartLoader.
Жизненный цикл лоадера
Порядок работы менеджера загрузчиков (LoaderManager) во время создания активности:
- Application.onCreate()
- Activity.onCreate()
- LoaderManager.LoaderCallbacks.onCreateLoader()
- Activity.onStart()
- Activity.onResume()
- LoaderManager.LoaderCallbacks.onLoadFinished()
При изменении конфигурации (поворот и т.п.):
- Application:config changed
- Activity.onCreate
- Activity.onStart
- [No call to the onCreateLoader, т.к. нет необходимости менять данные]
- LoaderManager.LoaderCallbacks.onLoadFinished
- [optionally if searchview has text in it]
- [есть необходимоть изменить данные]
- RestartLoader
- LoaderManager.LoaderCallbacks.onCreateLoader
- LoaderManager.LoaderCallbacks.onLoadFinished
При уничтожении активности:
- Activity.onStop()
- Activity.onDestroy()
- LoaderManager.LoaderCallbacks.onLoaderReset() //Обратите внимание, что этот метод вызывается