Предыстория
Мотивом написания данного приложения послужила курсовая работа по дисциплине «Компьютерные системы и сети». Честно говоря, эта одна из самых мной нелюбимых сторон в компьютерных технологиях, и я решил «подстраивать» курсовой проект под свои интересы, а именно, под Андроид-разработку.
Было решено создать библиотеку для соединения Андроид-устройств по средством Wi-Fi Direct технологии и передачи данных между ними (Wi-Fi Peer-to-Peer соединение осуществляется как раз с помощью технологии Wi-Fi Direct).
Почему Wi-Fi Direct?
После одобрения идеи о локальном соединении устройств передо мной встал вопрос: С помощью какой технологии собираюсь я это осуществить? Рассматривались следующие варианты:
- Bluetooth
- Wi-Fi Hotspot
- Wi-Fi Direct или Wi-Fi Peer-to-Peer
Bluetooth
Основными недостатками данного подхода являются ограничение пользователей в одной сети и малый радиус действия. Я же хочу создать группу устройств, общающихся между собой на расстоянии не менее 10 метров.
Wi-Fi Hotspot
Создание точки доступа является не плохим вариантом, однако меня смущает ситуация с потерей основного Wi-Fi соединения. Я бы хотел, чтобы пользователи могли находится в локальной сети, и одновременно иметь доступ к глобальной, например быть подключенными к своему домашнему роутеру.
Однако с помощью Wi-Fi Hotspot можно добиться максимальной скорости передачи данных (приложения Portal, SHAREit).
Wi-Fi Direct или Wi-Fi Peer-to-Peer
Данный подход решает все вышеупомянутые проблемы:
- неограниченное кол-во клиентов (если не так, прошу меня поправить)
- большой радиус действия
- соединение с устройствами по средством Wi-Fi без создания точки доступа
- технология поддерживается с API 14 (Android 4.0)
Но сразу закрадывается сомнение, мол Wi-Fi Peer-to-Peer, тогда как мы собираемся создать группу с владельцем и клентами, и тем более, чтобы все друг с другом общались. Это как раз-таки и является основной причиной написания данной статьи.
Wi-Fi Aware
Этот вариант не упоминал в основном списке, т.к. не считаю, что он может полноценно подойти для выполнения поставленной задачи. Однако Wi-Fi Aware является сравнительно молодой технологией для рассылки сообщений в определенном радиусе действия.
Начнем с проблем
Скажу честно, предоставляемая документация by developer.android.com давалась мне нелегко. И я начал искать сэмплы. Перебрав кучу материала, наткнулся на наиболее интересный sample by Google.
Никогда не думал, что на официальном сайте от Google можно найти говнокод, но это как раз тот случай. Однако даже этому стоит отдать должное, так как благодаря данному ресурсу я реализовал то, что хотел.
В сэмпле продемонстрирован пример чата двух устройств, которые нашли друг друга в списке, отображающем рядом находящиеся Wi-Fi Direct устройства с запущенным приложением чата. При выборе партнера непосредственно открывается ChatActivity.
Очень важным моментом является поиск собеседников: в списке не будут отображаться такие Wi-Fi Direct устройства, как телевизор или какая-нибудь гарнитура. Отображаются только устройства с запущенным Chat-приложением.
Также присутствовала проблема с тестированием. Тестировать работоспособность приложения необходимо было исключительно на реальных устройствах.
Что было реализовано для демонстрации работоспособности библиотеки
Приложение представляет следующий функционал:
- выбор роли в процессе работы с приложением (для примера были созданы «Мафия» и «Мирный житель»)
- создание группы
- присоединение к существующей группе
- общение между устройствами (при нажатии на кнопку меняется ее цвет как у инициатора, так и у всех остальных, соответствующих текущей роли (либо broadcast))
Описание экранов слева-направо:
- стартовый экран с выбором действия и роли
- список присоединившихся устройств к текущему владельцу группы
- список активных групп, к которым можно присоединиться
- сеттинги приложения (логин устройства, название группы, пароль для вхождения в группу)
- «игровой» экран
После выбора пользователем, в какую группу присоединиться, владельцу группы приходит сообщение об установлении связи с данным устройством.
Основная концепция работы библиотеки
Логика построена на основе архитектуры клиент-сервер. Сервером является владелец группы, клиентами — подсоединившиеся пользователи.
В приложении присутствуют собственные Serializer/Deserializer для преобразования передаваемых данных в массив байтов и обратно. В передаваемых данных хранится следующая информация:
- кому отправить (мафии, мирным жителям, всем)
- какого рода данные (например: изменение цвета кнопки)
- сами данные (сериализованный Object)
Первый пункт «кому отправить» не совсем корректен, т.к. отправляются данные всем. Сначала серверу, потом сервер отправляет этот пакет всем клиентам. А вот клиент уже сам решает, стоит ему обрабатывать эти данные или нет. Сделано это по той простой причине, что сервер не знает ролей его клиентов. Таким образом было достигнуто равноправие в группе. Владелец отличается от всех остальных только тем, что его обременили заниматься передачей данных всем его клиентам.
Компоненты библиотеки
- WifiDirectManager — основной класс библиотеки; отвечает за создание группы, поиск устройств, присоединение устройства в группу или приглашение в нее, отправка пакета данных всем участникам группы
- WifiP2pDeviceObservable — реализация паттерна Observable; при нахождении Wi-Fi Direct устройства оповещает наблюдателя
- WifiDirectBroadcastReceiver занимается оповещением приложения об изменениях Wi-Fi Direct соединения (например: удачно ли прошло соединение с другим устройством), состояния Wi-Fi на устройстве (например отключение должно последовать за собой предупреждение пользователя о невозможном продолжении работы приложения)
- MessageShaper отвечает за сериализацию/десериализацию передаваемых данных; при десериализации на выходе получаем объект типа android.os.Message (obj — объект, what — для каких целей объект (например: поменять цвет кнопки), arg1 — тип доступа (для мафии, мирных жителей, либо broadcast))
- Serializer непосредственно занимается сериализацией/десериализацией объектов
- Status — характеризует каждое устройство как владельца группы, либо клиента
- ChatNeedle хранит в себе сокет, по которому связаны два устройства между собой; обрабатывает input/output стримы данных; у клиента есть лишь один ChatNeedle — с владельцем, у владельца же их кол-во равняется кол-ву клиентов
- Member — класс, описывающий участника группы (не себя самого), для дальнейшего использования в игровом сценарии; хранит в себе ChatNeedle для соединения с этим участником группы
- MemberList — класс, хранящий в себе все устройства, с которыми связан наш девайс; в случае с владельцем группы — все клиенты, в случае клиента — владелец
- GroupOwnerSocketHandler применяется владельцем группы для открытия определенного кол-ва сокетов, к которым подсоединяются клиенты
- ClientSocketHandler используется для установления соединения клиента с владельцем группы
Самая сложная проблема во время разработки
Когда проект дошел до стадии тестирования мультиплеера (> 2 устройств), тут-то и началась жара. Два устройства как и прежде у меня соединялись без проблем (для чего впрочем и создан Wi-Fi Peer-to-Peer), однако при подсоединении третьего звена почти всегда происходил треш. При нажатии на кнопку у «владельца» (позже поймете, почему в кавычках) цвет менялся только у того клиента, с которым «владелец» последний раз общался.
После долгих и упорных часов раздумий я решил соединить устройства через опцию Wi-Fi Direct в настройках Wi-Fi каждого, и заметил вещь, которая перевернула мое сознание и добавила всего одну строчку в код метода для установки соединения.
До этого я считал, что тот девайс, который подсоединяется к другому, всегда будет клиентом… а это далеко не так. При соединении устройств рандомно передавались права владельца одному из них, что и побудило меня на поиск настройки config'a, который бы исправил эту ситуацию, и сделал подсоединяемое устройство клиентом.
При соединении двух устройств прописывается специальный config, который в себе содержит:
- адрес устройства, к которому мы хотим присоединиться
- wps.setup позволяет установить, как мы будем соединяться: с паролем или просто по нажатию кнопки
- groupOwnerIntent — вот он (споймал!), тот, кто решил мою проблему; целочисленное поле, значение которого варьируется от 0 до 15 (0 — хотим быть клиентом, 15 — хотим быть владельцем); по умолчанию -1, что дает системе право самой выбрать роль в этой связи
final WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = deviceAddress;
config.wps.setup = WpsInfo.PBC;
config.groupOwnerIntent = status.getIntent();
mWifiP2pManager.connect(mWifiP2pManagerChannel, config, null);
Status'ом (enum) выше является текущее положение устройства, если он создатель группы — GroupOwner, иначе — Client.
Необходимые доработки
Пока не существует решения по проблеме восстановления соединения. Например, при выходе из приложения, например, чтобы совершить звонок, после возвращения нас ждет… ничего. После дестроя активности необходимо опять подтягивать клиентов себе, а клиентам — владельца.
Также хочется ввести понятие по передаче прав владельца одному из клиентов на тот случай, если владельцу группы необходимо покинуть игру.
Защита. Для входа в определенную группу было бы неплохо реализовать авторизацию.
Заключение
Не думаю, что я реализовал что-то новое или революционное, однако данный проект послужил хорошим примером неординарного использования Wi-Fi Peer-to-Peer соединения. Мною была успешно построена сеть между устройствами с помощью Wi-Fi Direct, что позволило беспрепятственно общаться между ними.
Это моя первая статья, поэтому судить строго. Спасибо за внимание.
→ Ссылка на GitHub
(модуль wifidirect может быть без проблем перенесен в любой андроид-проект)
Автор: chelsenok