Главная страница
Образовательный портал Как узнать результаты егэ Стихи про летний лагерь 3агадки для детей
qrcode

Программирование под Android. Для профессионалов


Скачать 19.35 Mb.
НазваниеПрограммирование под Android. Для профессионалов
АнкорBrayn Khardi Bill Fillips - Programmirovanie po.
Дата23.05.2017
Размер19.35 Mb.
Формат файлаpdf
Имя файлаBrayn_Khardi_Bill_Fillips_-_Programmirovanie_po.pdf
оригинальный pdf просмотр
ТипДокументы
#21061
страница53 из 55
КаталогОбразовательный портал Как узнать результаты егэ Стихи про летний лагерь 3агадки для детей
Образовательный портал Как узнать результаты егэ Стихи про летний лагерь 3агадки для детей
1   ...   47   48   49   50   51   52   53   54   55
Глава 34. Локальные базы данных и SQLite
Листинг 34.24 (продолжение)
String provider = getString(getColumnIndex(COLUMN_LOCATION_PROVIDER));
Location loc = new Location(provider);
// Заполнение остальных свойств
loc.setLongitude(getDouble(getColumnIndex(COLUMN_LOCATION_LONGITUDE)));
loc.setLatitude(getDouble(getColumnIndex(COLUMN_LOCATION_LATITUDE)));
loc.setAltitude(getDouble(getColumnIndex(COLUMN_LOCATION_ALTITUDE)));
loc.setTime(getLong(getColumnIndex(COLUMN_LOCATION_TIMESTAMP)));
return loc;
}
}
Объект
LocationCursor выполняет те же функции, что и
RunCursor
, но в нем упа- ковывается курсор для возвращения строк таблицы location
, а их различные поля преобразуются в свойства объекта
Location
. В этой реализации есть одна тонкость: конструктору
Location требуется имя поставщика данных, поэтому мы извлекаем его из текущей строки, прежде чем задавать остальные свойства.
Метод queryLastLocationForRun(long)
очень похож на queryRun(long)
, за исклю- чением того, что он ищет последнее местоположение для заданной серии и упако- вывает результат в
LocationCursor
Как и в случае с queryRun(long)
, мы должны создать в
RunManager метод для его вызова и вернуть
Location из единственной строки курсора.
Листинг 34.25. Получение последнего местоположения для серии (RunManager.java)
public Location getLastLocationForRun(long runId) {
Location location = null;
LocationCursor cursor = mHelper.queryLastLocationForRun(runId);
cursor.moveToFirst();
// Если набор не пуст, получить местоположение
if (!cursor.isAfterLast())
location = cursor.getLocation();
cursor.close();
return location;
}
Теперь новый метод
RunFragment может использоваться для выборки последнего местоположения текущей серии при создании фрагмента.
Листинг 34.26. Получение последнего местоположения для текущей серии
(RunFragment.java)
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mRunManager = RunManager.get(getActivity());
// Проверить идентификатор Run и получить объект серии
Bundle args = getArguments();
if (args != null) {
long runId = args.getLong(ARG_RUN_ID, -1);
if (runId != -1) {

Упражнение. Выделение текущей серии
567
mRun = mRunManager.getRun(runId);
mLastLocation = mRunManager.getLastLocationForRun(runId);
}
}
}
В результате наш объект
RunTracker способен создавать и отслеживать столько серий, сколько выдержит диск (и батарея) вашего устройства. Все эти данные выводятся на экран в логичном, последовательном представлении. Приятного отслеживания!
Упражнение. Выделение текущей серии
В текущей реализации опознать текущую серию можно только одним способом: вручную открыть ее в списке и проверить состояние кнопок запуска и остановки.
Было бы удобнее, если бы пользователь мог быстрее и проще обратиться к текущей серии.
Простое упражнение: обеспечьте визуальное выделение строки списка, соответству- ющей текущей серии (например, выведите рядом с ней значок или измените ее цвет).
Чтобы усложнить задачу, используйте непрерывное оповещение о включенном режиме отслеживания, при нажатии на котором должна запускаться активность
RunActivity

Асинхронная загрузка
данных
В главе 34 мы реализовали хранение данных с использованием SQLite и использо- вали объекты
Cursor в главном потоке приложения. Однако такое решение нельзя признать полноценным; операции с базой данных следует по возможности вынести из главного потока приложения.
В этой главе мы используем объекты
Loader для выборки из базы данных в фоновом потоке. Интерфейс
Loader был введен в Android 3.0 (Honeycomb); он также досту- пен в библиотеке поддержки, поэтому ничто не препятствует его использованию в современных приложениях.
Loader и LoaderManager
Загрузчик
(loader) предназначен для загрузки некоторых данных (объекта) из ис- точника. Источником может быть диск, база данных,
ContentProvider
, сеть или другой процесс. Загрузчик производит выборку данных без блокировки главного потока и доставляет результаты стороне, которая в них заинтересована.
Существуют три встроенных типа загрузчиков:
Loader
,
AsyncTaskLoader и
Cursor-
Loader
(рис. 35.1).
Loader
— базовый класс, который сам по себе не очень полезен.
Он определяет API, используемый
LoaderManager для взаимодействия со всеми загрузчиками.
AsyncTaskLoader
— абстрактный класс
Loader
, использующий
AsyncTask для пере- дачи работы в другой поток. Почти все полезные классы загрузчиков, которые вы будете создавать, будут представлять собой субклассы
AsyncTaskLoader
Наконец,
CursorLoader расширяет
AsyncTaskLoader для загрузки
Cursor из
Con- tentProvider через
ContentResolver
. К сожалению, в приложении RunTracker не
35

Loader и LoaderManager
569
существует возможности использования
Cursor-
Loader с курсорами, полученными от
SQLiteDatabase
Все взаимодействие с загрузчиками осуществляется классом
LoaderManager
. Этот класс отвечает за за- пуск, остановку и сопровождение жизненного цикла всех загрузчиков, связанных с вашим компонентом.
В пределах
Fragment или
Activity для получения экземпляра
LoaderManager используется метод get-
LoaderManager()
Метод initLoader(int,
Bundle,
LoaderCallbacks)
запускает инициализацию
Loader
. В первом аргумен- те передается целочисленный идентификатор загруз- чика, во втором — объект
Bundle с аргументами (или null
), а последний аргумент содержит реализацию интерфейса
LoaderCallbacks
Как вы увидите в следующих разделах, существует много способов реализации
LoaderCallbacks
, но чаще всего они реализуются непосредственно в
Fragment
Метод restartLoader(int,
Bundle,
Loader Call- backs)
выполняет принудительный перезапуск существующего загрузчика.
Обычно перезапуск используется для перезагрузки заведомо (или потенциально) устаревших данных.
Интерфейс
LoaderCallbacks
состоит из трех методов: onCreateLoader(…)
, onLoadFinished(…)
и onLoaderReset(…)
. Все три метода будут подробно рассмотрены в ходе их реализации в RunTracker.
Почему стоит использовать загрузчик вместо, допустим, прямого использования
AsyncTask
? Самая убедительная причина заключается в том, что
LoaderManager обеспечивает жизнеспособность загрузчиков компонента вместе с их данными при изменениях конфигурации (например, при поворотах).
Если вы будете использовать
AsyncTask для загрузки данных, вам придется управ- лять жизненным циклом объектов при изменениях конфигурации и сохранять данные в месте, которое эти изменения переживет. Часто задача упрощается ис- пользованием вызова setRetainInstance(true)
для
Fragment
, и все же в отдельных ситуациях разработчику приходится вмешиваться в происходящее и писать код, который обеспечивает правильность выполнения всего происходящего.
Загрузчики в основном (хотя и не полностью!) избавляют вас от этих хлопот.
Если при изменении конфигурации вы инициализируете загрузчик, который уже завершил загрузку своих данных, он может выдать эти данные немедленно, не пытаясь загрузить их заново. Данный механизм работает независимо от того, был ваш фрагмент сохранен (retained) или нет; это упрощает вашу работу, так как вам не приходится учитывать дополнительные аспекты жизненного цикла, обусловленные сохранением фрагментов.
наследует от наследует от
Рис. 35.1. Иерархия классов
Loader

570
Глава 35. Асинхронная загрузка данных
Использование загрузчиков в RunTracker
В настоящее время RunTracker загружает три блока данных: список серий (
RunCur- sor
), данные отдельной серии (
Run
) и последнее местоположение серии (
Location
).
Все данные читаются из базы данных SQLite, поэтому все операции выборки следует переместить в
Loader для повышения эффективности работы пользовательского интерфейса.
В следующих разделах мы создадим два абстрактных субкласса
AsyncTaskLoader
Первый,
SQLiteCursorLoader
, представляет собой упрощенную версию системного класса
CursorLoader
, который работает с объектами
Cursor
, поступившими из любого источника. Второй,
DataLoader
, способен загружать произвольные данные; он упрощает использование
AsyncTaskLoader для субклассов.
Загрузка списка серий
Текущая реализация
RunListFragment напрямую запрашивает у
RunManager объект
RunCursor
, представляющий список серий, в onCreate(Bundle)
. В этом разделе мы напишем загрузчик, который будет опосредованно выполнять этот запрос в другом потоке.
RunListFragment приказывает
LoaderManager запускать (и перезапускать) загрузчик и реализует интерфейс
LoaderCallbacks для получения информации о готовности данных.
Чтобы упростить код
RunListFragment
(и классов, которые будут написаны позднее), начнем с создания абстрактного субкласса
AsyncTaskLoader с именем
SQLiteCur- sorLoader
(листинг 35.1). Этот класс повторяет большую часть кода
CursorLoader
, но без обязательного использования
ContentProvider
Листинг 35.1. Загрузчик для курсоров SQLite (SQLiteCursorLoader.java)
public abstract class SQLiteCursorLoader extends AsyncTaskLoader {
private Cursor mCursor;
public SQLiteCursorLoader(Context context) {
super(context);
}
protected abstract Cursor loadCursor();
@Override
public Cursor loadInBackground() {
Cursor cursor = loadCursor();
if (cursor != null) {
// Проверить, что окно контента заполнено
cursor.getCount();
}
return cursor;
}
@Override
public void deliverResult(Cursor data) {

Загрузка списка серий
571
Cursor oldCursor = mCursor;
mCursor = data;
if (isStarted()) {
super.deliverResult(data);
}
if (oldCursor != null && oldCursor != data && !oldCursor.isClosed()) {
oldCursor.close();
}
}
@Override
protected void onStartLoading() {
if (mCursor != null) {
deliverResult(mCursor);
}
if (takeContentChanged() || mCursor == null) {
forceLoad();
}
}
@Override
protected void onStopLoading() {
// Попытаться отменить текущую задачу загрузки, если возможно.
cancelLoad();
}
@Override
public void onCanceled(Cursor cursor) {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
@Override
protected void onReset() {
super.onReset();
// Убедиться в том, что загрузчик остановлен
onStopLoading();
if (mCursor != null && !mCursor.isClosed()) {
mCursor.close();
}
mCursor = null;
}
}
SQLiteCursorLoader реализует
AsyncTaskLoader
API для эффективной загрузки и хранения
Cursor в поле mCursor
. Метод loadInBackground()
вызывает абстрактный метод loadCursor()
для получения
Cursor
, а затем метод getCount()
для курсора, чтобы убедиться в доступности данных в памяти после их передачи главному потоку.
Метод deliverResult(Cursor)
решает две задачи. Если загрузчик запущен (это озна- чает, что данные могут быть поставлены), вызывается реализация deliverResult(…)

572
Глава 35. Асинхронная загрузка данных суперкласса. Если старый курсор больше не нужен, он закрывается для освобож- дения ресурсов. Поскольку существующий курсор может быть кэширован и ис- пользован заново, очень важно перед закрытием старого курсора убедиться в том, что старый и новый курсоры различны.
Остальные реализации методов не критичны для понимания смысла RunTracker, но вы можете найти дополнительную информацию в документации API по
Async-
TaskLoader
. При наличии этого базового класса можно реализовать очень простой субкласс
RunListCursorLoader в
RunListFragment в виде внутреннего класса.
Листинг 35.2. Реализация RunListCursorLoader (RunListFragment.java)
@Override public void onListItemClick(ListView l, View v, int position, long id) {
// Аргумент id содержит идентификатор серии;
// CursorAdapter автоматически предоставляет эту информацию.
Intent i = new Intent(getActivity(), RunActivity.class);
i.putExtra(RunActivity.EXTRA_RUN_ID, id);
startActivity(i);
}
private static class RunListCursorLoader extends SQLiteCursorLoader {
public RunListCursorLoader(Context context) {
super(context);
}
@Override
protected Cursor loadCursor() {
// Запрос на получение списка серий
return RunManager.get(getContext()).queryRuns();
}
}
private static class RunCursorAdapter extends CursorAdapter {
Теперь мы можем обновить
RunListFragment и реализовать интерфейс
LoaderCall- backs для
Cursor
. Добавьте приведенные ниже методы и включите обратные вызовы в объявление класса.
Листинг 35.3. Реализация LoaderCallbacks (RunListFragment.java)
public class RunListFragment extends ListFragment implements LoaderCallbacks
{
@Override
public Loader onCreateLoader(int id, Bundle args) {
return new RunListCursorLoader(getActivity());
}
@Override
public void onLoadFinished(Loader loader, Cursor cursor) {
// Создание адаптера, ссылающегося на этот курсор
RunCursorAdapter adapter =

Загрузка списка серий
573
new RunCursorAdapter(getActivity(), (RunCursor)cursor);
setListAdapter(adapter);
}
@Override
public void onLoaderReset(Loader loader) {
// Прекращение использования курсора (через адаптер)
setListAdapter(null);
}
Метод onCreateLoader(int,
Bundle)
вызывается объектом
LoaderManager
, когда ему потребуется создать загрузчика. Аргумент id полезен при наличии нескольких однотипных загрузчиков, которых необходимо различать, а объект
Bundle содержит все передаваемые аргументы. В этой реализации никакие аргументы не использу- ются; она просто создает новый экземпляр
RunListCursorLoader
, ссылающийся на текущий экземпляр
Activity для контекста.
Метод onLoadFinished(Loader,
Cursor)
будет вызван для главного потока после того, как данные будут загружены в фоновом режиме. В этой версии мы назна- чаем адаптером
ListView объект
RunCursorAdapter
, ссылающийся на новый курсор.
Наконец, метод onLoaderReset(Loader)
будет вызываться при отсутствии доступных данных. Чтобы исключить возможные проблемы, мы прекращаем ис- пользовать курсор, присваивая адаптеру списка null
Наконец, после реализации обратных вызовов мы можем приказать
LoaderManager выполнить его работу. Также можно удалить поле mCursor и метод onDestroy()
, в котором эта переменная очищалась.
Листинг 35.4. Использование Loader (RunListFragment.java)
public class RunListFragment extends ListFragment implements LoaderCallbacks
{
private static final int REQUEST_NEW_RUN = 0;
private RunCursor mCursor;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
// Запрос на получение списка серий
mCursor = RunManager.get(getActivity()).queryRuns();
// Создание адаптера, ссылающегося на этот курсор
RunCursorAdapter adapter = new RunCursorAdapter(getActivity(), mCursor);
setListAdapter(adapter);
// Инициализация загрузчика для загрузки списка серий
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onDestroy() {
mCursor.close();
продолжение


574
Глава 35. Асинхронная загрузка данных
Листинг 35.4 (продолжение)
super.onDestroy();
}
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (REQUEST_NEW_RUN == requestCode) {
mCursor.requery();
((RunCursorAdapter)getListAdapter()).notifyDataSetChanged();
// Перезапуск загрузчика для получения новых серий
getLoaderManager().restartLoader(0, null, this);
}
}
После внесения всех изменений запустите приложение и убедитесь в том, что дан- ные в списке заполняются так же, как прежде. При некоторой наблюдательности вы заметите, что во время загрузки данных в фоновом режиме в списке ненадолго появляется вращающийся индикатор прогресса.
ListFragment автоматически предоставляет эту функциональность, если адаптеру задается null
Загрузка одной серии
Класс
SQLiteCursorLoader хорошо подходит для загрузки данных, которые должны оставаться в курсоре (например, списков), но в
RunFragment мы загружаем два от- дельных объекта, а курсор скрывается
RunManager
. Для работы с произвольными данными нужен более общий загрузчик.
В этом разделе мы создадим новый класс
DataLoader
, являющийся субклассом
AsyncTaskLoader
DataLoader выполняет некоторые простые операции, которые должны выполняться всеми субклассами
AsyncTaskLoader
, оставляя своим суб- классам только реализацию loadInBackground()
Код класса
DataLoader приведен в листинге 35.5.
Листинг 35.5. Простой загрузчик данных (DataLoader.java)
public abstract class DataLoader extends AsyncTaskLoader {
private D mData;
public DataLoader(Context context) {
super(context);
}
@Override
protected void onStartLoading() {
if (mData != null) {
deliverResult(mData);
} else {
forceLoad();
}
}

Загрузка одной серии
575
@Override
public void deliverResult(D data) {
mData = data;
if (isStarted())
super.deliverResult(data);
}
}
Класс
DataLoader использует обобщенный тип
D
для хранения экземпляра загружа- емых данных. В методе onStartLoading()
он проверяет доступность этих данных, и если они доступны — немедленно поставляет их. В противном случае он вызывает метод forceLoad()
суперкласса для загрузки данных.
Реализация deliverResult(D)
сохраняет новый объект данных, и если загрузчик запущен — вызывает реализацию суперкласса для выполнения поставки.
Чтобы использовать новый класс, субклассируйте
DataLoader и используйте суб- класс
RunLoader в
RunFragment
Листинг 35.6. Загрузчик серии (RunLoader.java)
public class RunLoader extends DataLoader {
private long mRunId;
public RunLoader(Context context, long runId) {
super(context);
mRunId = runId;
}
@Override
public Run loadInBackground() {
return RunManager.get(getContext()).getRun(mRunId);
}
}
Конструктор
RunLoader ожидает получить
Context
(
Activity
) и значение long
, представляющее идентификатор загружаемой серии. Метод loadInBackground()
запрашивает у
RunManager серию с заданным идентификатором и возвращает ее.
Когда все будет сделано, вы сможете использовать
RunLoader в
RunFragment вместо прямого взаимодействия с
RunManager в главном потоке. Однако в реа- лизации существует одно отличие: так как
RunFragment загружает два разных типа данных, серию и позицию, механизм
LoaderCallbacks
в сочетании с ограничениями обобщенных типов Java не позволяет напрямую реализовать интерфейс в методах
RunFragment
. Чтобы обойти ограничения, можно создать внутренние классы, реализующие
LoaderCallbacks
для
Run и
Location по от- дельности, и передать экземпляр каждого из них при вызове метода initLoader()
класса
LoaderManager
Интеграция начинается с добавления внутреннего класса
RunLoaderCallbacks в
RunFragment

576
1   ...   47   48   49   50   51   52   53   54   55

перейти в каталог файлов

Образовательный портал Как узнать результаты егэ Стихи про летний лагерь 3агадки для детей

Образовательный портал Как узнать результаты егэ Стихи про летний лагерь 3агадки для детей