Лоадеры (loaders) в Android. Для чего нужны. Конспект.

Теги: Java, Android, Loader

Краткое описание. Создание. Использование.

Глобальная концепция: разработаны для работы с данными: загрузки (сохранения) данных (создание отдельного потока загрузки, загрузка, обработка ошибок, оповещение о состоянии загрузки). Нас интересует (в данный момент) одно частное применение лоадеров, а именно использование при пересоздании 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() //Обратите внимание, что этот метод вызывается
02 07 2017

Теги заметки: