[Android] Cloud to Device Messaging или Push Notification

Давно же я не писал о разработке, и пожалуй напишу я о том, чем занимался не давно. Я напишу о Cloud to Device Messaging (C2DM), это такой Android‘овский аналог iOS‘овского push notification.

Вообще для Android устройств такие уведомления не особо бывают и нужны, так как в отличии от iOS в Android вполне вменяемая многозадачность, но мне от части стало просто интересно, а с другой стороны в одном из приложений очень просилась возможность выдачи данных по запросу с веб-сервиса, а для случая с C2DM ничего особенного городить на стороне Android приложения и на стороне веб-сервиса не надо.

И так 2 основных задачи:

  1. работа с сервисом C2DM в android приложении
  2. отправка сообщений (команд) со стороны веб-сервиса

C2DM в android приложении

Поддержка C2DM есть только начиная с Android 2.2 (API 8), к тому же на устройстве должна обязательно быть зарегистрирована учетная запись Google.

Так как сервис для работы использует сервера Google, надо зарегистрироваться на Google Projects for Android: C2DM после чего к вашему аккаунту (лучше завести новый, так как доступ к нему будет использоваться и в приложении, и на стороне веб-сервиса) будет добавлена возможность использования уведомлений с ограничением 200 тысяч сообщений в сутки. В примерах указан аккаунт mymail@gmail.com.

Теперь надо добавить в свой проект Package (название будет com.google.android.c2dmс) готовым набором классов для работы с C2DM (скачать тут).

Создаем в своем проекте класс для работы с C2DM (за основу взят пример от Google):

package com.test.www;

import com.google.android.c2dm.C2DMBaseReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class C2DMReceiver extends C2DMBaseReceiver {
    public C2DMReceiver(){
        super("mymail@gmail.com");
    }

    @Override
    public void onRegistered(Context context, String registration) {
        Log.w("onRegistered", registration);
        //Отправить идентификатор на свой сервер
    }

    @Override
    public void onUnregistered(Context context) {
        Log.w("onUnregistered", "");
    }

    @Override
    public void onError(Context context, String errorId) {
        Log.w("onError", errorId);
    }

    @Override
    protected void onMessage(Context context, Intent receiveIntent)
    {
        String data = receiveIntent.getStringExtra("message");
        if(data != null)
        {
            Log.e("gps","C2DMReceiver: " + data);
            //Обработать полученное сообщение
            if (data.contentEquals("command"))
            {
            	//Выполнить действие, соответствующее команде
            }
        }
    }
}

Немного изменяем AndroidManifest.xml:

добавляем блок permission

<permission
                android:name="com.test.www.permission.C2D_MESSAGE"
                android:protectionLevel="signature"/>
    <uses-permission android:name="com.test.www.permission.C2D_MESSAGE"/>
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>

в блок Application добавляем блоки service и reciver

<service android:name=".C2DMReceiver"/>
	<receiver
              android:name="com.google.android.c2dm.C2DMBroadcastReceiver"
              android:permission="com.google.android.c2dm.permission.SEND">
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
            <category android:name="com.test.www"/>
        </intent-filter>
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.REGISTRATION"/>
             <category android:name="com.test.www"/>
        </intent-filter>
     </receiver>

Вот в общем-то и все, теперь надо в основной класс добавить регистрацию в сервисе C2DM:

//При запуске получаем идентификатор, так как он уже может быть выдан
pushId =  C2DMessaging.getRegistrationId(this);
//Инициализация C2DM
if(pushId == "")
{
   C2DMessaging.register(this, "mymail@gmail.com");
}

Веб-сервис

Приложение для Android готово, но теперь надо отправить ему нужную команду, для этого необходимо написать простенький сервис на php, для корректной работы понадобится curl. Данный код был немного адаптирован для Code Igniter, но легко дорабатывается под чистый php:

//Аутентификация на Google и получение Auth Token
function _googleAuthenticate()
	{    

    $ch = curl_init();
    if(!$ch){
        return false;
    }

    curl_setopt($ch, CURLOPT_URL, "https://www.google.com/accounts/ClientLogin");
    $post_fields = "accountType=" . urlencode('HOSTED_OR_GOOGLE')
        . "&Email=" . urlencode('mymail@gmail.com')
        . "&Passwd=" . urlencode('password')
        . "&source=" . urlencode('test')
        . "&service=" . urlencode('ac2dm');
    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
    curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    $response = curl_exec($ch);
    curl_close($ch);
    if (strpos($response, '200 OK') === false) {
        return false;
    }
    preg_match("/(Auth=)([\w|-]+)/", $response, $matches);
    if (!$matches[2]) {
        return false;
    }
    $_SESSION['google_auth_id'] = $matches[2];
    return $matches[2];
	}
//Функция отправки сообщения
function _sendMessage($authCode, $deviceRegistrationId, $msgType, $messageText)
	{

        $headers = array('Authorization: GoogleLogin auth=' . $authCode);
        $data = array(
            'registration_id' => $deviceRegistrationId,
            'collapse_key' => $msgType,
            'data.message' => $messageText
        );

        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, "https://android.apis.google.com/c2dm/send");
        if ($headers)
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

        $response = curl_exec($ch);
        curl_close($ch);
        return $response;
    }
//Отправка комманды. registrationId - хранится, например, в базе данных, куда Android приложение
//записывает идентификатор при регистрации
function sendCommand($registrationId)
	{
		//Получение Auth Token и отправка сообщения
		$token = $this->_googleAuthenticate();
		$this->_sendMessage($token,$registrationId,'1','текст сообщения');
	}