Освой программирование играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Метки: LoaderManager, Loader, LoaderManager.LoaderCallbacks, AsyncTaskLoader, CursorLoader
В старых версиях Android при работе с курсором использовались методы, которые теперь считаются устаревшими.
Первый метод работал в жизненном цикле активности. Курсор автоматически деактивировался или закрывался, когда активность останавливалась или закрывалась. При рестарте активности, курсор также активировался, создавая повторный запрос к данным.
Второй метод осуществлял сам запрос. Проблема с методами заключалась в том, что они работали в одном потоке с интерфейсом приложения и выполняли лишние запросы при изменении состояния активности, тормозя всё приложение, если база данных была слишком большая.
Например, загрузим данные в список старым способом, используя контент-провайдер. Результат обрабатывается адаптером SimpleCursorAdapter и выводится на экран.
public class SampleListActivity extends ListActivity {
private static final String[] PROJECTION = new String[] {"_id", "text_column"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Cursor cursor = managedQuery(
CONTENT_URI, // The Uri constant in your ContentProvider class
PROJECTION, // The columns to return for each data row
null, // No where clause
null, // No where clause
null); // No sort order
String[] dataColumns = { "text_column" };
int[] viewIDs = { R.id.text_view };
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
this, // The Activity context
R.layout.list_item, // Points to the XML for a list item
cursor, // Cursor that contains the data to display
dataColumns, // Bind the data in column "text_column"...
viewIDs); // ...to the TextView with id "R.id.text_view"
setListAdapter(adapter);
}
}
От этого кода следует отказаться по описанным выше причинам, чтобы не иметь проблем с производительностью.
Начиная с Android 3.0, появился другой механизм работы с курсором - загрузчик Loader и менеджер LoaderManager, которые также доступны в библиотеке совместимости для старых устройств.
Теперь все операции с курсорами происходят в асинхронном режиме. А данные кэшируются и при необходимости информация обновляется, если данные изменились.
LoaderManager позволяет грамотно управлять загрузчиками, связанными с активностью или фрагментом. Каждая активность и каждый фрагмент имеет один экземпляр менеджера LoaderManager, который работает с загрузчиками через методы initLoader(), restartLoader(), destroyLoader(). Активность через данного менеджера может предупредить о своём уничтожении, чтобы LoaderManager в свою очередь закрыл загрузчики для экономии ресурсов.
Сам LoaderManager не знает, как данные загружаются в приложение. Он просто даёт указания загрузчику начать, остановить, обновить загрузку данных и другие команды.
LoaderManager работает с объектами Loader<D>, где D является контейнером для загружаемых данных. При этом данные не обязательно должны быть курсором. Это могут быть и List, JSONArray и т.д. В одной активности может быть несколько загрузчиков, которые являются объектами.
Класс Loader является общим. Также доступны специализированные загрузчики AsyncTaskLoader и CursorLoader.
Работа с LoaderManager происходит через три метода обратного вызова интерфейса LoaderManager.LoaderCallbacks<D>.
Интерфейс LoaderManager.LoaderCallbacks<D> определяет порядок взаимодействия с загрузчиком через методы:
public class MainActivity extends Activity implements LoaderManager.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() возвращает новый загрузчик. LoaderManager вызывает метод при создании Loader.
При попытке доступа к загрузчику (например, посредством метода initLoader()), он проверяет, существует ли загрузчик, указанный с помощью идентификатора. Если он не существует, он вызывает метод onCreateLoader(). Именно здесь и создаётся новый загрузчик.
Метод onLoadFinished вызывается автоматически, когда Loader завершает загрузку данных. Загрузчик следит за поступающими данными, а менеджер получает уведомление о завершении загрузки и передаёт результат данному методу.
Этот метод гарантировано вызывается до высвобождения последних данных, которые были предоставлены этому загрузчику. К этому моменту необходимо полностью перестать использовать старые данные (поскольку они скоро будут заменены). Однако этого не следует делать самостоятельно, поскольку данными владеет загрузчик и он позаботится об этом. Загрузчик высвободит данные, как только узнает, что приложение их больше не использует. Например, если данными является курсор из CursorLoader, не следует вызывать close() самостоятельно. Если курсор размещается в CursorAdapter, следует использовать метод swapCursor() с тем, чтобы старый Cursor не закрылся.
Метод onLoadReset() перезагружает данные в загрузчике.
Этот метод вызывается, когда состояние созданного ранее загрузчика сбрасывается, в результате чего его данные теряются. Этот обратный вызов позволяет узнать, когда данные вот-вот будут высвобождены, с тем чтобы можно было удалить свою ссылку на них.
Порядок работы менеджера загрузчиков во время создания активности.
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]
SearchView.onQueryChangeText (Поиск по тексту, см. примеры ниже)
RestartLoader
LoaderManager.LoaderCallbacks.onCreateLoader
LoaderManager.LoaderCallbacks.onLoadFinished
При уничтожении активности:
Activity.onStop()
Activity.onDestroy()
LoaderManager.LoaderCallbacks.onLoaderReset() //Обратите внимание, что этот метод вызывается
Менеджер загрузчиков управляет загрузчиками. Загрузчик предназначен для загрузки данных из источника: диск, база данных, контент-провайдер, сеть или другой процесс. Загрузчик производит выборку данных без блокировки главного потока в отдельном потоке и доставляет результаты стороне, которая в них заинтересована, следя за изменениями данных и информирует о всех важных изменениях менеджеру через специальные слушатели.
Таким образом, активность или фрагмент не интересуются, как загружаются данные. Они доверили эту работу загрузчику.
Существуют три встроенных типа загрузчиков: Loader, AsyncTaskLoader и CursorLoader. Loader — базовый класс, который сам по себе не очень полезен. Он определяет API, используемый LoaderManager для взаимодействия со всеми загрузчиками.
Для задач асинхронной загрузки данных в отдельном потоке используется класс, наследующий AsyncTaskLoader<D> вместо Loader<D>. Класс AsyncTaskLoader<D> является абстрактным и работает как AsyncTask. На основе этого класса вы можете реализовать абстрактный метод loadInBackground().
Слушатель получает информацию от загрузчика. Для этого менеджер регистрирует слушатель OnLoadCompleteListener<D>, который прослушивает события. Когда загрузка закончилась, то вызывается метод onLoadFinished(Loader<D> loader, D result).
Чтобы загрузчик начал работать, его надо запустить. Запущенный загрузчик следит за данными, пока его не перезапустят или остановят.
Остановленный загрузчик продолжает мониторить изменения в данных, но не сообщает о них. При необходимости вы можете заново запустить или перезапустить остановленный загрузчик.
При перезапуске загрузчик не должен запускать новую загрузку данных и мониторить изменения. Его задача - освободить лишние данные. Это состояние редко используется, но в некоторых случаях оно нужно.
Ваша задача сводится к созданию собственного загрузчика, реализации метода loadInBackground() и переопределении методов onStartLoading(), onStopLoading(), onReset(), onCanceled(), deliverResult(D results).
Небольшой пример для демонстрации.
Создадим кнопку и текстовую метку на экране.
Создадим класс загрузчика, который наследуется от AsyncTaskLoader. Наш загрузчик будет возвращать строку, поэтому укажем <String>:
package ru.alexanderklimov.emptyactivity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.content.AsyncTaskLoader;
import android.util.Log;
import java.util.Random;
public class RandomLoader extends AsyncTaskLoader<String> {
public final String TAG = getClass().getSimpleName();
public static final String ARG_WORD = "word";
public static final int RANDOM_STRING_LENGTH = 100;
private String mWord;
public RandomLoader(Context context, Bundle args) {
super(context);
if (args != null)
mWord = args.getString(ARG_WORD);
}
@Override
public String loadInBackground() {
if (mWord == null) {
return null;
}
Log.d(TAG, "loadInBackground");
return generateString(mWord);
}
@Override
public void forceLoad() {
Log.d(TAG, "forceLoad");
super.forceLoad();
}
@Override
protected void onStartLoading() {
super.onStartLoading();
Log.d(TAG, "onStartLoading");
forceLoad();
}
@Override
protected void onStopLoading() {
super.onStopLoading();
Log.d(TAG, "onStopLoading");
}
@Override
public void deliverResult(String data) {
Log.d(TAG, "deliverResult");
super.deliverResult(data);
}
private String generateString(String characters) {
Random rand = new Random();
char[] text = new char[RANDOM_STRING_LENGTH];
for (int i = 0; i < RANDOM_STRING_LENGTH; i++) {
text[i] = characters.charAt(rand.nextInt(characters.length()));
}
return new String(text);
}
}
Переменная mWord будет хранить базовую строку, на основе которой будет создаваться случайная строка. Этот параметр передается при создании загрузчика в конструктор с использованием объекта Bundle. Константа RANDOM_STRING_LENGTH задает максимальную длину новой случайной строки.
Наследуясь от AsyncTaskLoader, мы переопределяем несколько методов:
Для получения данных в методе loadInBackground() мы вызываем вспомогательный метод generateString(), который генерит случайную строку.
Класс активности реализует интерфейс LoaderManager.LoaderCallbacks с его методами, позволяющий нам «участвовать» в жизненном цикле загрузчика и взаимодействовать с LoaderManager.
package ru.alexanderklimov.emptyactivity;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements
LoaderManager.LoaderCallbacks<String> {
public final String TAG = this.getClass().getSimpleName();
private TextView mResultTextView;
public static final int LOADER_ID = 1;
private Loader<String> mLoader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mResultTextView = (TextView) findViewById(R.id.resultTxt);
Bundle bundle = new Bundle();
bundle.putString(RandomLoader.ARG_WORD, "test");
// Инициализируем загрузчик с идентификатором
// Если загрузчик не существует, то он будет создан,
// иначе он будет перезапущен.
mLoader = getSupportLoaderManager().initLoader(LOADER_ID, bundle, this);
}
// Будет вызван, если до этого не существовал
// Это значит, что при повороте не будет вызываться
// так как предыдущий загрузчик с данным ID уже был создан ранее
// Будет также вызван при рестарте через метод LoaderManager.restartLoader()
@Override
public Loader<String> onCreateLoader(int id, Bundle args) {
// Создаем новый CursorLoader с нужными параметрами
Loader<String> mLoader = null;
// условие можно убрать, если вы используете только один загрузчик
if (id == LOADER_ID) {
mLoader = new RandomLoader(this, args);
Log.d(TAG, "onCreateLoader");
}
return mLoader;
}
// Вызовется, когда загрузчик закончит свою работу. Вызывается в основном потоке
// Может вызываться несколько раз при изменении данных
// Также вызывается при поворотах
@Override
public void onLoadFinished(Loader<String> loader, String data) {
Log.d(TAG, "onLoadFinished");
mResultTextView.setText(data);
// Если используется несколько загрузчиков, то удобнее через оператор switch-case
// switch (loader.getId()) {
// case LOADER_ID:
// // Данные загружены и готовы к использованию
//
// break;
// }
// список теперь содержит данные на экране
}
// Вызовется при уничтожении активности
@Override
public void onLoaderReset(Loader<String> loader) {
Log.d(TAG, "onLoaderReset");
}
public void onClick(View view) {
Log.d(TAG, "startLoad");
mLoader.onContentChanged();
}
}
Метод onCreateLoader() — вызывается при инициализации загрузчика. Если загрузчик с таким идентификатором уже был создан, то метод не вызовется. Внутри мы определяем, какой идентификатор нам был передан, чтобы создать нужный загрузчик. Если загрузчик только один, то условие можно убрать.
Метод onLoadFinished() вызывается по окончанию загрузки. В метод приходят загруженные данные, а также объект загрузчика. Полученную строку мы выводим в текстовой метке.
Метод onLoaderReset() вызывается при «сбросе» состояния загрузчика. Здесь данные обнуляются, и нам нужно удалить все имеющиеся ссылки на них.
Щелчок кнопки выполняет роль триггера для запуска новой загрузки данных. Мы используем метод onContentChanged(), чтобы сигнализировать загрузчику об изменении данных.
Как происходит первый запуск загрузчика? Мы это делаем в методе onCreate(), инициализируя загрузчик. При инициализации мы передаем в параметрах идентификатор загрузчика, Bundle — объект, содержащий передаваемые аргументы (мы передаем базовую строку) и указатель на callback-объект (в нашем случае — это сама активность).
Ну теперь запускаем приложение и смотрим логи. При нажатии на кнопку текст обновляется, но в логах мы уже не видим повторного создания загрузчика.
Класс CursorLoader является наследником класса Loader и работает с курсорами в асинхронном режиме. CursorLoader расширяет AsyncTaskLoader для загрузки Cursor из ContentProvider через ContentResolver.
Смотри также документацию на русском - http://developer.android.com/intl/ru/guide/components/loaders.html
Пример работы с CursorLoader - ContactsContract. Обращение к базе данных контактов
Работаем с системным приложением Галерея
ACTION_SEND_MULTIPLE - Отправка нескольких типов контента