2014年4月2日

push notification - android client 以 GCM 實現 (二)

app 發布後,想要從 server 發訊息給 client 時,該怎麼做呢?一直保持 server 與 client 間的連線?若 server 只是偶而或幾天才發送一次訊息,這樣做不僅耗電還浪費資源,重點是很可能被使用者移除!但是不保持連線,那要如何讓 server 主動將訊息傳給 client 呢?下面就來介紹一下 Android 如何透過 GCM 讓 server 將訊息傳送給 client
※ GCM 註冊流程請參考上篇 push notification - android client 以 GCM 實現 (一)

Client 流程

1. 打開 Android SDK 下載 Google Play Services


2. 將下面檔案複製到 workspace 中,然後 import 至 eclipse,並設定為 library

C:\android-sdk目錄\extras\google\google_play_services\libproject\google-play-services_lib

3. 新建專案並引入上述的 library

4. 在 AndroidManifest.xml 中加入下列權限

<!-- 使用GCM -->
<permission
    //com.example.gcmtest 為 package name
    android:name="com.example.gcmtest.permission.C2D_MESSAGE"
    android:protectionLevel="signature" />
<uses-permission android:name="com.example.gcmtest.permission.C2D_MESSAGE" />

<!-- 存取 internet -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- App receives GCM messages. -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<!-- GCM requires a Google account. -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<!-- Keeps the processor from sleeping when a message is received. -->
<uses-permission android:name="android.permission.WAKE_LOCK" />

5. 跟 Google GCM Server 註冊,取得 regId

GoogleCloudMessaging gcm = GoogleCloudMessaging
  .getInstance(context);

String registerid = "";
Log.i(ConfigUtil.TAG, "registerGCM package id:" + ConfigUtil.SENDER_ID);
try {
 registerid = gcm.register(ConfigUtil.SENDER_ID);
} catch (IOException e) {
 e.printStackTrace();
}
Log.i(ConfigUtil.TAG, "registerGCM regId:" + registerid);  //取得的 regId
if(!registerid.equals("")){
 registerGCM(registerid);
}
※ GCM Server 回傳 RegID

6. 將 RegID 傳給自己的 server 儲存
7. Server 發送訊息到 Google GCM Server ( 實作部分稍後解說 )
8. 在 AndroidManifest.xml 中註冊接收 GCM Broadcast

<!-- 接收 GCM 的 receiver -->
<receiver
    android:name="com.example.gcmtest.receiver.GcmBroadcastReceiver"  //接收的 class 路徑
    android:permission="com.google.android.c2dm.permission.SEND" >
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
        <category android:name="com.example.gcmtest" />  //package name
    </intent-filter>
</receiver>

9. Google GCM Service 發送訊息到 Android Device,Android 以 Broadcast 接收

public class GcmBroadcastReceiver extends BroadcastReceiver {
 Context ctx;

 @Override
 public void onReceive(Context context, Intent intent) {
  Log.i(ConfigUtil.TAG, "action=" + intent.getAction());
  GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);
  ctx = context;
  String messageType = gcm.getMessageType(intent);
  if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {
   Log.i(ConfigUtil.TAG, "Send error: " + intent.getExtras().toString());
  } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {
   Log.i(ConfigUtil.TAG, "Deleted messages on server: "
     + intent.getExtras().toString());
  } else {
   //action = com.google.android.c2dm.intent.REGISTRATION
   sendNotification(intent);  //正常收到訊息
  }
  setResultCode(Activity.RESULT_OK);
 }
}
※ client 接收到 GCM Broadcas

server 流程

  1. 管理 client RegID ( 儲存、刪除 )
  2. 發訊息到 Google GCM Server
    (1) header 設定 JSON 格式,並加入 project number ( 請參考 push notification - android client 以 GCM 實現 (一) )
    
    Content-Type:application/json
    Authorization:key=AIzaSyB-1uEai2WiUapxCs2Q0GZYzPu7Udno5aA  //project number
    
    (2) content:設定接收端的 regId 與要傳送的訊息內容,並轉成 JSON 格式
    
    {
      "registrationids" : ["APA91bHun4MxP5egoKMwt2KZFBaFUH-1RYqx..."],  //regId
      "data" : {
        "key" : "value"
     ...
    },
    }
    
    ※ registrationids,data 為固定 key ( 一定要有 ),請照此規格包裝您要傳送的訊息
範例 ( 這裡以 java 實現 ):

List registrationIds = new ArrayList();
registrationIds.add(registerid);  //client regId
MsgObj msgObj = new MsgObj();
msgObj.setMsg(et_msg.getText().toString());  //要發送的訊息

Gcmlite gcmlite = new Gcmlite();
gcmlite.setRegistration_ids(registrationIds);
gcmlite.setData(msgObj);

String paramvalue = GsonUtil.gson.toJson(gcmlite);  //轉成 JSON String
Log.i(ConfigUtil.TAG, "snend:"+paramvalue);
String responseStr = "";
try {
 responseStr = HttpUtil.sendMsgToGcmServer(paramvalue);
} catch (HttpHostConnectException e) {
 e.printStackTrace();
} catch (UnsupportedEncodingException e) {
 e.printStackTrace();
} catch (ClientProtocolException e) {
 e.printStackTrace();
} catch (IOException e) {
 e.printStackTrace();
} catch (Exception e) {
 e.printStackTrace();
}
Log.i(ConfigUtil.TAG, "responseStr:"+responseStr);
傳送後會得到以下訊息 ( 訊息代表意義稍後解說 )



Error

有時 client 跟 GCM server 註冊時會出現下列訊息,請等待指數時間後再重新註冊



GCM response status

  • 200:success
  • 400:JSON passing error or contained invalid fields
  • 401:API key is not valid
  • 5xx:GCM server is temporarily unavailable(timeouts..). honoring any Retry-After header included in the response.Sender must retry later

GCM success response body

  • multicast_id:Broadcast uuid
  • success:成功數量
  • failure:失敗數量
  • canonical_ids
  • results
    • message_id:message uuid? I'm not sure.
    • registration_id:訊息已成功發送,不過 client 的 registration ID 已經更換,server 需要 replace id,不更換的話之後 client 可能無法收到 Broadcast
      ※ 如果 GCM 要改變 registration ID,GCM 會在發送個通知給 Android Application,client 會自動處理
    • error
      • Unavailable:GCM servers 忙碌中,稍後可嘗試重新 send msg
      • NotRegistered:client 已經移除 app,或 app 不接收 "com.google.android.c2dm.intent.RECEIVE" intents 的 broadcast,所以您必須將 registration ID 從 DB 中移除
      • 其他 error response
※ 如果 failure 與 canonical_ids 都是 0,則不需要再查看其他 response

References

範例下載 ( 測試時請記得將 ConfigUtil 中 SERVER_URL, SENDER_ID, API_KEY 換掉 )