вторник, 30 июня 2015 г.

Android GCM. Часть 1. Регистрация устройства.

Ни одно современное мобильное приложение сейчас не обходится без пуш уведомлений - сообщений, рассылаемых сервером на устройства клиентов. Google предоставляет сервис Cloud Messaging (GCM) для рассылки уведомлений на устройства Android.

Здесь будет рассказано, как интегрировать поддержку GCM в свое приложение.
(Реализация на серверной стороне будет рассмотрена отдельно)

Архитектура


Реализация GCM включает в себя 3 составляющих:
1. Сервер приложения, с которого исходят уведомления;
2. Мобильное устройство, которое принимает уведомления;
3. GCM серверы, которые отправляют сообщения с сервера приложения на мобильные устройства.


Перед началом работы


Для разработки понадобиться:
1. Установить Google Play services SDK, если он еще не установлен у вас. Инструкция по установке здесь.
2. Подключить Google Play Services для GCM в приложении, добавив строчку compile 'com.google.android.gms:play-services-gcm:7.5.0' в Gradle. Так же убедитесь, что все ссылки на Google Play Services в Gradle имеют одинаковую версию, иначе вы не сможете запустить отладку. Инструкция здесь.
3. Создать приложение в Google Developer Console и активировать в нем GCM API, как описано здесь. Это забота серверной стороны. Клиентскому приложению нужен только номер созданного приложения из Google Developer Console. Если вы создаете только клиентское приложение, уточните этот номер у разработчика серверной части.

Редактирование файла Manifest


Добавьте следующие строчки в manifest приложения:

1. <uses-permission android:name="android.permission.INTERNET" />
Для того, чтобы приложение могло отправить идентификатор устройства на сервер.

2. <uses-permission android:name="android.permission.WAKE_LOCK" />
Предотвращает засыпание процессора во время приема сообщений.

3. <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
Для разрешения приложению регистрировать и принимать сообщения

4. <permission android:name="com.example.gcm.permission.C2D_MESSAGE"
       android:protectionLevel="signature" />
<uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" />
Нужен для запрета другим приложениям принимать уведомления создаваемого приложения.
Здесь нужно заменить com.example.gcm на имя пакета вашего приложения.

5. <uses-permission android:name="android.permission.GET_ACCOUNTS" />
Было в описании, но не сказано, для чего нужно.

Полная инструкция здесь.

Регистрация устройства


Чтобы удостовериться, что клиент и сервер приложения могут обмениваться сообщениями, они должны выполнить процедуру "рукопожатия". Для этого каждое устройство приобретает уникальный идентификационный токен, который нужно отправить на сервер приложения. Сервер сохраняет его и в дальнейшем использует для отправки уведомлений на соответствующие устройства.

Токен будем хранить в Shared Preferences. Так же нужно хранить информацию о том, отправлен токен на сервер или нет. Сначала напишем методы для этого:

// Имя Shared Preferences
private static final String SHARED_PREF_NAME = "Preferences";
// Ключ токена
private static final String PREF_DEVICE_TOKEN = "DeviceToken";
// Флаг, отвечающий за то, отправлен токен на сервер или нет
private static final String PREF_DEVICE_TOKEN_SENT = "DeviceTokenSent";

// Возвращает Shared Preferences

public static SharedPreferences getSharedPreferences(Context context) {
    return context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
}

// Сохраняет токен в Shared Preferences

public static void setDeviceToken(Context context, String deviceToken) {
    SharedPreferences sp = getSharedPreferences(context);
    SharedPreferences.Editor editor = sp.edit();
    editor.putString(PREF_DEVICE_TOKEN, deviceToken);
    editor.commit();
}

// Достает токен из Shared Preferences

public static String getDeviceToken(Context context) {
    SharedPreferences sp = getSharedPreferences(context);
    return sp.getString(PREF_DEVICE_TOKEN, null);
}

// Возвращает true, если токен отправлен на сервер

public static boolean isDeviceTokenSent(Context context) {
    SharedPreferences sp = getSharedPreferences(context);
    return sp.getBoolean(PREF_DEVICE_TOKEN_SENT, false);
}

// Задает флаг, отправлен токен на сервер или нет

public static void setDeviceTokenSend(Context context, boolean sent) {
    SharedPreferences sp = getSharedPreferences(context);
    SharedPreferences.Editor editor = sp.edit();
    editor.putBoolean(PREF_DEVICE_TOKEN_SENT, sent);
    editor.commit();
}

Теперь пишем метод для создания и сохранения токена устройства. Для получения токена на мобильном устройстве используется Instance ID API:

// Номер проекта в Google Developer Console
public final static String PROJECT_NUMBER = "111233456789";
// Пространство для получение токена. Равно строке "GCM"
public final static String SCOPE = GoogleCloudMessaging.INSTANCE_ID_SCOPE;

// Возвращает токен устройства.

// Если его еще не существует, то создает его и сохраняет в Shared Preferences
public static String createDeviceToken(Context context) {
    String token = getDeviceToken(context);
    if(token == null) {
        token = InstanceID.getInstance(context).getToken(PROJECT_NUMBER, SCOPE, null);
        setDeviceToken(context, token);
    }
    return token;
}

Номер проекта можно найти во вкладке Overview:



Метод getToken в Instance ID API является долгой операцией, следовательно createDeviceToken должен быть выполнен в отдельном потоке.

// Отправляет токен на сервер.
// Если токена не существует, создает его.
public static void createAndSendDeviceToken(final Context context) {
    // Если токен уже отправлен на сервер, то ничего не делаем
    if(isDeviceTokenSent(context)) {
        return;
    }

    new AsyncTask<Void, Void, String>() {

        @Override
        protected String doInBackground(Void... params) {
            try {
                return createDeviceToken(context)
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }

        @Override

        protected void onPostExecute(String s) {
            super.onPostExecute(s);

            if (s != null) {

                sendTokenToServer(s);
            } else {
                // Действие при ошибке
            }
        }
    }.execute();
}

Осталось реализовать отправку токена на сервер в методе sendTokenToServer. Если токен успешно отправлен, запомните это вызовом метода setDeviceTokenSend(context, true);