> 編寫:[jdneo](https://github.com/jdneo) - 原文:[http://developer.android.com/training/cloudsync/gcm.html](http://developer.android.com/training/cloudsync/gcm.html)
谷歌云消息(GCM)是一個用來給Android設備發送消息的免費服務,它可以極大地提升用戶體驗。利用GCM消息,你的應用可以一直保持更新的狀態,同時不會使你的設備在服務器端沒有可用更新時,喚醒無線電并對服務器發起輪詢(這會消耗大量的電量)。同時,GCM可以讓你最多一次性將一條消息發送給1,000個人,使得你可以在恰當地時機很輕松地聯系大量的用戶,同時大量地減輕你的服務器負擔。
這節課將包含一些把GCM集成到應用中的最佳實踐方法,前提是假定你已經對該服務的基本實現有了一個了解。如果不是這樣的話,你可以先閱讀一下:[GCM demo app tutorial](http://developer.android.com/google/gcm/demo.html)。
### 高效地發送多播消息
一個GCM最有用的特性之一是單條消息最多可以發送給1,000個接收者。這個功能可以更加簡單地將重要消息發送給你的所有用戶群體。例如,比方說你有一條消息需要發送給1,000,000個人,而你的服務器每秒能發送500條消息。如果你的每條消息只能發送給一個接收者,那么整個消息發送過程將會耗時1,000,000/500=2,000秒,大約半小時。然而,如果一條消息可以一次性地發送給1,000個人的話,那么耗時將會是(1,000,000/1,000)/500=2秒。這不僅僅體現出了GCM的實用性,同時對于一些實時消息而言,其重要性也是不言而喻的。就比如災難預警或者體育比分播報,如果延遲了30分鐘,消息的價值就大打折扣了。
想要利用這一功能非常簡單。如果你使用的是Java語言版本的[GCM helper library](http://developer.android.com/google/gcm/gs.html#libs),只需要向`send`或者`sendNoRetry`方法提供一個注冊ID的List就行了(不要只給單個的注冊ID):
~~~
// This method name is completely fabricated, but you get the idea.
List regIds = whoShouldISendThisTo(message);
// If you want the SDK to automatically retry a certain number of times, use the
// standard send method.
MulticastResult result = sender.send(message, regIds, 5);
// Otherwise, use sendNoRetry.
MulticastResult result = sender.sendNoRetry(message, regIds);
~~~
如果想用除了Java之外的語言實現GCM支持,可以構建一個帶有下列頭部信息的HTTP POST請求:
~~~
Authorization: key=YOUR_API_KEY
Content-type: application/json
~~~
之后將你想要使用的參數編碼成一個JSON對象,列出所有在`registration_ids`這個Key下的注冊ID。下面的代碼片段是一個例子。除了`registration_ids`之外的所有參數都是可選的,在`data`內的項目代表了用戶定義的載荷數據,而非GCM定義的參數。這個HTTP POST消息將會發送到:`https://android.googleapis.com/gcm/send`:
~~~
{ "collapse_key": "score_update",
"time_to_live": 108,
"delay_while_idle": true,
"data": {
"score": "4 x 8",
"time": "15:16.2342"
},
"registration_ids":["4", "8", "15", "16", "23", "42"]
}
~~~
關于更多GCM多播消息的格式,可以閱讀:[Sending Messages](http://developer.android.com/google/gcm/gcm.html#send-msg)。
### 對可替換的消息執行折疊
GCM經常被用作為一個觸發器,它告訴移動應用向服務器發起鏈接并更新數據。在GCM中,可以(也推薦)在新消息要替代舊消息時,使用可折疊的消息(Collapsible Messages)。我們用體育比賽作為例子,如果你向所有用戶發送了一條包含了當前比賽比分的消息,15分鐘之后,又發送了一條消息更新比分,那么第一條消息就沒有意義了。對于那些還沒有收到第一條消息的用戶,就沒有必要將這兩條消息全部接收下來,何況如果要接收兩條消息,那么設備不得不進行兩次響應(比如對用戶發出通知或警告),但實際上兩條消息中只有一條是重要的。
當你定義了一個折疊Key,此時如果有多個消息在GCM服務器中,以隊列的形式等待發送給同一個用戶,那么只有最后的那一條消息會被發出。對于之前所說的體育比分的例子,這樣做能讓設備免于處理不必要的任務,也不會讓設備對用戶造成太多打擾。對于其他的一些場景比如與服務器同步數據(檢查郵件接收),這樣做的話可以減少設備需要執行同步的次數。例如,如果有10封郵件在服務器中等待被接收,并且有10條GCM消息發送到設備提醒它有新的郵件,那么實際上只需要一個GCM就夠了,因為設備可以一次性把10封郵件都同步了。
為了使用這一特性,只需要在你要發出的消息中添加一個消息折疊Key。如果你在使用[GCM helper library](http://developer.android.com/google/gcm/gs.html#libs),那么就使用Message類的`collapseKey(String key)`方法。
~~~
Message message = new Message.Builder(regId)
.collapseKey("game4_scores") // The key for game 4.
.ttl(600) // Time in seconds to keep message queued if device offline.
.delayWhileIdle(true) // Wait for device to become active before sending.
.addPayload("key1", "value1")
.addPayload("key2", "value2")
.build();
~~~
如果你沒有使用[GCM helper library](http://developer.android.com/google/gcm/gs.html#libs),那么就直接在你要構建的POST頭部中添加一個字段。將`collapse_key`作為字段名,并將Key的名稱作為該字段的值。
### 在GCM消息中嵌入數據
通常, GCM消息被用作為一個觸發器,或者用來告訴設備,在服務器或者別的地方有一些待更新的數據。然而,一條GCM消息的大小最大可以有4kb,因此,有時候可以在GCM消息中放置一些簡單的數據,這樣的話設備就不需要再去和服務器發起連接了。在下列條件都滿足的情況下,我們可以將數據放置在GCM消息中:
- 數據的總大小在4kb以內。
- 每一條消息都很重要,且需要保留。
- 這些消息不適用于消息折疊的使用情形。
例如,短消息或者回合制網游中玩家的移動數據等都是將數據直接嵌入在GCM消息中的例子。而電子郵件就是反面例子了,因為電子郵件的數據量一般都大于4kb,而且用戶一般不需要對每一封新郵件都收到一個GCM提醒的消息。
同時在發送多播消息時,也可以考慮這一方法,這樣的話就不會導致大量用戶在接收到GCM的更新提醒后,同時向你的服務器發起連接。
這一策略不適用于發送大量的數據,有這么一些原因:
- 為了防止惡意軟件發送垃圾消息,GCM有發送頻率的限制。
- 無法保證消息按照既定的發送順序到達。
- 無法保證消息可以在你發送后立即到達。假設設備每一秒都接收一條消息,消息的大小限制在1K,那么傳輸速率為8kbps,或者說是1990年代的家庭撥號上網的速度。那么如此大量的消息,一定會讓你的應用在Google Play上的評分非常尷尬。
如果恰當地使用,直接將數據嵌入到GCM消息中,可以加速你的應用的“感知速度”,因為這樣一來它就不必再去服務器獲取數據了。
### 智能地響應GCM消息
你的應用不應該僅僅對收到的GCM消息進行響應就夠了,還應該響應地更智能一些。至于如何響應需要結合具體情況而定。
**不要太過激進**
當提醒用戶去更新數據時,很容易不小心從“有用的消息”變成“干擾消息”。如果你的應用使用狀態欄通知,那么應該[更新現有的通知](http://developer.android.com/guide/topics/ui/notifiers/notifications.html#Updating),而不是創建第二個。如果你通過鈴聲或者震動的方式提醒用戶,一定要設置一個計時器。不要讓應用每分鐘的提醒頻率超過1次,不然的話用戶很可能會不堪其擾而卸載你的應用,關機,甚至把手機扔到河里。
**用聰明的辦法同步數據,別用笨辦法**
當使用GCM告知設備有數據需要從服務器下載時,記住你有4kb大小的數據可以和消息一起發出,這可以幫助你的應用做出更智能的響應。例如,如果你有一個支持訂閱的閱讀應用,而你的用戶訂閱了100個源,那么這就可以幫助你的應用更智能地決定應該去服務器下載什么數據。下面的例子說明了在GCM載荷中可以發送什么樣的數據,以及設備可以做出什么樣的反應:
- `refresh` - 你的應用被告知向每一個源請求數據。此時你的應用可以向100個不同的服務器發起獲取訂閱內容的請求,或者如果你在服務器上有一個聚合服務,那么可以只發送一個請求,將100個源的數據進行打包并讓設備獲取,這樣一次性就完成更新。
- `refresh, freshID` - 一種更好的解決方案,你的應用可以有針對性的完成更新。
- `refresh, freshID, timestamp` - 三種方案中最好的,如果正好用戶在收到GCM消息之前手動做了更新,那么應用可以利用時間戳和當前的更新時間進行對比,并決定是否有必要執行下一步的行動。
- 序言
- Android入門基礎:從這里開始
- 建立第一個App
- 創建Android項目
- 執行Android程序
- 建立簡單的用戶界面
- 啟動其他的Activity
- 添加ActionBar
- 建立ActionBar
- 添加Action按鈕
- 自定義ActionBar的風格
- ActionBar的覆蓋層疊
- 兼容不同的設備
- 適配不同的語言
- 適配不同的屏幕
- 適配不同的系統版本
- 管理Activity的生命周期
- 啟動與銷毀Activity
- 暫停與恢復Activity
- 停止與重啟Activity
- 重新創建Activity
- 使用Fragment建立動態的UI
- 創建一個Fragment
- 建立靈活動態的UI
- Fragments之間的交互
- 數據保存
- 保存到Preference
- 保存到文件
- 保存到數據庫
- 與其他應用的交互
- Intent的發送
- 接收Activity返回的結果
- Intent過濾
- Android分享操作
- 分享簡單的數據
- 給其他App發送簡單的數據
- 接收從其他App返回的數據
- 給ActionBar增加分享功能
- 分享文件
- 建立文件分享
- 分享文件
- 請求分享一個文件
- 獲取文件信息
- 使用NFC分享文件
- 發送文件給其他設備
- 接收其他設備的文件
- Android多媒體
- 管理音頻播放
- 控制音量與音頻播放
- 管理音頻焦點
- 兼容音頻輸出設備
- 拍照
- 簡單的拍照
- 簡單的錄像
- 控制相機硬件
- 打印
- 打印照片
- 打印HTML文檔
- 打印自定義文檔
- Android圖像與動畫
- 高效顯示Bitmap
- 高效加載大圖
- 非UI線程處理Bitmap
- 緩存Bitmap
- 管理Bitmap的內存
- 在UI上顯示Bitmap
- 使用OpenGL ES顯示圖像
- 建立OpenGL ES的環境
- 定義Shapes
- 繪制Shapes
- 運用投影與相機視圖
- 添加移動
- 響應觸摸事件
- 添加動畫
- View間漸變
- 使用ViewPager實現屏幕側滑
- 展示卡片翻轉動畫
- 縮放View
- 布局變更動畫
- Android網絡連接與云服務
- 無線連接設備
- 使得網絡服務可發現
- 使用WiFi建立P2P連接
- 使用WiFi P2P服務
- 執行網絡操作
- 連接到網絡
- 管理網絡
- 解析XML數據
- 高效下載
- 為網絡訪問更加高效而優化下載
- 最小化更新操作的影響
- 避免下載多余的數據
- 根據網絡類型改變下載模式
- 云同步
- 使用備份API
- 使用Google Cloud Messaging
- 解決云同步的保存沖突
- 使用Sync Adapter傳輸數據
- 創建Stub授權器
- 創建Stub Content Provider
- 創建Sync Adpater
- 執行Sync Adpater
- 使用Volley執行網絡數據傳輸
- 發送簡單的網絡請求
- 建立請求隊列
- 創建標準的網絡請求
- 實現自定義的網絡請求
- Android聯系人與位置信息
- Android聯系人信息
- 獲取聯系人列表
- 獲取聯系人詳情
- 使用Intents修改聯系人信息
- 顯示聯系人頭像
- Android位置信息
- 獲取最后可知位置
- 獲取位置更新
- 顯示位置地址
- 創建和監視地理圍欄
- Android可穿戴應用
- 賦予Notification可穿戴特性
- 創建Notification
- 在Notifcation中接收語音輸入
- 為Notification添加顯示頁面
- 以Stack的方式顯示Notifications
- 創建可穿戴的應用
- 創建并運行可穿戴應用
- 創建自定義的布局
- 添加語音功能
- 打包可穿戴應用
- 通過藍牙進行調試
- 創建自定義的UI
- 定義Layouts
- 創建Cards
- 創建Lists
- 創建2D-Picker
- 創建確認界面
- 退出全屏的Activity
- 發送并同步數據
- 訪問可穿戴數據層
- 同步數據單元
- 傳輸資源
- 發送與接收消息
- 處理數據層的事件
- Android TV應用
- 創建TV應用
- 創建TV應用的第一步
- 處理TV硬件部分
- 創建TV的布局文件
- 創建TV的導航欄
- 創建TV播放應用
- 創建目錄瀏覽器
- 提供一個Card視圖
- 創建詳情頁
- 顯示正在播放卡片
- 幫助用戶在TV上探索內容
- TV上的推薦內容
- 使得TV App能夠被搜索
- 使用TV應用進行搜索
- 創建TV游戲應用
- 創建TV直播應用
- TV Apps Checklist
- Android企業級應用
- Ensuring Compatibility with Managed Profiles
- Implementing App Restrictions
- Building a Work Policy Controller
- Android交互設計
- 設計高效的導航
- 規劃屏幕界面與他們之間的關系
- 為多種大小的屏幕進行規劃
- 提供向下和橫向導航
- 提供向上和歷史導航
- 綜合:設計樣例 App
- 實現高效的導航
- 使用Tabs創建Swipe視圖
- 創建抽屜導航
- 提供向上的導航
- 提供向后的導航
- 實現向下的導航
- 通知提示用戶
- 建立Notification
- 當啟動Activity時保留導航
- 更新Notification
- 使用BigView風格
- 顯示Notification進度
- 增加搜索功能
- 建立搜索界面
- 保存并搜索數據
- 保持向下兼容
- 使得你的App內容可被Google搜索
- 為App內容開啟深度鏈接
- 為索引指定App內容
- Android界面設計
- 為多屏幕設計
- 兼容不同的屏幕大小
- 兼容不同的屏幕密度
- 實現可適應的UI
- 創建自定義View
- 創建自定義的View類
- 實現自定義View的繪制
- 使得View可交互
- 優化自定義View
- 創建向后兼容的UI
- 抽象新的APIs
- 代理至新的APIs
- 使用舊的APIs實現新API的效果
- 使用版本敏感的組件
- 實現輔助功能
- 開發輔助程序
- 開發輔助服務
- 管理系統UI
- 淡化系統Bar
- 隱藏系統Bar
- 隱藏導航Bar
- 全屏沉浸式應用
- 響應UI可見性的變化
- 創建使用Material Design的應用
- 開始使用Material Design
- 使用Material的主題
- 創建Lists與Cards
- 定義Shadows與Clipping視圖
- 使用Drawables
- 自定義動畫
- 維護兼容性
- Android用戶輸入
- 使用觸摸手勢
- 檢測常用的手勢
- 跟蹤手勢移動
- Scroll手勢動畫
- 處理多觸摸手勢
- 拖拽與縮放
- 管理ViewGroup中的觸摸事件
- 處理鍵盤輸入
- 指定輸入法類型
- 處理輸入法可見性
- 兼容鍵盤導航
- 處理按鍵動作
- 兼容游戲控制器
- 處理控制器輸入動作
- 支持不同的Android系統版本
- 支持多個控制器
- Android后臺任務
- 在IntentService中執行后臺任務
- 創建IntentService
- 發送工作任務到IntentService
- 報告后臺任務執行狀態
- 使用CursorLoader在后臺加載數據
- 使用CursorLoader執行查詢任務
- 處理查詢的結果
- 管理設備的喚醒狀態
- 保持設備的喚醒
- 制定重復定時的任務
- Android性能優化
- 管理應用的內存
- 代碼性能優化建議
- 提升Layout的性能
- 優化layout的層級
- 使用include標簽重用layouts
- 按需加載視圖
- 使得ListView滑動順暢
- 優化電池壽命
- 監測電量與充電狀態
- 判斷與監測Docking狀態
- 判斷與監測網絡連接狀態
- 根據需要操作Broadcast接受者
- 多線程操作
- 在一個線程中執行一段特定的代碼
- 為多線程創建線程池
- 啟動與停止線程池中的線程
- 與UI線程通信
- 避免出現程序無響應ANR
- JNI使用指南
- 優化多核處理器(SMP)下的Android程序
- Android安全與隱私
- Security Tips
- 使用HTTPS與SSL
- 為防止SSL漏洞而更新Security
- 使用設備管理條例增強安全性
- Android測試程序
- 測試你的Activity
- 建立測試環境
- 創建與執行測試用例
- 測試UI組件
- 創建單元測試
- 創建功能測試
- 術語表