Предположим, мы пишем игру для Android, которая подразумевает некое сетевое взаимодействие между устройствами. Причем наши устройства находятся в одной сети и мы хотим, чтобы взаимодействие между ними осуществлялось быстро, а значит вариант с обменом данными через интернет нам не подходит. Ах да, еще одна маленькая ложка дегтя — мы хотим охватить максимально возможную аудиторию, для чего нам необходимо поддерживать Android 2.3.
Что же нам делать? Давайте поговорим об этом, а заодно рассмотрим относительно новые возможности Android SDK для соединения двух и более устройств.
О чем это и для кого это?
Как-то раз, уйдя с предыдущего места работы и погрузившись в заслуженный отдых, я принялся писать сетевую игру, в которую могут играть люди, находящиеся в одной локальной сети. И сразу же столкнулся с тем, что для нормального функционирования подобной игры нам мало соорудить сетевое взаимодействие — нам нужно сделать нормальное и быстрое обнаружение устройств в сети. Собственно, в данной статье я поделюсь своим опытом в реализации решения для данной задачи.
Сразу оговорюсь, что статья предназначена в большей мере для тех, кто имеет опыт Android-разработки, написал несколько приложений и хочет расширить свой кругозор, а также улучшить профессиональные навыки.
Какие возможные способы решения существуют?
- Android Network Service Discovery. Простой и эффективный способ обнаружения устройств. На Android Developer есть пошаговое руководство по подключению NSD, есть пример NsdChat, который можно скачать там же. Но есть один существенный минус — данный метод поддерживается только начиная с API Level 16, то есть с Android 4.1 Jelly Bean;
- Второе решение, предлагаемое нам на сайте Android Developer — Wi-Fi Peer-to-Peer. Проблема этого метода та же самая — поддерживается он только начиная с API Level 16;
- Есть странное решение, которое предлагается некоторыми программистами на Stack Overflow — самостоятельно сканировать локальную сеть на предмет наличия сервера. То есть проходить по всем адресам сети. Это уже сейчас звучит как странный велосипед, а теперь представьте, что порт нашего сервера назначается автоматически. Таким образом, сканирование даже самую небольшой сети становится достаточно долгой и трудоемкой задачей;
- Наконец, мы можем обратить внимание на Java-библиотеки и написать что-нибудь с их использованием. Например, JmDNS.
Последний способ выглядит вполне адекватным и, кажется, может обеспечить нас требуемой скоростью и удобством обнаружения устройств в сети для конечного пользователя.
Итак…
Я вооружился JmDNS и решил попробовать соорудить несколько классов, которые по максимуму упростят написание описанных выше приложений. Но для начала пришлось немного повырезать дубликаты .class-файлов из jar-пакета JmDNS (проблема описана здесь):
mkdir unjar cd unjar jar xf ../jmdns.jar jar cfm ../jmdns.jar META-INF/MANIFEST.MF javax/
Далее я взял исходный код NsdChat с Android Developer и изменил его служебный класс, который отвечает за инициализацию сокетов и организацию сетевого взаимодействия. Также я написал wrapper для JmDNS
import android.content.Context; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.util.Log; import java.io.IOException; import java.net.InetAddress; import javax.jmdns.JmDNS; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceListener; /** * @author alwx * @version 1.0 */ public class NetworkDiscovery { private final String DEBUG_TAG = NetworkDiscovery.class.getName(); private final String TYPE = "_alwx._tcp.local."; private final String SERVICE_NAME = "LocalCommunication"; private Context mContext; private JmDNS mJmDNS; private ServiceInfo mServiceInfo; private ServiceListener mServiceListener; private WifiManager.MulticastLock mMulticastLock; public NetworkDiscovery(Context context) { mContext = context; try { WifiManager wifi = (WifiManager) mContext.getSystemService(android.content.Context.WIFI_SERVICE); WifiInfo wifiInfo = wifi.getConnectionInfo(); int intaddr = wifiInfo.getIpAddress(); byte[] byteaddr = new byte[]{ (byte) (intaddr & 0xff), (byte) (intaddr >> 8 & 0xff), (byte) (intaddr >> 16 & 0xff), (byte) (intaddr >> 24 & 0xff) }; InetAddress addr = InetAddress.getByAddress(byteaddr); mJmDNS = JmDNS.create(addr); } catch (IOException e) { Log.d(DEBUG_TAG, "Error in JmDNS creation: " + e); } } /** * starts server with defined names on given port * * @param port server port */ public void startServer(int port) { try { wifiLock(); mServiceInfo = ServiceInfo.create(TYPE, SERVICE_NAME, port, SERVICE_NAME); mJmDNS.registerService(mServiceInfo); } catch (IOException e) { Log.d(DEBUG_TAG, "Error in JmDNS initialization: " + e); } } /** * performs servers discovery * * @param listener listener, that will be called after successful discovery * (see {@link me.alwx.localcommunication.connection.NetworkDiscovery.OnFoundListener} */ public void findServers(final OnFoundListener listener) { mJmDNS.addServiceListener(TYPE, mServiceListener = new ServiceListener() { @Override public void serviceAdded(ServiceEvent serviceEvent) { ServiceInfo info = mJmDNS.getServiceInfo(serviceEvent.getType(), serviceEvent.getName()); listener.onFound(info); } @Override public void serviceRemoved(ServiceEvent serviceEvent) { } @Override public void serviceResolved(ServiceEvent serviceEvent) { mJmDNS.requestServiceInfo(serviceEvent.getType(), serviceEvent.getName(), 1); } }); } /** * closes connection & unregisters all services */ public void reset() { if (mJmDNS != null) { if (mServiceListener != null) { mJmDNS.removeServiceListener(TYPE, mServiceListener); mServiceListener = null; } mJmDNS.unregisterAllServices(); } if (mMulticastLock != null && mMulticastLock.isHeld()) { mMulticastLock.release(); } } /** * accuires Wi-Fi lock */ private void wifiLock() { WifiManager wifiManager = (WifiManager) mContext.getSystemService(android.content.Context.WIFI_SERVICE); mMulticastLock = wifiManager.createMulticastLock(SERVICE_NAME); mMulticastLock.setReferenceCounted(true); mMulticastLock.acquire(); } public interface OnFoundListener { void onFound(ServiceInfo info); } }
Здесь размещены 4 основные функции для работы Network Discovery:
- startServer для создания сервера и регистрации соответствующего сервиса в локальной сети;
- findServers для поиска серверов;
- reset для окончания работы с Network Discovery и последующего освобождения ресурсов;
- wifiLock для запроса блокировки Wi-Fi.
В завершении я написал универсальный класс ConnectionWrapper для полноценной организации обнаружения, а также обмена сообщениями в локальной сети. Таким образом, создание сервера в конечном приложении выглядит следующим образом:
getConnectionWrapper().startServer(); getConnectionWrapper().setHandler(mServerHandler);
А вот и mServerHandler, использующийся для приема и обработки сообщений:
private Handler mServerHandler = new MessageHandler(MainActivity.this) { @Override public void onMessage(String type, JSONObject message) { try { if (type.equals(Communication.Connect.TYPE)) { final String deviceFrom = message.getString(Communication.Connect.DEVICE); Toast.makeText(getApplicationContext(), "Device: " + deviceFrom, Toast.LENGTH_SHORT).show(); } } catch (JSONException e) { Log.d(DEBUG_TAG, "JSON parsing exception: " + e); } } };
Отправка сообщений еще проще:
getConnectionWrapper().send( new HashMap<String, String>() {{ put(Communication.MESSAGE_TYPE, Communication.ConnectSuccess.TYPE); }} );
И, наконец, метод для обнаружения и подключения к серверу:
private void connect() { getConnectionWrapper().findServers(new NetworkDiscovery.OnFoundListener() { @Override public void onFound(javax.jmdns.ServiceInfo info) { if (info != null && info.getInet4Addresses().length > 0) { getConnectionWrapper().stopNetworkDiscovery(); getConnectionWrapper().connectToServer( info.getInet4Addresses()[0], info.getPort(), mConnectionListener ); getConnectionWrapper().setHandler(mClientHandler); } } }); }
Как видите, все очень просто. А главное, все это работает в любой версии Android для максимум двух устройств. Но сделать так, чтобы это работало для условно неограниченного числа устройств очень легко, и очевидное решение придет к вам почти сразу после детального изучения класса Connection. Пусть это будет в качестве домашнего задания.
Ах, да, весь код доступен для изучения и использования всеми желающими в моем репозитории на GitHub.. И, конечно, не исключаю то, что некоторые вещи можно сделать лучше и проще, поэтому не стесняйтесь форкать и делать pull request’ы.
ссылка на оригинал статьи http://habrahabr.ru/post/208132/
Добавить комментарий