Главная страница
Образовательный портал Как узнать результаты егэ Стихи про летний лагерь 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
страница42 из 55
КаталогОбразовательный портал Как узнать результаты егэ Стихи про летний лагерь 3агадки для детей
Образовательный портал Как узнать результаты егэ Стихи про летний лагерь 3агадки для детей
1   ...   38   39   40   41   42   43   44   45   ...   55
Глава 27. Looper, Handler и HandlerThread
Главный поток
«Готово! А теперь назначим фотографию этому виджету
ImageView.»
Рис. 27.9. Планирование операций в главном потоке из ThumbnailDownloader
В файле
ThumbnailDownloader.java добавьте упоминавшуюся выше переменную mRes- ponseHandler для хранения экземпляра
Handler
, переданного из главного потока.
Затем замените конструктор другим, который получает
Handler и задает перемен- ную, и добавьте интерфейс слушателя для передачи ответов.
Листинг 27.6. Добавление обработчика ответа (ThumbnailDownloader.java)
public class ThumbnailDownloader extends HandlerThread {
private static final String TAG = "ThumbnailDownloader";
private static final int MESSAGE_DOWNLOAD = 0;
Handler mHandler;
Map requestMap =
Collections.synchronizedMap(new HashMap());
Handler mResponseHandler;
Listener mListener;
public interface Listener {
void onThumbnailDownloaded(Token token, Bitmap thumbnail);
}
public void setListener(Listener listener) {
mListener = listener;
}
public ThumbnailDownloader() {
super(TAG);
public ThumbnailDownloader(Handler responseHandler) {
super(TAG);
mResponseHandler = responseHandler;
}
Затем измените класс
PhotoGalleryFragment так, чтобы он передавал
Handler классу
ThumbnailDownloader
, а также
Listener для задания возвращаемых экземпляров

Сообщения и обработчики сообщений
453
Bitmap по дескрипторам
ImageView
. Помните, что по умолчанию
Handler присо- единяет себя к экземпляру
Looper текущего потока. Так как экземпляр
Handler создается в onCreate(…)
, он будет присоединен к экземпляру
Looper главного потока.
Листинг 27.7. Подключение к обработчику ответа (PhotoGalleryFragment.java)
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
new FetchItemsTask().execute();
mThumbnailThread = new ThumbnailDownloader();
mThumbnailThread = new ThumbnailDownloader(new Handler());
mThumbnailThread.setListener(new ThumbnailDownloader.Listener() {
public void onThumbnailDownloaded(ImageView imageView, Bitmap thumbnail) {
if (isVisible()) {
imageView.setImageBitmap(thumbnail);
}
}
});
mThumbnailThread.start();
mThumbnailThread.getLooper();
Log.i(TAG, "Background thread started");
}
Теперь
ThumbnailDownloader имеет доступ к экземпляру
Handler
, связанному с экзем- пляром
Looper главного потока, через поле mResponseHandler
. Кроме того, он прика- зывает
Listener выполнять операции пользовательского интерфейса с возвращаемы- ми объектами
Bitmap
. Обратите внимание: вызов imageView.setImageBitmap(Bitmap)
защищается вызовом
Fragment.isVisible()
. Защита предотвращает назначение изображения устаревшему виджету
ImageView
Аналогичным образом можно отправить главному потоку нестандартный объект
Messsage
. Для этого потребуется другой субкласс
Handler с переопределением handleMessage(…)
. Однако вместо этого мы используем другой удобный метод
Handler
— post(Runnable)
Handler.post(Runnable)
— вспомогательный метод для отправки сообщений сле- дующего вида:
Runnable myRunnable = new Runnable() {
public void run() {
/* Здесь располагается ваш код */
}
};
Message m = mHandler.obtainMessage();
m.callback = myRunnable;
Если у
Message задано поле callback
, то вместо приемника
Handler выполняется объект
Runnable из поля callback
Включите в
ThumbnailDownloader.handleRequest()
следующий код.

454
Глава 27. Looper, Handler и HandlerThread
Листинг 27.8. Загрузка и вывод (ThumbnailDownloader.java)
private void handleRequest(final Token token) {
try {
final String url = requestMap.get(token);
if (url == null) return;
byte[] bitmapBytes = new FlickrFetchr().getUrlBytes(url);
final Bitmap bitmap = BitmapFactory
.decodeByteArray(bitmapBytes, 0, bitmapBytes.length);
Log.i(TAG, "Bitmap created");
mResponseHandler.post(new Runnable() {
public void run() {
if (requestMap.get(token) != url)
return;
requestMap.remove(token);
mListener.onThumbnailDownloaded(token, bitmap);
}
});
} catch (IOException ioe) {
Log.e(TAG, "Error downloading image", ioe);
}
}
А поскольку mResponseHandler связывается с
Looper главного потока, этот код об- новления пользовательского интерфейса будет выполнен в главном потоке.
Что делает этот код? Сначала он проверяет requestMap
. Такая проверка необходима, потому что
GridView заново использует свои представления. К тому времени, когда
ThumbnailDownloader завершит загрузку
Bitmap
, может оказаться, что виджет
Grid-
View уже переработал
ImageView и запросил для него изображение с другого URL- адреса. Эта проверка гарантирует, что каждый объект
Token получит правильное изображение, даже если за прошедшее время был сделан другой запрос.
Наконец, мы удаляем
Token из requestMap и назначаем изображение для
Token
Прежде чем запускать приложение, чтобы увидеть завоеванные тяжелым трудом изображения, необходимо принять во внимание одну последнюю опасность. Если пользователь повернет экран,
ThumbnailDownloader может оказаться связанным с недействительными экземплярами
ImageView
. Нажатие на них грозит всевозмож- ными неприятностями.
Напишите следующий метод для удаления всех запросов из очереди.
Листинг 27.9. Добавление метода очистки очереди (ThumbnailDownloader.java)
public void clearQueue() {
mHandler.removeMessages(MESSAGE_DOWNLOAD);
requestMap.clear();
}
Затем очистите загрузчик в
PhotoGalleryFragment при уничтожении представления.

Упражнение. Предварительная загрузка и кэширование
455
Листинг 27.10. Вызов метода очистки очереди (PhotoGalleryFragment.java)
@Override
public void onDestroyView() {
super.onDestroyView();
mThumbnailThread.clearQueue();
}
На этом наша работа в этой главе подходит к концу. Запустите приложение Photo-
Gallery. Прокрутите список и понаблюдайте за тем, как происходит динамическая загрузка изображений.
Приложение PhotoGallery выполняет свою основную функцию — вывод изобра- жений из Flickr. В нескольких следующих главах мы дополним его новыми функ- циями: поиском фотографий и открытием страницы Flickr каждой фотографии в веб-представлении.
Для любознательных: AsyncTask и потоки
Теперь вы понимаете, как работают классы
Handler и
Looper
, и класс
AsyncTask уже кажется не таким волшебным. При этом он требует меньшего объема работы по сравнению с тем, что мы сделали сейчас. Так почему бы не использовать
AsyncTask вместо
HandlerThread
?
По нескольким причинам. Самая принципиальная заключается в том, что класс
AsyncTask проектировался не для этого. Он предназначен для кратковременной работы, которая не повторяется слишком часто.
AsyncTask отлично подходит для таких ситуаций, как в нашем коде из предыдущей главы. Но если вы создаете мно- жество
AsyncTask или они выполняются в течение долгого времени, вероятно, вы неправильно выбрали класс.
Есть и другая, более убедительная техническая причина: в Android 3.2 реализация
AsyncTask была серьезно изменена. Начиная с Android 3.2,
AsyncTask не создает поток для каждого экземпляра
AsyncTask
. Вместо этого он создает объект
Executor для выполнения фоновой работы всех экземпляров
AsyncTask в одном фоновом по- токе. Это означает, что экземпляры
AsyncTask будут выполняться друг за другом, а затянувшаяся операция
AsyncTask не позволит другим экземплярам
AsyncTask получить процессорное время.
Организовать безопасное параллельное выполнение
AsyncTask с использованием пула потоков возможно, но мы не рекомендуем так поступать. Если вы рассматри- ваете такое решение, обычно лучше самостоятельно организовать многопоточное выполнение, используя объекты
Handler для взаимодействия с главным потоком, когда возникнет такая необходимость.
Упражнение. Предварительная загрузка
и кэширование
Пользователи понимают, что не все происходит мгновенно (или по крайней мере большинство пользователей). Но несмотря на это, программисты стремятся к со- вершенству.

456
Глава 27. Looper, Handler и HandlerThread
Для достижения моментального отклика в большинстве реальных приложений приведенный код расширяется в двух направлениях:

добавление уровня кэширования;

предварительная загрузка изображений.
Кэш представляет собой место для хранения определенного количества объектов
Bitmap
, чтобы они оставались в памяти даже после завершения использования.
Объем кэша ограничен, поэтому вам понадобится стратегия выбора сохраняемых объектов при исчерпании свободного места. Многие кэши используют стратегию
LRU (Least Recently Used): при нехватке свободного места из кэша удаляется эле- мент, который дольше всего не использовался.
Библиотека поддержки Android содержит класс с именем
LruCache
, реализующий стратегию LRU. В первом упражнении используйте
LruCache для добавления про- стейшего кэширования
ThumbnailDownloader
. Каждый раз, когда для URL-адреса загружается объект
Bitmap
, вы помещаете его в кэш. Затем, когда требуется загру- зить новое изображение, вы сначала проверяете содержимое кэша и смотрите, нет ли его в кэше.
После того как в программе будет создан кэш, он может использоваться для пред- варительной загрузки, то есть загрузки данных в кэш еще до того, как они факти- чески потребуются программе. Тем самым предотвращается задержка для загрузки объектов
Bitmap до их вывода.
Качественно реализовать предварительную загрузку непросто, но она существенно меняет восприятие приложения пользователем. Во втором, более сложном упраж- нении для каждого выводимого элемента
GalleryItem выполните предварительную загрузку 10 предшествующих и 10 следующих элементов
GalleryItem

Поиск
Следующим шагом в работе над приложением PhotoGallery станет поиск фото- графий на Flickr. В этой главе вы узнаете, как правильно интегрировать поиск в приложение по правилам Android.
Впрочем, как выясняется, правильных способов несколько. Поиск был интегрирован в Android с самого начала, но он (как и кнопка меню) с тех пор заметно изменился.
Как и меню, новый код поиска строился на базе существующих API. Таким обра- зом, строя поиск в традиционном стиле, вы на самом деле готовитесь реализовать полноценный современный поиск Jelly Bean.
Поиск в Flickr
Начнем с того, что нужно сделать на стороне Flickr. Для выполнения поиска в Flickr следует вызвать метод flickr.photos.search
. Вот как выглядит вызов метода flickr.
photos.search для поиска текста «red»:
https://api.flickr.com/services/rest/?method=flickr.photos.search
&api_key=XXX&extras=url_s&text=red
Метод получает новые параметры, определяющие условия поиска, в частности па- раметр строки запроса. К счастью, разбор разметки XML, которая будет возвращена элементам
GalleryItem
, работает точно так же.
Внесите изменения, представленные в листинге 28.1, чтобы добавить в
FlickrFetchr новый метод поиска. Поскольку разбор данных результатов search и getRecent для
Gal- leryItem происходит одинаково, мы переработаем часть старого кода из fetchItems()
в новый метод, который будет называться downloadGalleryItems(String)
. Будьте внимательны — старый код fetchItems()
перемещается в новую версию fetchItems()
, а не удаляется из приложения.
28

458
Глава 28. Поиск
Листинг 28.1. Добавление метода поиска фотографий Flickr (FlickrFetchr.java)
public class FlickrFetchr {
public static final String TAG = "PhotoFetcher";
private static final String ENDPOINT = "https://api.flickr.com/services/rest/";
private static final String API_KEY = "4f721bbafa75bf6d2cb5af54f937bb70";
private static final String METHOD_GET_RECENT = "flickr.photos.getRecent";
private static final String METHOD_SEARCH = "flickr.photos.search";
private static final String PARAM_EXTRAS = "extras";
private static final String PARAM_TEXT = "text";
public ArrayList fetchItems() {
public ArrayList downloadGalleryItems(String url) {
ArrayList items = new ArrayList();
try {
String url = Uri.parse(ENDPOINT).buildUpon()
.appendQueryParameter("method", METHOD_GET_RECENT)
.appendQueryParameter("api_key", API_KEY)
.appendQueryParameter(PARAM_EXTRAS, EXTRA_SMALL_URL)
.build().toString();
String xmlString = getUrl(url);
Log.i(TAG, "Received xml: " + xmlString);
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser parser = factory.newPullParser();
parser.setInput(new StringReader(xmlString));
parseItems(items, parser);
} catch (IOException ioe) {
Log.e(TAG, "Failed to fetch items", ioe);
} catch (XmlPullParserException xppe) {
Log.e(TAG, "Failed to parse items", xppe);
}
return items;
}
public ArrayList fetchItems() {
// Сюда перемещается код, приведенный выше
String url = Uri.parse(ENDPOINT).buildUpon()
.appendQueryParameter("method", METHOD_GET_RECENT)
.appendQueryParameter("api_key", API_KEY)
.appendQueryParameter(PARAM_EXTRAS, EXTRA_SMALL_URL)
.build().toString();
return downloadGalleryItems(url);
}
public ArrayList search(String query) {
String url = Uri.parse(ENDPOINT).buildUpon()
.appendQueryParameter("method", METHOD_SEARCH)
.appendQueryParameter("api_key", API_KEY)
.appendQueryParameter(PARAM_EXTRAS, EXTRA_SMALL_URL)
.appendQueryParameter(PARAM_TEXT, query)
.build().toString();
return downloadGalleryItems(url);
}
}

Диалоговое окно поиска
459
Метод downloadGalleryItems(String)
используется дважды, потому что код загруз- ки и разбора XML одинаков для search и getRecent
. Поиск сводится к простому вызову нового метода flickr.photos.search с передачей закодированной строки запроса в параметре text
Теперь подключим тестовый код для вызова кода поиска из
PhotoGalleryFragment.
FetchItemsTask
. Пока мы используем жестко запрограммированный запрос, чтобы убедиться в правильности работы поиска.
Листинг 28.2. Код с фиксированным запросом поиска (PhotoGalleryFragment.java)
private class FetchItemsTask extends AsyncTask> {
@Override protected ArrayList doInBackground(Void... params) {
String query = "android"; // Только для тестирования
if (query != null) {
return new FlickrFetchr().search(query);
} else {
return new FlickrFetchr().fetchItems();
}
}
@Override protected void onPostExecute(ArrayList items) {
}
}
}
По умолчанию используется старый код getRecent
. Если поисковый запрос отли- чен от null
(а теперь это условие выполняется всегда), то
FetchItemsTask получает результаты поиска.
Запустите PhotoGallery и проверьте результаты. Если повезет, вы увидите пару фоток Энди.
Диалоговое окно поиска
В этом разделе мы реализуем в PhotoGallery поисковый интерфейс Android. Начнем с традиционного интерфейса диалогового окна.
Создание поискового интерфейса
В Honeycomb создатели Android избавились от физических кнопок поиска. Впрочем, даже до этого присутствие кнопки поиска не было гарантировано. Современные приложения Android с функцией поиска обязаны реализовать кнопку поиска, если они должны работать на устройствах до 3.0.
Реализация поиска не так уж сложна — достаточно вызвать метод
Activity.on-
SearchRequested()
. Этот метод выполняет точно такую же операцию, как и нажатие кнопки поиска.

460
Глава 28. Поиск
Добавьте XML-файл в меню PhotoGallery res/menu/fragment_photo_gallery.xml
. Нашему приложению также понадобится интерфейс для очистки условия поиска, поэтому добавьте кнопку сброса.
Листинг 28.3. Добавление команд меню поиска (res/menu/fragment_photo_gallery.xml)


android:title="@string/search"
android:icon="@android:drawable/ic_menu_search"
android:showAsAction="ifRoom"
/>

android:title="@string/clear_search"
android:icon="@android:drawable/ic_menu_close_clear_cancel"
android:showAsAction="ifRoom"
/>

Сейчас нам не хватает пары строк; добавьте их в strings.xml
. (Позднее нам понадо- бится строка с подсказкой поиска, заодно добавим и ее.)
Листинг 28.4. Добавление строк поиска (res/values/strings.xml)

PhotoGalleryActivity
Search Flickr
Search
Clear Search

Подключите обратные вызовы командного меню. Как сказано выше, для кнопки поиска будет вызываться метод onSearchRequested()
. Для кнопки отмены пока не будет делаться ничего.
Листинг 28.5. Обратные вызовы командного меню (PhotoGalleryFragment.java)
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setHasOptionsMenu(true);
}
@Override public void onDestroyView() {
}

Диалоговое окно поиска
461
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_photo_gallery, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_search:
getActivity().onSearchRequested();
return true;
case R.id.menu_item_clear:
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Опробуйте новый интерфейс меню и убедитесь в том, что он правильно отобра- жается.
Рис. 28.1. Новый интерфейс поиска
Впрочем, при нажатии кнопки поиска сейчас ничего не происходит. Чтобы метод onSearchRequested()
заработал, необходимо сделать
PhotoGalleryActivity
поис-
ковой активностью
(searchable activity).

462
1   ...   38   39   40   41   42   43   44   45   ...   55

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

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

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