Каковы преимущества CursorLoaders?
Я использую Cursors
экстенсивно в своем приложении, чтобы загружать и иногда писать информацию из базы данных и в нее. Я видел, что Honeycomb и пакет совместимости имеют новые классы Loader
, предназначенные для загрузки данных "хорошим" способом.
По существу, эти новые классы (в частности CursorLoader
) значительно лучше, чем предыдущие методы управления данными? В чем преимущество CursorLoader
над управляемым Cursors
, например?
И я использую ContentProvider
для обработки данных, которые, очевидно, принимают Uris
, но как это связано с методом initLoader()
? Должен ли я настроить каждый из моих Fragments
для использования Loaders отдельно? И насколько уникальным должен быть идентификатор для каждого загрузчика, находится ли он в области моего приложения или всего лишь фрагмента? Есть ли простой способ просто передать Uri
в CursorLoader для запроса моих данных?
Все, что я вижу на данный момент, это то, что Loaders добавляют лишний дополнительный шаг для ввода моих данных в мое приложение, так что может кто-нибудь лучше объяснить их мне?
Ответы
Ответ 1
Есть два основных преимущества использования 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
Надеюсь, что это поможет!
Ответ 2
Если кто-то окажется в подобной ситуации, вот что я сделал:
- Создал класс, который реализует
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);
}
И когда я хотел использовать 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()
не будет вызван