Решение в виде сервиса на устройстве, который регулярно опрашивает сервер выглядит несовременно, да и опасно: достаточно большое количество пользователей могут задосить нам сервер. Увеличение интервала опроса в нашем случае решает проблему нагрузки с одной стороны, но снижает скорость доставки с другой.
Второй вариант: использовать Cloud to Device Messaging Framework. Действительно хорошее решение, но в ряде случаев не подходит. Во-первых, передаче сообщений через сторонние сервера могут препятствовать соображения конфиденциальности, а во-вторых, если приложение должно поддерживать версию Android 2.1 и ниже, то этот фреймворк нам не поможет.
Третий вариант - использовать свой http push сервер, например nginx + nginx push stream module. Можно поспорить, что лучше, много запросов или много коннектов а также сравнить производительность разных серверов, но цель у нас сейчас другая. Давайте рассмотрим простой вариант реализации такого оповещения.
1. Собираем и конфигурируем nginx
Скачиваем исходники nginx-а и модуля. Выполняем configure c параметром
--add-module=path/to/wandenberg-nginx-push-stream-module.
Выполняем make и make install. Ничего необычного.
Также просто можем поступить и с конфигурацией сервера: берём официальный пример настроек, он нас пока вполне устраивает:
location /pub {
# activate publisher (admin) mode for this location
push_stream_publisher admin;
# query string based channel id
set $push_stream_channel_id $arg_id;
}
location ~ /sub/(.*) {
# activate subscriber (streaming) mode for this location
push_stream_subscriber;
# positional channel path
set $push_stream_channels_path $1;
}
при такой конфигурации можно опубликовать сообщение в канал c именем channel командой:
curl -X POST 'http://myserver.com/pub?id=channel' -d 'Text of message'
и забрать его оттуда:
curl http://myserver.com/sub/channel
Для того, чтобы nginx сохранял сообщения, которые ещё не доставлены добавим ещё две директивы:
push_stream_store_messages on; (в блок location /pub) и push_stream_message_ttl 10m; (в блок http). Параметр во второй директиве говорит, что недоставленное сообщение будет сохраняться в канале 10 минут.
Это только самые необходимые настройки. Остальной процесс тюнинга сервера оставим за рамками этой статьи, тут у каждого админа есть свои особенные приёмы и предпочтения.
2. Делаем тестовое Android-приложение
На клиентской стороне нам понадобится “слушать” nginx из сервиса и переподключаться к нему при любых сбоях, если сеть доступна в данный момент. Для того чтобы иметь достаточно “живучий” сервис, используем библиотеку WakefulIntentService. Для работы этого компонента нам потребуется разрешение android.permission.WAKE_LOCK. Добавим в AndroidManifest.xml остальные разрешения и объявление сервиса и получим:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="net.multipi.messager" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="7" /> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.VIBRATE"/> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".MessagerActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".AppService" /> </application> </manifest>
Главное Activity будет предельно простым, в его метод onCreate добавим только запуск сервиса:
WakefulIntentService.sendWakefulWork(getApplicationContext(), AppService.class);
Тут AppService - класс, расширяющий WakefulIntentService. Он и будет делать всю работу, незримо но надёжно :)
Вот его код:
package net.multipi.messager; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkInfo; import com.commonsware.cwac.wakeful.WakefulIntentService; public class AppService extends WakefulIntentService { private static final String SUB_HOST = "http://myserver.com/sub/channel"; public AppService() { super("AppService"); } @Override protected void doWakefulWork(Intent intent) { while(true) { try { if (isOnline()) { readMessage(); } } catch (Exception e) { e.printStackTrace(); } } } private void readMessage() throws Exception { HttpURLConnection conn = (HttpURLConnection) new URL(SUB_HOST).openConnection(); conn.setUseCaches(false); conn.setDoOutput(true); conn.setDoInput(true); int respCode = -1; for (int i=0; i<10 && respCode != HttpURLConnection.HTTP_OK; i++){ respCode = conn.getResponseCode(); Thread.sleep(100); } if (respCode != HttpURLConnection.HTTP_OK) { throw new Exception("http error code " + respCode); } BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); String message = br.readLine(); sendNotify(getResources().getString(R.string.msg_title), message); } private boolean isOnline() { ConnectivityManager conMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo i = conMgr.getActiveNetworkInfo(); if (i == null || !i.isConnected()) { return false; } return true; } private void sendNotify(String title, String message) { NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); Notification notification = new Notification(android.R.drawable.star_off, title, System.currentTimeMillis()); notification.vibrate = new long[]{0,100,200,300}; notification.defaults = Notification.DEFAULT_ALL; notification.flags |= Notification.FLAG_AUTO_CANCEL; PendingIntent intent = PendingIntent.getActivity(getApplication(), 0, new Intent(), 0); notification.setLatestEventInfo(getApplication(), title, message, intent); notificationManager.notify(0, notification); } }
В методе doWakefulWork запускаем вечный цикл, в котором проверяем есть ли подключение к сети. Проверку выполняем в методе isOnline, реализованном тут же. Если подключение обнаружено, будет вызван блокирующий метод readMessage, который остановит выполнение вечного цикла до прихода сообщения с сервера. В методе readMessage мы открываем соединение к нашему серверу и читаем статус ответа, а при успешном статусе и строку ответа, которую выводим методом sendNotify. Вывод выполняем в строку статуса устройства со значком, вибрацией и т.п.
Для проверки работы собранной нами связки запустите приложение на устройстве, закройте его, отправьте curl - запрос к своему серверу и наблюдайте как в строке статуса вашего Android-устройства появится сообщение с текстом, который вы отправили.
Чтобы отправлять "адресные" сообщения на конкретное устройство, следует каждому из устройств выделить отдельное название канала. Учитывая, что имя канала - просто строка, можно составить её как хэш от любого известного на сервере идентификатора устройства или пользователя: например IMEI или номера телефона.
Комментариев нет:
Отправить комментарий