Каковы преимущества CursorLoaders?

Я использую Cursors экстенсивно в своем приложении, чтобы загружать и иногда писать информацию из базы данных и в нее. Я видел, что Honeycomb и пакет совместимости имеют новые классы Loader, предназначенные для загрузки данных "хорошим" способом.

По существу, эти новые классы (в частности CursorLoader) значительно лучше, чем предыдущие методы управления данными? В чем преимущество CursorLoader над управляемым Cursors, например?

И я использую ContentProvider для обработки данных, которые, очевидно, принимают Uris, но как это связано с методом initLoader()? Должен ли я настроить каждый из моих Fragments для использования Loaders отдельно? И насколько уникальным должен быть идентификатор для каждого загрузчика, находится ли он в области моего приложения или всего лишь фрагмента? Есть ли простой способ просто передать Uri в CursorLoader для запроса моих данных?

Все, что я вижу на данный момент, это то, что Loaders добавляют лишний дополнительный шаг для ввода моих данных в мое приложение, так что может кто-нибудь лучше объяснить их мне?

2 ответа

Есть два основных преимущества использования CursorLoader в вашем приложении: Activity.managedQuery():

  • Запрос обрабатывается в фоновом потоке для вас (благодаря построению на AsyncTaskLoader), поэтому большие запросы данных не блокируют пользовательский интерфейс. Это то, что рекомендовали документы для себя при использовании простой Cursor, но теперь это делается под капотом.
  • CursorLoader автоматически обновляется. Помимо выполнения начального запроса, CursorLoader регистрирует a ContentObserver с запрошенным набором данных и вызывает forceLoad() сам по себе, когда набор данных изменяется. Это приводит к тому, что вы получаете асинхронные обратные вызовы в любое время, когда данные изменяются для обновления представления.

Каждый экземпляр Loader также обрабатывается через сингулярный LoaderManager, поэтому вам все равно не нужно управлять курсором напрямую, и теперь соединение может сохраняться даже за пределами одного Activity. LoaderManager.initLoader() и LoaderManager.restartLoader() позволяют повторно подключиться к уже существующей Loader, уже настроенной для вашего запроса, и в некоторых случаях мгновенно получать последние данные, если они доступны.

Теперь ваш Activity или Fragment теперь реализует интерфейс LoaderManager.Callback. Вызов initLoader() приведет к методу onCreateLoader(), где вы при необходимости создадите запрос и новый экземпляр CursorLoader. Метод onLoadFinished() будет запускаться каждый раз, когда будут доступны новые данные, и будет включать в себя последний Cursor для подключения к просмотру или в противном случае итерации.

Кроме того, есть довольно хороший пример всего этого, связанного на странице документации по LoaderManager: http://developer.android.com/reference/android/app/LoaderManager.html

Надеюсь, что это поможет!


Если кто-то окажется в подобной ситуации, вот что я сделал:

  • Создал класс, который реализует LoaderCallbacks и обрабатывает все запросы, которые вам понадобятся.
  • Поставьте это с помощью Context и Adapter.
  • Создайте уникальные идентификаторы для каждого запроса, который вы будете использовать (если вы используете UriMatcher, также можете использовать те же самые)
  • Сделайте удобный метод, который передает запросы в пакет, необходимый для LoaderCallbacks
  • Это в значительной степени это:) Я поместил часть своего кода ниже, чтобы показать, что именно я сделал

В моем классе GlobalCallbacks:

public static final String PROJECTION = "projection";
public static final String SELECTION = "select";
public static final String SELECTARGS = "sargs";
public static final String SORT = "sort";
Context mContext;
SimpleCursorAdapter mAdapter;
public GlobalCallbacks(Context context, SimpleCursorAdapter adapter) {
 mContext = context;
 mAdapter = adapter;
}
@Override
public Loader<cursor> onCreateLoader(int id, Bundle args) {
 Uri contentUri = AbsProvider.customIntMatch(id);
 if (contentUri != null) {
 return new CursorLoader(mContext, contentUri, args.getStringArray(PROJECTION), args.getString(SELECTION), 
 args.getStringArray(SELECTARGS), args.getString(SORT));
 } else return null;
}
@Override
public void onLoadFinished(Loader<cursor> arg0, Cursor arg1) {
 mAdapter.swapCursor(arg1); 
}
@Override
public void onLoaderReset(Loader<cursor> arg0) {
 mAdapter.swapCursor(null);
}
</cursor></cursor></cursor>

И когда я хотел использовать CursorLoader (Helper.bundleArgs() - метод удобства):

scAdapt = new Adapters.NewIndexedAdapter(mHost, getMenuType(), 
 null, new String[] { "name" }, new int[] { android.R.id.text1 });
 getLoaderManager().initLoader(
 GlobalCallbacks.GROUP,
 Helper.bundleArgs(new String[] { "_id", "name" }),
 new GlobalCallbacks(mHost, scAdapt));
 setListAdapter(scAdapt);

И в помощнике:

public static Bundle bundleArgs(String[] projection, String selection, String[] selectionArgs) {
 Bundle b = new Bundle();
 b.putStringArray(GlobalCallbacks.PROJECTION, projection);
 b.putString(GlobalCallbacks.SELECTION, selection);
 b.putStringArray(GlobalCallbacks.SELECTARGS, selectionArgs);
 return b;
}

Надеюсь, это поможет кому-то еще:)

ИЗМЕНИТЬ

Чтобы более подробно объяснить:

  • Сначала инициализируется адаптер с нулем Cursor. Мы не поставляем его с Cursor, потому что GlobalCallbacks даст адаптеру правильный Cursor в onLoadFinished(..)
  • Далее, скажем LoaderManager, мы хотим инициализировать новый CursorLoader. Мы поставляем новый экземпляр GlobalCallbacks (который реализует Loader.Callbacks), который затем контролирует загрузку курсора. Мы также должны снабдить его адаптером, чтобы он мог поменять местами новый Cursor после его полной загрузки. В какой-то момент LoaderManager (который встроен в ОС) вызовет onCreateLoader(..) из GlobalCallbacks и начнет асинхронную загрузку данных
  • Helper.bundleArgs(..) просто помещает аргументы для запроса в Bundle (например, проекция столбцов, порядок сортировки, предложение WHERE)
  • Затем мы устанавливаем Fragment ListAdapter. Курсор по-прежнему будет пустым в этой точке, поэтому он покажет знак загрузки или пустое сообщение до тех пор, пока onLoadFinished() не будет вызван

licensed under cc by-sa 3.0 with attribution.