Google 已宣佈將停用 C2DM(Cloud to Device Messaging)

 

GCM 是 Google 提供的一項服務,可讓 Android app 的開發者從自己的伺服器傳送訊息到安裝在 Android 設備的 app。GCM 僅提供上限 4kb 的輕量資料傳遞

 

關於 GCM 的運用:

 

可由您的官方(網站) 發送活動訊息給有安裝 app 的 Android 裝置。

 

開發社群 app,例如甲上傳的最新動向照片,朋友乙便會知道。

 

您可能會聯想到即時通訊,不過 Google 並不保證每封訊息的依序發送,並且有 4KB 的上限,所以您若要利用 GCM 開發即時通訊, 您自己的 server 可能要擔負蠻多工作的,例如甲乙兩人對話內容的先後順序,及較大資料量 或非文字(如照片) 的傳送。

 

開啟 Google APIs Console page (還沒 google 帳戶的,快去申請一個吧......)

 

建立新 project:

255C24A86A0374196C6BD1A64BFBD4  384D1B3FF722F9EED74AA590406E38  






輸入 project 名稱:

D26CDA8709CC5F7D097678B409F205  



建立 project 後,原 project 下拉式功能表會顯示新建立的 project 名(此例為 test),並指在 Services,另外,您會在網址列看到如下:
HTTPs://code.google.com/apis/console/#project:4815162342



4815162342
就是您的 project ID,也是 GCM 的 sender ID,之後的 CommonUtilities.java 會用到。



而在畫面右方,是各種 Google API service 的啟用狀態,


6BD0CD3E426C9232AC37F6BC7AF154  






找到 Google Cloud Messaging for Android,
由 OFF 變更為 ON,第一次啟用 Google API services 會出現使用條約,必須勾選同意。



16ACC094D89B00A7A9F5C94CE5B515  


您也可以在 Active 頁簽 看到 GCM 的狀態為 ON

C7B0CBD388DB04A0AF715BF070A3BA  



切換到 API Access 選項,您應該會看到一個自動產生的 "Simple API Access" key",
若沒有,則點擊 "Create new Server key..."

D533C53A84583C9B96B3CC367081E6  




直接按左下角的 Create 鈕

BA5F666559A3EAACD52EE4B03780CF  



  



您會看到 API key



CE538485AB62233D5D5F1291BC6487

您也可以重新產生新的 API key,只不過新的 key 不會馬上生效,生效時間顯示在標題為 Activated on 那一列,而左下最後二列則是舊的 key 及其到期時間,另外,其時間可能和您的時區不同,所以您可以這樣估算,舊 key 可使用期限約為您產生新 key 後 24 小時。

539A4C4EFA9E179F82E18B891480EE  


再來看 Android 端要做的工作,因為 GCM 會使用到 Google API,所以我們先開啟 SDK Manager 下載安裝 Google APIs,只要勾選您要開發的目標設備 Android APIs 版本平臺的 Google APIs 安裝就可以了。


AF1CF0C9D8CAC1F8BBAD9DFBA002D9  

您在 AVDManager(Android Virtual Device Manager) 中建立的 Android 模擬器必須要能支援 Google APIs,大多數人應該一開始都沒有建立支援 Google APIs 的模擬器,所以就另外建立新的模擬器吧;我想我要開發的 app 多會用到,所以就一次將所有的模擬器都改為使用 Google API 的了。


E1F6742AB07B71F972E0227AC92C5B  


新增的 Android 模擬器中,還要輸入已註冊的 google 帳戶。

B8BBE59DFA85E20D312B94F25B0097  3A82E81061ED7731807B9B22319B88  






接下來在 Eclipse 打開 SDK Manager,安裝
Extras > Google Cloud Messaging for Android Library

 

SDK Manager 會在您的 /android sdk 安裝目錄
/extras/google/ 下建立一個 gcm 目錄,且在 gcm 目錄下包含



gcm-client,
gcm-server,
samples/gcm-demo-client,
samples/gcm-demo-server,
samples/gcm-demo-appengine 幾個子目錄。

 

在 /android sdk 安裝目錄/extras/google/gcm/gcm-client/dist 目錄下找到 gcm.jar 並複製到 您 app 的 /libs/ 下
接下來修改您程式專案的 AndroidManifest.xml:
<!-- 注意 minSdkVersion 要 8 以上 -->
<uses-sdk android:minsdkversion="8" android:targetsdkversion="17" />

 

<permission android:name="完整package名稱.permission.C2D_MESSAGE" android:protectionlevel="signature" />
<uses-permission android:name="完整package名稱.permission.C2D_MESSAGE" />



<!-- 使用GCM -->
<uses-permission android:name="完整package名稱.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" />
<!-- 手機震動 -->
<uses-permission android:name="android.permission.VIBRATE" />

 

...
...

 

<application>
...
...
<!-- 接收 GCM 的 receiver -->
<receiver android:name="com.google.android.gcm.GCMBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="完整的 app package 名稱" />
</intent-filter>
</receiver>

 

<!-- GCM service -->
<service android:name=".GCMIntentService" />
</application>
...
...



直接拿 Google 提供的範例程式來改,到 /android sdk 安裝目錄/extras/google/gcm/samples/gcm-demo-client/src/com/google/android/gcm/demo/app/ 下找到 GCMIntentService.java 複製到您程式的 /src/ 下,

 

並加以修改,主要覆寫修改 onMessage,generateNotification 幾個函式。

 

@Override
protected void onMessage(CoNtext coNtext, Intent intent)
{
Log.i(TAG, "Received message");
// 接收 GCM server 傳來的訊息
Bundle bData = intent.getExtras();

 

// 處理 bData 內含的訊息
// 在本例中, 我的 server 端程式 gcm_send.php 傳來了 message, campaigndate, title, description 四項資料
String message = bData.getString("message");
String campaigndate = bData.getString("campaigndate");
String title = bData.getString("title");
String description = bData.getString("description");
...
...
// 通知 user
generateNotification(coNtext, bData);
}

 

...
...
// 注意這裡我不是直接改寫原範例的generateNotification()
// 範例的 generateNotification() 傳入的參數是 CoNtext, String
// 利用 JAVA 的多型特性, 我保留了原 generateNotification(), 說不定以後會用到
// 另外增加了一個傳入參數為 CoNtext, Bundle 的 generateNotification()
private static void generateNotification(CoNtext coNtext, Bundle data)
{
int icon = R.drawable.ic_launcher;
long when = System.currentTimeMillis();
NotificationManager nm = (NotificationManager) coNtext.getSystemService(CoNtext.NOTIFICATION_SERVICE);
Intent ni = new Intent(coNtext, MainActivity.class);
ni.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent intent = PendingIntent.getActivity(coNtext, 0, ni, 0);
// 如果您想讓通知的內容有動態的變化
// 就可以運用傳進來的參數 -- Bundle 型別 data
// 取出您要的欄位填入 setContentTitle() 和 setContentText()
// ;-)
Notification noti = new NotificationCompat.Builder(coNtext)
.setContentTitle("收到 GCM 通知囉")
.setContentText("太棒了! 大成功!!")
.setContentIntent(intent)
.setDefaults(Notification.DEFAULT_ALL)
.setSmallIcon(icon)
.setWhen(when)
.build();
nm.notify(0, noti);
}


複製範例程式的 strings.xml 中與 GCM 有關的常數:

<!-- GCM(Google Cloud Messaging) -->
<string name="error_config">Please set the %1$s constant and recompile the app.</string>
<string name="already_registered">Device is already registered on server.</string>
<string name="gcm_registered">From GCM: device successfully registered!</string>
<string name="gcm_unregistered">From GCM: device successfully unregistered!</string>
<string name="gcm_message">From GCM: you got message!</string>
<string name="gcm_error">From GCM: error (%1$s).</string>
<string name="gcm_recoverable_error">From GCM: recoverable error (%1$s).</string>
<string name="gcm_deleted">From GCM: server deleted %1$d pending messages!</string>
<string name="server_registering">Trying (attempt %1$d/%2$d) to register device on Demo Server.</string>
<string name="server_registered">From Demo Server: successfully added device!</string>
<string name="server_unregistered">From Demo Server: successfully removed device!</string>
<string name="server_register_error">Could not register device on Demo Server after %1$d attempts.</string>
<string name="server_unregister_error">Could not unregister device on Demo Server (%1$s).</string>
<string name="options_register">Register</string>
<string name="options_unregister">Unregister</string>
<string name="options_clear">Clear</string>
<string name="options_exit">Exit</string>


另外再複製 CommonUtilities.java,修改

...
...
static final String SERVER_URL = "HTTP://您的網址";
static final String SENDER_ID = "4815162342";
static final String DISPLAY_MESSAGE_ACTION = "完整package名稱.DISPLAY_MESSAGE";
...
...


複製並修改 ServerUtilites.java

static boolean register(final CoNtext coNtext, final String regId)
{
Log.i(TAG, "registering device (regId = " + regId + ")");
String serverUrl = SERVER_URL + "/接收gcm註冊.php"; // 就是文後的 gcm_register.php
...
...
}

...
...

public static void unregister(final CoNtext coNtext, final String regId)
{
Log.i(TAG, "unregistering device (regId = " + regId + ")");
String serverUrl = SERVER_URL + "/登出gcm.php"; // 就是文後的 gcm_unregister.php
...
...
}


主程式 activity 內容

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new GCMTask().execute();
....
....
}

...
...

private class GCMTask extends AsyncTask




{
protected Void doInBackground(Void... params)
{
Log.d(TAG, "檢查裝置是否支援 GCM");
// 檢查裝置是否支援 GCM
GCMRegistrar.checkDevice(MainActivity.this);
GCMRegistrar.checkManifest(MainActivity.this);
final String regId = GCMRegistrar.getRegistrationId(MainActivity.this);
if (regId.equals(""))
{
Log.d(TAG, "尚未註冊 Google GCM, 進行註冊");
GCMRegistrar.register(MainActivity.this, SENDER_ID);
}
return null;
}
}




server 端建置 server 上至少要有 2 支程式: 一支接收 Android 設備的 GCM 註冊成功的 regId, 一支負責處理登出 regId。

另外有支程式,其功能是用來發送訊息給 client 端 或 接收 client 端上傳的訊息,故這支程式您可以放在您的網站上 或是 放在公司內某個部門的電腦內,要納入考慮的是,當安裝您 app 的 Android 裝置數達成千上萬時,執行網站上的 php 會效率的問題。

不過,本篇筆記的重點目的是傳送訊息給 client 端,所以這支發送訊息的程式( gcm_send.php) 仍是以 php 撰寫,並放在網站上,做為網站的幕後程式。

接收註冊的程式 gcm_register.php:

if(isset($_POST['regId']))
{
$regId = $_POST['regId'];
$sql = "INSERT INTO 資料表 (gcm_id,...) VALUES ('$regId',...)";
$pdo->exec($sql);
}


接收登出的程式 gcm_unregister.php:

if(isset($_POST['regId']))
{
$regId = $_POST['regId'];
$sql = "DELETE FROM 資料表 WHERE gcm_id='$regId' ";
$rs = $pdo->exec($sql);
}


發送訊息 gcm_send.php:

// 這支程式就是將自己模擬成 client 端,
// 發送 POST 給 Google GCM server

$apiKey = "AIzaSyBFy2kqH0pEdtye3K-3qxovUXTvvG9938k";

// 列出要發送的 user 端 Android 裝置
$sql = "SELECT * FROM gcm_client_資料表";
$rs = $pdo->query($sql);

$regID = array();
foreach($rs as $row)
{
array_push($regID, $row['gcm_id']);
}

// Set POST variables
$url = 'HTTPs://android.googleapis.com/gcm/send';

// 要發送的訊息內容
// 例如我要發送 message, campaigndate, title, description 四樣資訊
// 就將這 4 個組成陣列
// 您可依您自己的需求修改
$fields = array('registration_ids' => $regID,
'data' => array( 'message' => $message,
'campaigndate' => $campaigndate,
'title' => $title,
'description' => $description
)
);

$headers = array('Content-Type: application/json',
'Authorization: key='.$apiKey
);

// Open connection
$ch = curl_init();

// Set the url, number of POST vars, POST data
curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_POST, true );
curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
// Disabling SSL Certificate support temporarly
// curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// 發送的訊息內容轉成 JSON 格式
curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( $fields ) );

// 傳送到 Google GCM server,
// 並接收回傳結果
$result = curl_exec($ch);

// Close connection
curl_close($ch);

echo 'result =='.$result; // 這裡只是讓您知道訊息發送結果是否成功


GCM 運作流程 安裝好的 app 第一次執行時,app 會向 Google 註冊並取得 regId,成功取得後便將 regId 傳送給您網站的 gcm_register.php; regId 長達 162 字元,如果您想將 regId 儲存于資料庫系統內,則您需要建立一個 table 存放 Android 裝置傳上來的 regId,存放 regId 的欄位長度最好大於 162 字元,因為以 Android 設備爆炸性成長的速度來看,如果愈來愈多開發人員採用 GCM,那麼 regId 長度勢必再增加... 當您的網站接收並儲存註冊 GCM 成功的 regId,爾後您的網站便可以發送訊息到已註冊的 app。 而當 app 卸載時,就會呼叫 gcm_unregister.php 做登出的動作。 當您的 server 要發送訊息給有安裝您 app 的 Android 設備時,您的 server 是將訊息發送給 Google GCM server,Google 再將訊息轉發給您 指定的 regId。

發送訊息的限制 當我的 app 下載人數超過 1000 人後,發現我家的兩支手機都沒收到發送的訊息,再回頭去看官方檔(老人家很懶的...達到自己想要的目標就沒再深入研究了... :-P ),原來 GCM 有節流的機制,一次發送訊息給太大量的 Android 裝置,訊息就會直接被丟棄!! >:-(

所以我就將 server 端的程式(gcm_send.php) 稍稍改了一下,變成分批發送,踹到目前每批 300 人發送,還可正常運作,等下次要再發送訊息時再將數量提升到 400 人試試( 直接對已上線的 app 發送測試訊息應該會被 user 幹譙並立馬移除 app 吧...),不過我不敢說是這就是準則,僅提供您做參考,所以就保守一點,每 100 個為一批發送吧。


創作者介紹
創作者 shadow 的頭像
shadow

資訊園

shadow 發表在 痞客邦 留言(0) 人氣()