Работа с сетью
Одной из определяющих характеристик современных мобильных устройств является то, что они могут обеспечить нас связью и сетевым подключением, то есть он-лайн, не привязывая нас к определенному местоположению. Смартфоны и планшеты комбинируют мощные процессоры с быстрым сетевым соединением по 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.
И затем, наконец, результат возвращается обратно вызывающему методу.
Второй формат данных, который мы рассмотрим – это Extensible Markup Language – XML. XML – это язык разметки для создания XML-документов. XML-документы содержат разметку и контент. Разметка кодирует описание структуры хранения в документе и логической структуры при помощи тегов и атрибутов. Контент – это все остальное. И, в частности, контент содержит данные ответа, когда XML используется для кодирования ответа HTTP.
Теперь вернемся к примеру приложения. Если мы зададим немного другой URL, то веб-сервис вернет данные землетрясения в формате XML, а не в формате JSON. Итак, вот эти данные.
Вначале есть элемент – тег, называемый geonames. В этот элемент вложен ряд элементов землетрясения и каждый из элементов землетрясения содержит другие элементы, которые обеспечивают данные для одного землетрясения.
Подобно тому, что мы видели в формате JSON, есть элемент eqid, его значение является идентификатором землетрясения. Есть также элемент lng, его значение – долгота, на которой землетрясение произошло, и точно так же, как в примере JSON, есть и множество других элементов.
Таким образом, если приложение получает XML-данные из интернета, ему нужно будет разобрать XML-документ, чтобы создать список для вывода на экран. Для разбора XML-документов Android предоставляет несколько различных типов парсеров XML.
Парсер DOM – Document Object Model (объектный). Парсеры DOM читают весь XML-документ и преобразуют его в структуру объектной модели документа – дерево, а затем приложение обрабатывает эту древовидную структуру. Этот парсер требует больше памяти, но позволяет приложению делать многопроходную обработку документа.
SAX – Simple API for XML (событийные) парсеры читают XML-документ как поток. И когда они сталкиваются с новым тегом в документе, они производят возврат в приложение, которое и обрабатывает информацию в этом теге. Эти парсеры используют меньше памяти, чем DOM-парсеры, но они ограничены выполнением обработки за один проход документа.
Pull-парсеры, так же как и SAX-парсеры, читают документ как поток, но используют подход, основанный на итераторах, где приложение, а не парсер, решает, когда следует переходить к следующему шагу синтаксического анализа. Pull-парсеры также используют меньше памяти, чем DOM, но они в дополнение дают приложению больший контроль над процессом синтаксического анализа, чем SAX-парсеры.
Пример приложения выглядит точно так же, как тот, который мы рассмотрели при разборе ответов JSON. Поэтому перейдем к исходному коду этого приложения.
Рассмотрим сразу класс HTTPGetTask. Метод doInBackground похож на тот, что мы видели ранее. Но теперь он использует класс XML response handler для обработки ответа. Ключевым методом в этом классе является метод handleResponse, он начинается с создания объекта PullParser.
Затем код устанавливает вход парсера в качестве XML-документа, который был возвращен телом HTTP-ответа. После этого код получает первое событие парсера и затем начинает перебирать XML-документ.
Внутри цикла while есть 3 события, наличие которых этот код проверяет: стартовый XML-тег, конечный XML-тег и содержимое элемента. После определения какое событие наступило, происходит вызов соответствующего метода.
Вызывается метод startTag, в качестве параметра получая элемент, который начинается. Этот метод идентифицирует, является ли полученный элемент данных тем, который необходимо сохранить, и если это так, он сохраняет его, задавая значения определенным переменным.
Метод endTag получает в качестве параметра заканчивающий элемент. И этот метод так же определяет, является ли полученный элемент данных тем, который должен быть сохранен, и если это так, он его сохраняет. Кроме того, если это конечный тег землетрясения, тогда в список результатов добавляется результирующая строка для этой части данных землетрясения.
Когда событие является контентом, вызывается метод text. В качестве параметра передается содержимое элемента. Этот метод определяет, какой тег в настоящее время обрабатывается, и сохраняет содержимое для последующего использования.
После завершения метода doInBackground в основной Activity, как и в предыдущих примерах, вызывается метод onPostExecute c результатом, переданным в качестве параметра.
Этот метод создает и устанавливает адаптер списка для вывода на экран, передавая в качестве параметра результирующий список, который был вычислен при работе метода handleResponse.
И последнее. Чтобы ваше приложение могло работать с интернетом, ему необходимо предоставить соответствующее разрешение: