
Полная версия:
Программирование приложений для мобильных устройств под управлением Android. Часть 2
AsyncTask – универсальный класс. Он определяется тремя основными параметрами: params, progress, и result. Params – тип параметров, которые отправляются в AsyncTask при выполнении. Progress – тип любых единиц прогресса, публикуемого в процессе выполнения фоновой операции. Result – тип результата фонового вычисления.
Во время выполнения AsyncTask проходит через четыре шага. Во-первых, onPreExecute () – метод вызывается в потоке UI до начала работы фонового потока.
Сразу же после него запускается doInBackground (Params…) и выполняет вычисления в фоновом режиме. Сюда же передаются параметры. И этот же метод возвращает в последний шаг результат типа Result.
Во время работы doInBackground может дополнительно вызвать метод publishProgress (Progress…) с перечнем переменных, который обеспечит некоторую индикацию прогресса длительного процесса. А переданные переменные используются в UI потоке при помощи метода onProgressUpdate (Progress…), который запускается в потоке пользовательского интерфейса после вызова publishProgress.
И последний шаг, onPostExecute (Result) вызывается в UI потоке после окончания фоновых вычислений с результатом, возвращенным как его параметр.
Давайте рассмотрим версию нашего приложения загрузки картинки, реализованного с AsyncTask. Если мы запустим приложение, то увидим, что оно выглядит подобно предыдущим примерам, но здесь добавлен новый элемент интерфейса – индикатор выполнения, который показывает, какое количество работы по загрузке изображения уже выполнено.
После нажатия кнопки «Load Icon» появится маленький индикатор выполнения и начнет медленно заполняться. После нажатия второй кнопки раскроется знакомый текст – кнопка «Other Button» работает. И наконец, на экране появится изображение.

А теперь рассмотрим исходный код этого приложения. Откроем основную Activity.

«Слушатель» кнопки «Load Button» создает новый экземпляр LoadIconTask и сразу же происходит вызов execute с передачей в качестве параметра id ресурса изображения.
Далее рассмотрим класс LoadIconTask более подробно. LoadIconTask – это AsyncTask с параметрами: params – типа целое, progress – типа целое и result – типа битовый массив.

Первый метод, который мы рассмотрим, это onPreExecute. Этот метод выполняется в UI потоке и его назначение – сделать индикатор выполнения видимым на дисплее.
Следующий метод – doInBackground. Этот метод получает в качестве параметра целое число – id ресурса для битового массива, который был передан в метод LoadiconTask. execute. doInBackground выполняет работу загрузки битового массива. И пока он делает это, то периодически вызывает publishProgress, передавая параметр, представляющий собой процент загрузки, которая была проделана до этого момента. Этот пример является немного надуманным с целью упрощения понимания.
Следующий метод – onProgressUpdate. Этот метод выполняется в потоке пользовательского интерфейса, получает параметр, который был передан в PublishProgress, и затем устанавливает индикатор выполнения для отображения процента выполненной работы.
И наконец, последний метод – onPostExecute. Этот метод так же работает в UI потоке и получает только что загруженный массив (Bitmap) в качестве параметра.

Сначала onPostExecute делает индикатор выполнения невидимым, так как он больше не нужен и затем выводит загруженное изображение на экран.
Класс Handler – это обработчик, позволяющий отправлять и обрабатывать сообщения и объекты Runnable, ассоциированные с очередью сообщений потока. Каждый его экземпляр связан с отдельным потоком и его очередью сообщений. Как и AsyncTask, класс Handler предназначен для взаимодействия между двумя потоками, но он более гибок, так как может работать с любыми двумя потоками, а не только между фоновым и потоком пользовательского интерфейса. Каждый поток может передать работу другому потоку путем отправки сообщения или объекта Runnable обработчику, который с ним ассоциирован.
Мы используем объекты Runnable, когда отправитель (поток-заказчик) точно знает какую работу и как необходимо выполнить, но выполнить её надо в потоке обработчика. С другой стороны, Сообщение – это класс, который может содержать данные, такие как код сообщения, произвольный объект данных или целочисленные аргументы. И мы будем использовать сообщения, когда поток-отправитель указывает какую операцию необходимо выполнить в другом потоке, а как её выполнить определит handler.
В Android каждый поток связан с очередью сообщений – messageQueue и петлителем – Looper. Очередь сообщений – структура данных, которая содержит сообщения и объекты runnable. Петлитель забирает эти сообщения и объекты runnable из очереди сообщений и диспетчеризирует их в зависимости от обстоятельств.

Эта диаграмма изображает поток Tread A, который создает Runnable внутри метода post (опубликовать) и использует объект-обработчик – Handler, чтобы отправить его в поток этого обработчика. Runnable помещается в очередь сообщений потока, связанного с обработчиком. И тоже самое происходит с сообщениями.

А эта диаграмма изображает поток Thread B, который создает сообщение и использует метод обработчика sendMessage, чтобы отправить это сообщение в поток обработчика. Сообщение помещается в очередь сообщений, связанную с этим обработчиком.
В то время, когда выполняются все вышеописанные операции, объект-петлитель просто «сидит» ожидая работы, которая появится в очереди сообщений. И когда эта работа появляется, петлитель реагирует одним из двух способов в зависимости от вида работы, которая только что появилась. Если эта работа – сообщение, петлитель обрабатывает это сообщение, вызывая метод обработчика handleMessage и передавая в нем само сообщение. Если же эта работа будет Runnable, то петлитель просто вызывает его метод run.
Существует несколько методов, которые позволяют запланировать выполнение работы в разное время. Например, можно использовать метод postAtTime, чтобы добавить runnable в очередь сообщений и выполнить его в определенное вами время. Метод postDelayed позволяет добавить runnable в очередь сообщений и выполнить его после указанной задержки.
Если вы хотите отправлять сообщения, сначала необходимо сообщение создать. Один из способов это сделать – использовать метод обработчика obtainMessage, который выдаст вам сообщение уже с установленным обработчиком. Можно также использовать метод obtain класса message.
Что касается runnable, то есть несколько методов, которые можно использовать, чтобы отправить сообщение. Метод sendMessage уже упоминался. Есть также его версия, которая позволяет поместить сообщение вперед в очереди сообщений, чтобы оно было выполнено как можно скорее. Есть метод sendMessageAtTime, чтобы поставить сообщение в очередь согласно требуемому времени. Есть также метод sendMessageDelayed, который поставит сообщение в очередь в текущее время плюс указанная задержка.
Давайте рассмотрим исходный код некоторых версий нашего рабочего примера, в котором были использованы обработчики. Вот приложение Threading handler runnable. Откроем основную Activity этого приложения. Во-первых, вы видите, что этот код создает новый обработчик. Этот обработчик создается в основном потоке пользовательского интерфейса. Таким образом, объекты runnable, которые получает этот обработчик, будут выполняться в UI потоке.

Далее вы видите «слушателя» кнопки «Load Icon». Когда пользователь нажимает кнопку «Load Icon», этот код создает и запускает новый поток, метод run которой определен runnable-объектом LoadIconTask.

Метод run класса LoadIconTask начинается, создавая новый объект Runnable, который при исполнении сделает видимой шкалу прогресса.

Далее происходит загрузка битового массива. И во время этой загрузки, периодически публикуется её прогресс методом setProgress другого runnable.

Далее создается новый Runnable, который устанавливает только что загруженный битовый массив на экран. И заканчивается все, созданием последнего Runnable, который делает шкалу прогресса невидимой.

А теперь рассмотрим вторую версию этого приложения, которое отправляет сообщения вместо runnable – ThreadingHandlerMessages. Откроем основную Activity этого приложения. Во-первых, этот код создает новый обработчик. И как в предыдущем примере, этот обработчик будет создаваться основным UI потоком. И соответственно работа, которую выполняет этот обработчик, будет выполняться в UI потоке. У этого обработчика есть метод handleMessage, в котором он реализует различные виды работ. Этот метод вначале проверяет код сообщения, содержащийся в этом сообщении, и затем выполняет действие, определенное для этого кода сообщения.

Например, если код – set_progress_bar_visibility, то будет установлено состояние видимости шкалы прогресса. Если код – progress_update, то будет установлено значение прогресса на шкале прогресса. Если код – set_bitmap, то этот код выводит битовый массив на дисплей.
Теперь перейдем к «слушателю» кнопки «Load Icon». Как и в предыдущих примерах, когда пользователь нажимает кнопку, этот код, создает и запускает новый поток, чей метод run определен runnable-объектом LoadIconTask.

Выполнение этого метода начинается при получении сообщения с кодом set_progress_bar_visibility с параметром, указывающим, что шкала прогресса должна стать видимой. Тогда это сообщение отправляется в обработчик, который и сделает шкалу прогресса видимой.
Далее происходит загрузка битового массива. И одновременно с этим, периодически обновляется прогресс, при помощи сообщения с кодом progress_update и с параметром, который указывает процент выполненной работы. Это будет результатом вызова обработчиком метода setProgress объекта progressBar.

Затем принимается и отправляется сообщение, чтобы вывести только что загруженное изображение на дисплей. И наконец, отправляется последнее сообщение, чтобы сделать шкалу прогресса невидимой.
Работа с сетью
Одной из определяющих характеристик современных мобильных устройств является то, что они могут обеспечить нас связью и сетевым подключением, то есть он-лайн, не привязывая нас к определенному местоположению. Смартфоны и планшеты комбинируют мощные процессоры с быстрым сетевым соединением по WiFi и сотовым сетям. Мобильные приложения должны уметь использовать эти сетевые возможности, чтобы обеспечить нас данными и предоставить доступ к службам. Android включает в себя поддержку множества сетевых классов при помощи пакетов Java.net, org. appache и android.net.
В текущей главе мы рассмотрим некоторые из этих классов, используя каждый из них для реализации одного и того же примера приложения. Это приложение взаимодействует с интернет-службой для получения информации о землетрясениях, произошедших в определенном географическом регионе. Чтобы заставить это приложение работать, код должен создать http-запрос, отправить его на сервер, получить результаты и затем отобразить эти результаты.
Для реализации этого, мы будем использовать три класса, это класс сокетов (Socket), класс HttpURLConnection и AndroidHttpClient.
Если запустить приложение, использующее класс socket, первоначально мы увидим одну кнопку с надписью «Load Data». И, если нажать эту кнопку, приложение отправит запрос HTTP GET на внешний сервер, и этот сервер ответит сложным текстом, содержащим запрошенные данные о землетрясениях.

Давайте рассмотрим исходный код, чтобы узнать, что нужно для получения этих данных. Откроем основную Activity этого приложения, и здесь мы видим «слушателя» кнопки загрузки данных. Когда эта кнопка нажата, приложение создает и затем выполняет AsyncTask с именем HttpGetTask.

Класс HttpGetTask сначала объявляет некоторые переменные, которые используются при создании запроса HttpGet.




Первым вызывается метод doInBackground. Этот метод создает новый сокет, который будет соединен с хостом api.geonames.org по стандартному http-порту: 80.

Затем код получает исходящий поток сокета, записывает в него http_get_command, и эта строка будет отправлена хосту, который интерпретирует его как запрос HTTP GET, а затем ответит, отправив обратно соответствующие данные ответа. Далее код получает входящий поток сокета, передавая его методу readStream, который считывает данные ответа из входящего потока сокета, и возвращает ответ в виде строки.
Затем эта строка передается методу onPostExecute, который выполняется в главном потоке, и который отображает ответ в текстовой вью.


Вернемся к приложению. Текст ответа включает в себя не только данные землетрясения, но и заголовки ответа HTTP. Но вы, возможно, не хотели бы показывать здесь этот избыточный текст, а хотели бы вывести на экран только данные землетрясения. Поэтому вы должны будете проанализировать ответ и извлечь только те данные, которые хотели бы вывести на экран. Кроме того, здесь отсутствует код обработки ошибок, который сделал бы это приложение более надежным. Использование сокетов – это компромиссный вариант, сходный с программированием низкого уровня. Вы можете записать в сокет все, что хотите, но взамен вы должны будете обрабатывать все многочисленные детали создания HTTP-запросов, прописать всю обработку ошибок и всю обработку HTTP-ответов.
В следующей реализации используется класс HttpURLConnection. Этот класс предоставляет интерфейс более высокого уровня, который обрабатывает больше деталей сетевого соединения, чем класс сокетов, но он также имеет менее гибкий API, чем другой вариант – класс Android HTTP client. Также необходимо отметить, что команда Android больше не работает над клиентом Android HTTP, отложив улучшения этого класса на будущее.
Итак, давайте рассмотрим пример приложения, реализованного на этот раз с классом HttpURLConnection. Приложение NetworkingURL внешне выглядит и работает также как и приложение из предыдущего примера, но после нажатия кнопки «Load Data» в текстовом выводе отсутствуют Http заголовки, они были удалены.
Давайте рассмотрим исходный код и посмотрим, как это работает. Откроем основную Activity этого приложения, и здесь мы увидим «слушателя» кнопки загрузки данных. Как и ранее, при нажатии этой кнопки приложение создаст и выполнит AsynchTask с именем httpGetTask.

Давайте рассмотрим этот класс. Когда в HttpGetTask срабатывает метод execute, вызывается метод doInBackground. Этот метод начинается с создания нового объекта URL и передачи строки URL-адреса для требуемой службы в качестве параметра.


Затем код вызывает метод openConnection для объекта URL, который возвращает Http соединение. Этот объект затем сохраняется в переменной httpUrlConnection.
Код продолжается, получая поток входящих данных (input stream) Http-соединения и передавая его в метод readStream. А метод readStream считывает данные ответа из сокета входящего потока, а затем возвращает ответ в виде строки. Однако на этот раз httpUrlConnection лишает заголовков http-ответ и обрабатывает проверку ошибок.
Далее эта строка передается методу onPostExecute, который выводит на экран ответ в текстовую вью.

Третий класс – AndroidHTTPClient. Этот класс является реализацией DefaultHttpClient проекта Apache. Он позволяет многое настроить. В частности, класс разбивает транзакцию HTTP на объект запроса и объект ответа. Это дает возможность создавать подклассы, которые настраивают обработку запросов и их ответов.
Пример приложения выглядит так же, поэтому перейдем прямо к коду и посмотрим на реализацию в приложении NetworkingAndroidHttpClient.

Откроем основную Activity этого приложения и перейдем к классу HttpGetTask. Этот класс начинается с создания нового объекта AndroidHttpClient, вызывая метод newInstance.


Далее вызывается метод doInBackground, код создает объект HttpGet, передавая в запросе URL-строку.
Затем создается объект ResponseHandler – обработчик ответа. В этом случае для запроса HttpGet обработчик ответа имеет тип BasicResponseHandler, который возвращает тело ответа.

И, наконец, запрос и обработчик ответа передаются в метод execute, который отправляет запрос, получает ответ, передавая его через обработчик ответа. Результат всего этого затем передается в onPostExecute, который выводит ответ в текстовую вью.

До сих пор примеры приложения запрашивали данные, а затем просто отображали эти данные в текстовой вью в том виде, в котором получили. Но эти данные имеют сложный формат и неудобны для восприятия человеку, поэтому нуждаются в дополнительной машинной обработке.
По сути, это все более популярный способ передачи текстовых данных через интернет, и многие веб-сервисы сейчас предоставляют данные в таких форматах. В частности, два формата, о которых мы будем говорить, это JavaScript Object Notation – JSON и Extensible Markup Language – XML. Рассмотрим каждый из них по отдельности.
Первый формат, это JavaScript Object Notation – JSON. Этот формат предназначен для небольших объемов и напоминает структуры данных, встречающиеся в традиционных языках программирования. Данные JSON упаковываются в два типа структур данных. Один – карты, которые являются наборами пар ключ – значение, и два – упорядоченные списки.
Теперь вернемся к примеру приложения, которое обращается к веб-службе за информацией о землетрясениях. Ответ, который вернулся, фактически был отформатирован в JSON.

Вот эти данные, давайте разобьем их на части.

Во-первых, данные содержат один объект JSON, и этот объект является картой, которая имеет одну пару ключ – значение. Ключ называется «earthquakes», а значение – это упорядоченный список. Этот список имеет несколько объектов, и каждый из этих объектов сам является картой, содержащей пары ключ – значение.
Например. Есть ключ под названием «eqid» и его значением является id землетрясения. Далее идет ключ «magnitude» с некоторым числовым значением. Затем ключ «lng», его значение – это долгота, на которой произошло землетрясение. Кроме того, есть множество других ключей, и все вместе эти значения являются данными для одного землетрясения.
Давайте посмотрим на пример приложения, которое получает эти данные из интернета, а затем обрабатывает их так, чтобы создать более читабельный для пользователя вид. Запустим приложение NetworkingAndroidHttpClientJSON. Как и ранее, это приложение первоначально отображает единственную кнопку с пометкой «Load Data» и, как и ранее, при нажатии этой кнопки приложение отправляет HTTP-запрос на внешний сервер, и этот сервер будет отвечать сложным текстом, содержащим запрошенные данные о землетрясениях. Однако на этот раз данные будут обработаны и представлены в виде списка.

Давайте рассмотрим исходный код, чтобы увидеть, как это работает. Сразу переходим к классу HttpGetTask.





Здесь метод doInBackground аналогичен тому, что мы видели раньше, но на этот раз он использует класс JSONResponseHandler для обработки ответа. Ключевым методом в этом классе является метод handleResponse. Этот метод начинается с передачи необработанного ответа через базовый обработчик ответа – BasicResponseHandler, который просто возвращает тело ответа без заголовков ответа HTTP.

Затем код использует JSONTokener для разбора JSON-ответа в объект Java, чтобы затем возвратить этот объекта верхнего уровня, который в данном случае является картой.

Затем код извлекает значение, связанное с ключом землетрясения. В этом случае это упорядоченный список.

Затем код перебирает список землетрясений. И для каждого элемента этого списка он получает данные, связанные с одним землетрясением, и эти данные сохранены в картах.

Затем код суммирует различные части данных землетрясения, преобразуя их в одну строку и добавляя эту строку в список с именем result.