- Использование сокетов в Android
- Особенности использования сокетов
- Клиентский android-сокет
- Класс Connection
- Листинг Connection
- Класс активности MainActivity
- Листинг активности
- Методы управления сокетным соединением
- Серверное приложение
- Листинг ConnectionWorker
- Серверный класс
- Листинг CallableDelay
- Листинг CallableServer
- Листинг серверного класса Server
- Простой клиент-сервер на Android (интернет-мессенджер)
- Делаем сервер
- Клиентская часть
- Как в Android получить данные с сервера с помощью Retrofit
Использование сокетов в Android
Создано большое количество приложений как для Android, так и для других ОС, которые взаимодействуют друг с другом с помощью установления соединенией по сети. К таким приложениям относятся, например, мессенджеры социальных сетей WhatsApp, Viber. Как правило, для установления соединения между такими приложениями используются сокеты.
Сокет (socket) — это интерфейс, позволяющий связывать между собой программы различных устройств, находящихся в одной сети. Сокеты бывают двух типов: клиентский (Socket) и серверный (ServerSocket). Главное различие между ними связано с тем, что сервер «открывает» определенный порт на устройстве, «слушает» его и обрабатывает поступающие запросы, а клиент должен подключиться к этому серверу, зная его IP-адрес и порт. В Android сокеты для передачи данных используют по умолчанию протокол TCP/IP, важной особенностью которого является гарантированная доставка пакетов с данными от одного устройства до другого.
Особенности использования сокетов
Что важно знать при использовании сокетов в Android ?
- соединения сокетов отключаются при переходе устройства в спящий режим;
- чтобы не «рвать» соединение при наступлении спящего режима в устройстве можно использовать сервис;
- для использования интернет-сети необходимо Android-приложению предоставить нужные права в манифесте.
Для определения прав в манифесте необходимо в файл AndroidManifest.xml добавить следующую строку :
Теперь android-приложения будет иметь доступ к сети.
Далее в статье рассмотрим пример клиент-серверного сокетного соединения с передачей сообщения. Функции клиента будет выполнять android-приложение. Серверное java-приложение выполним в IDE Eclipse с использованием пакета concurrent. В конце страницы можно скачать оба приложения.
Клиентский android-сокет
Интерфейс andriod-приложения представлен на следующем скриншоте. Форма приложения включает поле ввода текстового сообщения и кнопки установления соединения сервером, передачи сообщения и закрытия соединения.
Клиентское приложение создадим из двух классов : класс взаимодействия с серверным сокетом Connection и класс стандартной активности MainActivity.
Класс Connection
Класс взаимодействия с сервером Connection получает при создании (через конструктор) параметры подключения : host и port. Методы Connection вызываются из активности и выполняют следующие функции :
Метод | Описание |
---|---|
openConnection | Метод открытия сокета/соединения. Если сокет открыт, то он сначала закрывается. |
closeConnection | Метод закрытия сокета |
sendData | Метод отправки сообщения из активности. |
finalize | Метод освобождения ресурсов |
Листинг Connection
Класс активности MainActivity
В активности MainActivity определены параметры сервера : host, port. Помните, что IP-адрес сервера для Вашего android-примера не может быть localhost (127.0.0.1), иначе Вы будете пытаться связаться с сервером внутри Andriod-системы. Кнопки интерфейса связаны с методами обращения к классу Connection. Кнопки отправки сообщения mBtnSend и закрытия соединения mBtnClose с сервером блокируются при старте приложения. После установления соединения с сервером доступ к кнопкам открывается.
Листинг активности
Методы управления сокетным соединением
Ниже представлены методы обработки событий, связанных с нажатием кнопок интерфейса. Обратите внимание, что подключение к серверу выполняется в отдельном потоке, а открытие доступа к кнопкам в основном потоке, для чего вызывается метод runOnUiThread. Для отправки сообщения серверу также создается отдельный поток.
Серверное приложение
Серверное приложение включает 2 класса : Server и ConnectionWorker. Серверный класс Server будет выполнять обработку взаимодействия с клиентом с использованием ConnectionWorker в отдельном потоке. Конструктор ConnectionWorker в качестве параметра получает объект типа Socket для чтения сообщений клиента из потока сокета.
Листинг ConnectionWorker
ConnectionWorker получает входной поток inputStream из клиентского сокета и читает сообщение. Если сообщение отсутствует, т.е. количество прочитанных байт равно -1, то это значит, что соединение разорвано, то клиентский сокет закрывается. При закрытии клиентского соединения входной поток сокета также закрывается.
Серверный класс
Серверный класс Server создадим с использованием многопоточного пакета util.concurrent. На странице описания сетевого пакета java.net и серверного ServerSocket был приведен пример серверного модуля с использованием обычного потока Thread, при работе с которым необходимо решать задачу его остановки : cтарый метод Thread.stop объявлен Deprecated и предан строжайшей анафеме, а безопасная инструкция Thread.interrupt безопасна, к сожалению, потому, что ровным счетом ничего не делает (отправляет сообщение потоку : «Пожалуйста, остановись»). Услышит ли данный призыв поток остается под вопросом – все зависит от разаработчика.
Чтобы иметь возможность остановить сервер «снаружи» в серверный класс Server включим 2 внутренних реализующих интерфейс Callable класса : CallableDelay и CallableServer. Класс CallableDelay будет функционировать определенное время, по истечении которого завершит свою работу и остановит 2-ой серверный поток взаимодействия с клиентами. В данном примере CallableDelay используется только для демонстрации остановки потока, организуемого пакетом util.concurrent.
Листинг CallableDelay
CallableDelay организует цикл с задержками. После завершения последнего цикла cycle поток завершает цикл, останавливает вторую задачу futureTask[1] и закрывает сокет. В консоль выводится соответствующее сообщение.
Листинг CallableServer
Конструктор CallableServer в качестве параметров получает значение открываемого порта для подключения клиентов. При старте (метод call) создается серверный сокет ServerSocket и поток переходит в режим ожидания соединения с клиентом. Остановить поток можно вызовом метода stopTask, либо завершением «задачи» типа FutureTask с данным потоком.
При подключении клиента метод serverSoket.accept возвращает сокет, который используется для создания объекта ConnectionWorker и его запуска в отдельном потоке. А сервер (поток) переходит к ожиданию следующего подключения.
В случае закрытия сокета (завершение внешней задачи FutureTask с данным потоком) будет вызвано исключение Exception, где выполняется проверка закрытия сокета; при положительном ответе основной цикл прерывается и поток завершает свою работу.
Листинг серверного класса Server
Cерверный класс Server создает два потоковых объекта (callable1, callable2), формирует из них две задачи futureTask и запускает задачи на выполнение методом execute исполнителя executor. После этого контролируется завершение выполнение обоих задач методом isTasksDone. При завершении выполнения обеих задач завершается также и цикл работы executor’а.
Два внутренних описанных выше класса (CallableDelay, CallableServer) не включены в листинг.
Источник
Простой клиент-сервер на Android (интернет-мессенджер)
Важно. Все написанное ниже не представляет собой какой либо ценности для профессионалов, но может служит полезным примером для начинающих Android разработчиков! В коде старался все действия комментировать и логировать.
Поехали. Многие мобильные приложения (и не только) используют архитектуру клиент-сервер. Общая схема, думаю, понятна.
Уделим внимание каждому элементу и отметим:
- сервер — представляет собой некую программу, работающую на удаленном компьютере, и реализующую функционал «общения» с приложениями-клиентами (слушает запросы, распознает переданные параметры и значения, корректно отвечает на них);
- клиент — в нашем случае, программа на мобильном устройстве, которая умеет формировать понятный серверу запрос и читать полученный ответ;
- интерфейс взаимодействия — некий формат и способ передачи/получения запросов/ответов обеими сторонами.
Неважно, как реализован любой из этих элементов, все они в любом случае присутствуют. Давайте реализуем примитивный сервер и Android клиент, работающий с ним. Как пример, будем использовать любой популярный мобильный интернет-мессенджер (Viber, ICQ), а приложение условно назовем «интернет-чат».
Схема взаимодействия следующая:
Клиент, установленный на устройстве А, посылает сообщение для клиента, установленного на устройстве Б. И наоборот. Сервер играет роль связующего звена между устройством А и Б… С, Д… и т.д. Также он играет роль «накопителя» сообщений, для их восстановления, на случай удаления на одном из клиентских устройств.
Для хранения сообщений используем SQL БД как на сервере, так и на устройствах-клиентах (в принципе, вся работа клиентов интернет-мессенджеров и сводится к постоянной синхронизации локальной и удаленной БД с сообщениями). Дополнительно, наш интернет-чат будет уметь стартовать вместе с запуском устройства и работать в фоне. Взаимодействие будет происходить путем HTTP запросов и JSON ответов.
Более логично, если синхронизация происходит через порт/сокет, это с одной стороны упрощает задачу (не нужно циклично слать HTTP запросы на проверку новых сообщений, достаточно проверять состояние прослушиваемого сокета), но с другой стороны, это усложняет создание серверной части приложения.
Делаем сервер
Для реализации «сервера», нам нужно зарегистрироваться на любом хостинге, который дает возможность работы с SQL и PHP.
Создаем пустую SQL БД, в ней создаем таблицу.
- author — автор сообщения;
- client — получатель сообщения;
- data — время и дата получения сообщения на сервере;
- text — сообщение.
В двух следующих файлах необходимо изменить переменные, содержащие данные для доступа к БД, на свои, полученные Вами при регистрации Вашего«сервера».
Структура запросов к api:
- обязательный атрибут action — может быть равен select (сервер ответит списком записей из своей БД), insert (сервер добавить новую запись в свою БД), delete (сервер очистит свою БД)
- если action=insert, нам нужно будет передать дополнительные параметры: author (кто написал сообщение), client (кому адресовано сообщение), text (сообщение)
- action=select может содержать дополнительный параметр data, в этом случае ответ сервера содержит не все сообщения из БД, а только те, у которых время создания позднее переданного
Примеры:
- chat.php?action=delete – удалит все записи на сервере
- chat.php?action=insert&author=Jon&client=Smith&text=Hello — добавит на сервере новую запись: автор Jon, получатель Smith, содержание Hello
- chat.php?action=select&data=151351333 — вернет все записи, полученные после переданного времени в long формате
Клиентская часть
Теперь структура Android приложения:
В фоне работает FoneService.java, который, в отдельном потоке, каждые 15 секунд делает запрос на сервер. Если ответ сервера содержит новые сообщения, FoneService.java записывает их в локальную БД и отправляет сообщение ChatActivity.java о необходимости обновить ListView, с сообщениями. ChatActivity.java (если она в этот момент открыта) получает сообщение и обновляет содержимое ListView из локальной БД.
Отправка нового сообщения из ChatActivity.java происходит сразу на сервер, минуя FoneService.java. При этом наше сообщение НЕ записывается в локальную БД! Там оно появится только после получения его назад в виде ответа сервера. Такую реализацию я использовал в связи с важным нюансом работы любого интернет-чата — обязательной группировкой сообщений по времени. Если не использовать группировку по времени, будет нарушена последовательность сообщений. Учитывая, что клиентские приложения просто физически не могут быть синхронизированы с точностью до миллисекунд, а возможно будут работать даже в разных часовых поясах, логичнее всего будет использовать время сервера. Так мы и делаем.
Создавая новое сообщение, мы передаем запросом на сервер: имя автора сообщения, имя получателя сообщения, текст сообщения. Получая эту запись назад, в виде ответа сервера, мы получаем то, что отправляли + четвертый параметр: время получения сообщения сервером.
Источник
Как в Android получить данные с сервера с помощью Retrofit
Retrofit — популярная и удобная библиотека, которая используется, если приложению необходимо работать с сервером. В этой статье я на очень простом примере рассмотрю основы работы с ней.
Предположим, что на каком-то сервере есть данные. И сервер готов нам выдать эти данные. Чаще всего для этого используется формат json. Вот пример таких данных: https://rawgit.com/startandroid/data/master/messages/messages1.json
Давайте используем Retrofit, чтобы получить в приложении данные из этого файла.
Для начала немного теории.
Конфигурирование Retrofit можно разделить на две части: API интерфейс и билдер. Давайте рассмотрим подробно, кто из них за что отвечает.
Представим себе работу по обмену данными приложения с сервером. Как это происходит? Обычно сервер предоставляет нам какое-то API, т.е. набор методов (он же REST). Если, например, это сервер интернет-магазина, то его API может содержать следующие методы:
getProducts — получить список товаров
getProdut — получить детальные данные о продукте
getOrders — получить список заказов
getOrder — получить детальные данные о заказе
createOrder — создать заказ
и т.д.
Это серверные методы и, чтобы их вызывать, нам необходимо выполнять запросы, которые могут выглядеть, например, так:
http://server/api/v1/getProducts
http://server/api/v1/getProduct
http://server/api/v1/getOrders
http://server/api/v1/getOrder
http://server/api/v1/createOrder
и т.д.
Т.е. у сервера есть какой-то базовый URL — http://server/api/v1/. И к нему просто добавляются имена методов (getProducts, getOrders, и т.д.), чтобы получать ссылки для работы с API методами.
Возвращаемся в приложение. Было бы очень удобно, если бы у нас в приложении был класс ServerApi с методами getProducts, getOrders и т.д. И при вызове этих методов происходил бы вызов соответствующих серверных методов. И Retrofit как раз может создать для нас такой класс. От нас потребуется интерфейс, в котором мы распишем необходимые методы.
Вот пример такого интерфейса:
Этот код немного упрощен. В реальном примере будут дополнительные аннотации и обертки. Но общий смысл он вполне передает. Retrofit создаст класс, который реализует этот интерфейс. Внутри этого класса и будут сгенерированы вызовы к серверу.
А мы в этом интерфейсе можем настроить названия методов и некоторые HTTP штуки, такие как передаваемые параметры (Query), заголовки (Header) и т.п.
Рассмотрим описание метода getProducts подробнее:
Аннотация GET означает, что HTTP-запрос для getProducts должен быть типа GET. А в качестве параметра для этой аннотации нам необходимо указать имя метода на сервере. Обратите внимание, что здесь не фигурирует базовый URL (http://server/api/v1/). Его мы будем указывать позже, в билдере.
— это тип возвращаемых данных. Retrofit сам сможет сконвертировать json данные в список объектов Product. Подробнее об этом тоже поговорим во время обсуждения билдера.
Ну и имя метода — getProducts(). В этом примере оно совпадает с именем метода на сервере. Но это необязательно. Т.е. вы вполне можете сделать так
Теперь имена методов различаются. Вы в коде будете вызывать метод getProductList, а на сервере будет вызываться getProducts.
Переходим к практике. Чтобы использовать в вашем проекте Retrofit, в build.gradle файле, в секции dependencies добавляйте зависимости:
Первая строка — это непосредственно Retrofit, а вторая — это его Gson-конвертер.
Она вернет нам json данные, которые представляют из себя список сообщений. Каждое сообщение содержит поля id, time, text и иногда image. Эта ссылка, конечно, не API, а просто текстовый файл, но смысл остается тем же, просто вместо имени метода у нас будет имя файла.
Мы делим ссылку на две части:
«https://rawgit.com/startandroid/data/master/messages/» — базовый URL (будет указан в билдере)
«messages1.json» — имя файла (будет указано в интерфейсе, в GET аннотации)
Мы можем создать интерфейс для работы с этой ссылкой. Там будет только один метод, и он будет возвращать List .
Давайте сначала создадим класс Message.
Его поля соответствуют полям в данных в messages1.json.
Теперь создаем интерфейс:
В аннотации GET указываем, что к базовому URL надо будет добавить «messages1.json», чтобы получилась нужная нам ссылка.
Обратите внимание на Call. Эта обертка нужна для работы Retrofit. В ней мы указываем, какой тип данных ожидаем получить из messages1.json — т.е. List .
Интерфейс создан, теперь переходим к билдеру:
В билдере мы указываем базовый URL и добавляем Gson конвертер, чтобы Retrofit сам смог сконвертировать json данные в объекты Message. Это минимум настроек, который от нас требуется для нашего простого примера. Для сложных случаев количество параметров может быть больше.
В итоге у нас есть объект Retrofit, который содержит базовый URL и способность преобразовывать json данные с помощью Gson. Мы передаем ему в метод create класс интерфейса, в котором описывали методы.
И получаем реализацию MessagesApi от Retrofit. В этой реализации соединены настройки билдера (базовый URL и Gson конвертер) и описания методов из интерфейса (метод messages для получения файла messages1.json). В итоге Retrofit будет брать базовый URL из билдера, присоединять к нему остаток пути, который мы указываем в GET в интерфейсе, и тем самым получит полную ссылку.
Возможно, возникает вопрос, зачем было разделять функционал на билдер и интерфейс. Я могу привести вам пару примеров из своей практики.
1) У интернет-магазина есть два сервера: рабочий (с реальными данными) и тестовый. На обоих развернуто одинаковое API. И есть приложение, которое общается с сервером. Debug-версия приложения должна работать с тестовым сервером и выводить в лог всю информацию об этом. Release-версия работает с рабочим сервером и ничего не выводит в лог.
Эту разницу между Debug и Release версиями можно реализовать двумя разными билдерами. Просто указываем для них разные базовые URL и разные настройки логирования. А интерфейс в обоих случаях будет использоваться один и тот же.
2) Есть сервер с API. И есть приложение, которое вызывает эти методы. На сервере реализована авторизация. Т.е. приложение должно вызвать метод авторизации, получить токен, и затем использовать этот токен при вызове некоторых API методов, которые связаны с пользователем.
В билдере есть возможность использовать специальный обработчик, который будет добавлять токен ко всем вызовам. Но нам надо добавлять токен не ко всем, а только к некоторым вызовам.
В этом случае можно создать два билдера. Первый не будет использовать токен. Реализацию, полученную от этого билдера используем для работы с методами, не требующими токена. А второй билдер будет добавлять токен ко всем вызовам. Реализацию, полученную от этого билдера используем для всех методов, где нужен токен.
Итак, связка интерфейса и билдера дала нам реализацию MessagesApi. Используем ее для получения данных. Вызываем метод messages()
Метод log — это просто вывод в лог текста
Вызов метода messages вернет нам Call объект. При выполнении этого кода запрос к серверу еще не был отправлен. Call дает нам возможность отправить запрос синхронно (блокируя текущий поток) или асинхронно (с колбэком). Мы выбираем асинхронный вариант: вызываем метод enqueue и создаем для него Callback. Запрос будет выполнен в отдельном потоке, а результат придет в Callback в main потоке.
Если взаимодействие с сервером пройдет успешно, то мы получим результат в метод onResponse в объекте Response. Чтобы добраться до данных, необходимо вызвать метод body() у Response объекта. Это даст нам список сообщений — List . Выводим в лог количество записей в этом списке.
Запустив этот код, мы получим список из 50 записей. Лог покажет следующее:
Если по каким-то причинам не удалось достучаться до сервера, то вместо onResponse, будет вызван метод onFailure. Добавим вывод в лог ошибки, которую вернет нам Retrofit
Чтобы эмулировать ошибку, мы можем просто поломать имя сервера в базовом URL, который задается в билдере. Например, добавим букву r к rawgit:
https://rrawgit.com/startandroid/data/master/messages/
Запускаем и видим в логе ошибку.
failure java.net.UnknownHostException: Unable to resolve host «rrawgit.com»: No address associated with hostname
Retrofit сообщает, что не нашел сервер rrawgit.com
Если случится какая-то ошибка в процессе работы Retrofit, то она попадет также в onFailure. Например, если сервер вернет некорректные данные, то GsonConverterFactory не сможет их распарсить и выкинет ошибку. А Retrofit отправит ее нам в onFailure.
Есть тип ошибок, который Retrofit умеет обрабатывать без вызова onFailure. Это HTTP-ошибки сервера. Самый простой случай — ошибка 404. Запрос дойдет до сервера, но сервер вместо данных вернет HTTP 404 Not found. Казалось бы — это ошибка и на должна прийти в onFailure. Но нет. onFailure вызывается в случае, когда не удалось достучаться до сервера. А здесь с сервером все ок, он просто вместо данных вернул нам информацию о том, что мы странные и хотим непонятного.
В итоге, ответ сервера мы получим в методе onResponse. И нам надо как-то различать, вернул сервер данные или ошибку. Для этого у объекта Response есть метод isSuccessful.
Перепишем метод onResponse
Если запрос был выполнен успешно (isSuccessfull), то выводим в лог количество полученных записей. Иначе выводим в лог код ошибки.
Снова поменяем базовый URL, чтобы спровоцировать ошибку 404. Добавим лишнюю букву s в слово messages:
https://rawgit.com/startandroid/data/master/messagess/
Запускаем и в логе видим
response code 404
Чтобы получить полный текст ошибки сервера, используйте
Приведу весь код работы с Retrofit:
Разумеется, вам не нужно при каждом запросе к серверу использовать билдер и создавать новый объект MessagesApi. Это делается один раз за время работы приложения и далее объект MessagesApi используется везде, где необходима работа с сервером. Для реализации Singleton подойдет Dagger или любая другая реализация этого паттерна.
На этом пока все, чтобы не перегружать вас информацией. Продолжение в следующем посте.
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник