GCMArchitectural Overview
Google Cloud Messaging for Android (GCM)是一个能够帮助开发者从服务器端发送数据到运行在Android手机上的程序的服务。这个服务提供了一个简单,轻量级的机制使得服务器端可以告诉移动端的程序与服务器端建立直接的联系,来获取更新的程序或者用户的数据。C2DM服务可以处理所有的消息队列的问题并且可以把消息发送到目标机器上运行的目标程序。
GCM的主要特点:
1 它允许第三方的程序服务端发送消息到他们的安卓设备。
2 GCM不能保证消息的发送和消息的顺序。
3 手机端的程序不需要一直运行来接收消息。系统会通过Intent broadcast来唤醒程序当有新的消息到来时。当然程序需要设置适当的broadcast receiver和permission。
4 它不提供任何的用户界面或者其他的东西来处理消息。C2DM只是简单的把收到的原始消息传递给程序。这个程序提供了处理这个消息的方法。比如,这个程序可能抛出一个通知,显示一个自定义的界面或者只是同步数据。
5 GCM要求手机必须运行Android2.2或者更高版本并且要有
GooglePlay Store ,或者运行具有谷歌api 的Android 2.2虚拟机。但是,你不仅限于通过Google Play Store部署你的程序。
6 它使用一个现有的连接用于谷歌服务。对前置3.0设备,这要求用户在他们的移动设备设置他们的谷歌账户。Android 4.0.4或更高对于谷歌帐户是不要求的。
GCM如何工作?
这一节给你一个
GCM如何工作的概况。
下面这张表总结了GCM里面关键的术语和概念。它们分成下面两类:
1. Components:GCM里面包含的组件
2. Credentials:用在不同阶段来确认各方都已经被认证的IDs和tokens。这样消息才能发到正确的地方。
下面是C2DM的主要过程:
1. Enabling C2DM:运行在手机上注册了来接收消息的Android程序。
2. Sending a message:发送消息到手机的第三方程序服务器。
3. Receiving a message:从C2DM服务器接收消息的Android程序。
下面是上面3个步骤的详细描述。
Enabling GCM
下面是运行在手机上的Android程序注册接收消息的步骤:
1. 程序第一次要使用消息服务时,触发一个registration intent到GCM服务器。这个registration intent(com.google.android.c2dm.intent.REGISTER)包括sender ID以及 安卓application ID。
2. 如果注册成功,GCM服务器broadcasts一个com.google.android.c2dm.intent.REGISTRATIONintent,它给予安卓程序registration ID。
程序应该保存这个ID留待后用。google可能定期的刷新registration ID,所以你的com.google.android.c2dm.intent.REGISTRATION Intent必须可以多次调用。程序应该能够做出相应的反应。
3. 为了完成注册,程序要把registrationID发送给第三方服务器端。第三方程序服务器通常把这个ID存在数据库中。
Sending a Message
如果第三方程序服务器要发送消息,下面的事情必须就位:
1对某个特定的设备,这个程序有一个允许它接收消息的registration ID。
2第三方程序服务器存储了这个registrationID。
3一个API键。这个是开发者必须在第三方程序服务器上为程序设置的东西(更多信息,看这里
Role of the Third-PartyApplication Server
) 现在它被用来发送消息到手机。
下面是第三方程序服务器发送消息的步骤:
1第三方程序服务器发送消息到GCM服务器。
2如果用户的手机当前不在线,google会把这个消息入队并存储这个消息。
3当用户手机在线时,google发送消息到手机。
4在手机端,系统使用适当的permission通过Intent broadcast把这个消息broadcast到特定的程序,然后特定的程序获得这个消息。这样就唤醒了这个程序。应用程序不需要提前运行来接收这个消息。
5程序处理这个消息。如果应用程序是做一个复杂的处理,你可能想获取屏幕唤醒锁并且在Service里做任何处理。
Receiving a Message
手机上的程序收到消息时的步骤:
1系统收到消息,然后从消息中提取键值对。
2系统使用com.google.android.c2dm.intent.RECEIVEIntent把键值对传给目标程序。
3目标程序从RECEIVEIntent中根据key取得数据并处理数据。
Writing AndroidApplication that use GCM
想要写一个使用GCM的程序,你必须有一个程序服务器端能够执行
Role of theThird-Party Application Server
所描述的任务。这一节描述了你创建一个使用GCM客户端的步骤。
请记住GCM是没有用户界面的。怎么在程序里处理消息取决于你。
写个程序客户端有两个主要步骤:
1. 创建一个manifest文件。这个文件包含程序使用GCM需要使用的权限。
2. 写java代码。要使用GCM,程序要包括:
A. 开始和停止注册服务的代码。
B. Receiversfor com.google.android.c2dm.intent.C2D_MESSAGE 和com.google.android.c2dm.intent.REGISTRATION。
Creating the Manifest
每一个程序在根目录下都有一个AndroidManifest.xml文件。这个文件提供程序的必要信息给Android系统,这些信息是系统在运行任何程序代码之前必须要有的。要使用GCM,这个文件必须包含:
1 com.google.android.c2dm.permission.RECEIVE。程序拥有注册和接受消息的权限。
2 android.permission.INTERNET。程序拥有联网的权限。
3 android.permission.GET_ACCOUNTS permission当GCM需要谷歌账户(但设备版本低于4.0.4时需要)
4 The android.permission.WAKE_LOCK permission程序可以保证处理器在睡觉的时候得到消息。
5 applicationPackage+”.permission.C2D_MESSAGE”防止其他程序注册和接受这个程序的消息。The permission name必须精确匹配这pattern-否则安卓应用程序将不会得到消息。
6Receivers for com.google.android.c2dm.intent.RECEIVE和com.google.android.c2dm.intent.REGISTRATION.category设置成applicationPackage。服务需要com.google.android.c2dm.SEND权限,这样C2DM就可以发送消息给它。
请注意,这两个注册并且收到的消息的实现为意图。
7 一个意图服务来处理意图所收到的广播接收器。
8 如果GCM功能是至关重要的Android应用程序的功能,一定要设置Android:minSdkVersion=“8”在清单中。这确保了Android应用程序不是安装在一个环境中否则它不能正常运行。
这里摘录支持GCM的manifest
<manifest package="com.example.gcm" ...>
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="16"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission android:name="com.example.gcm.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" />
<application ...>
<receiver
android:name=".MyBroadcastReceiver"
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="com.example.gcm" />
</intent-filter>
</receiver>
<service android:name=".MyIntentService" />
</application>
</manifest>
Registering for GCM
Android程序在接收任何消息前需要向GCM服务器注册。如果要注册,需要发送一个intent(com.google.android.c2dm.intent.REGISTER),包含2个参数:
1. sender:是一个授权发送消息到程序的ID,通常是程序开发者设置的一个gmail地址。
2. app:application’sID.通过PendingIntent设置来允许registrationservice提取程序信息。
Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");
registrationIntent.putExtra("app", PendingIntent.getBroadcast(this, 0, new Intent(), 0)); // boilerplate
registrationIntent.putExtra("sender", emailOfSender);
startService(registrationIntent);
直到程序把registration ID发送到第三方程序服务器,注册才结束。第三方程序服务器使用这个registration ID发送消息给目标机器上的目标程序。
此意图将以异步方式发送到GCM服务器,响应将作为一个com.google.android.c2dm.intent.REGISTRATION意图被提交给应用程序,它包含分配到运行在那个特定的设备的Android应用程序的注册ID。
Unregistering from GCM
Intent unregIntent = new Intent("com.google.android.c2dm.intent.UNREGISTER");
unregIntent.putExtra("app", PendingIntent.getBroadcast(this, 0, new Intent(), 0));
startService(unregIntent);
类似于注册请求,此意图是异步发送的,响应是一个com.google.android.c2dm.intent.REGISTRATION意图。
Handling Intents sent by GCM
正如在
Creating the Manifest
讨论的,manifest定义一个广播接收器为
com.google.android.c2dm.intent.REGISTRATION
and
com.google.android.c2dm.intent.RECEIVE
intents。这些intents被GCM传过来表明一个设备被注册(或者没注册),或者传递消息。
处理这些意图可能需要I / O操作(如网络调用第三方服务器),这样的操作不应该在接收者的onReceive()方法里。你可能会直接产生一个新的线程,但没人能保证流程运行的足够长的时间来完成这项工作。因此建议的方式来处理意图就是给他们授权一个服务,比如一个IntentService。例如:
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public final void onReceive(Context context, Intent intent) {
MyIntentService.runIntentInService(context, intent);
setResult(Activity.RESULT_OK, null, null);
然后在MyIntentService:
public class MyIntentService extends IntentService {
private static PowerManager.WakeLock sWakeLock;
private static final Object LOCK = MyIntentService.class;
static void runIntentInService(Context context, Intent intent) {
synchronized(LOCK) {
if (sWakeLock == null) {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
sWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "my_wakelock");
sWakeLock.acquire();
intent.setClassName(context, MyIntentService.class.getName());
context.startService(intent);
@Override
public final void onHandleIntent(Intent intent) {
try {
String action = intent.getAction();
if (action.equals("com.google.android.c2dm.intent.REGISTRATION")) {
handleRegistration(intent);
} else if (action.equals("com.google.android.c2dm.intent.RECEIVE")) {
handleMessage(intent);
} finally {
synchronized(LOCK) {
sWakeLock.release();
注意:您的应用程序必须获得一个锁在开始服务签否则装置在服务启动前就进入待机状态了。
Handling Registration Results
当一个com.google.android.c2dm.intent.REGISTRATION意图被接受,它可能潜在的包含3个额外的东西:registration_id, error,and unregistered。
当注册成功, registration_id 包含注册ID和其他多余没有设置的东西。应用程序必须确保第三方服务器接收注册ID。它可以通过保存注册ID,并将其发送给服务器。如果网络中断了,或有错误,当网启动一次,或者在下次启动时,应用程序应该重新尝试发送注册ID。
注意:尽管com.google.android.c2dm.intent.REGISTRATION意图通常后收到的请求是由应用程序,谷歌可能会定期刷新注册ID。所以应用程序必须准备在任何时间好处理它。
当注销成功,只有额外未注册的被设置,以及类似的登记工作流程,应用程序必须联系第三方服务器删除注册ID(注意,注册ID在意图中是不可用的,但是应用程序得到注册ID时应该保存注册ID)。
如果程序的要求(注册或者未注册)失败,错误将会以一个报错的代码发送过来,其他额外的将会被设置。这里是可能的errorcodes:
private void handleRegistration(Intent intent) {
String registrationId = intent.getStringExtra("registration_id");
String error = intent.getStringExtra("error");
String unregistered = intent.getStringExtra("unregistered");
// registration succeeded
if (registrationId != null) {
// store registration ID on sharedpreferences
// notify 3rd-party server about theregistered ID
// unregistration succeeded
if (unregistered != null) {
// get old registration ID from sharedpreferences
// notify 3rd-party server about theunregistered ID
// last operation (registration or unregistration)returned an error;
if (error != null) {
if ("SERVICE_NOT_AVAILABLE".equals(error)) {
// optionally retry using exponentialback-off
// (see
Advanced Topics
)
} else {
// Unrecoverable error, log it
Log.i(TAG, "Received error: " + error);
com.google.android.c2dm.intent.RECEIVE
被GCM用来传送从第三方服务器
发送过来的信息到运行在手机上的程序的。如果服务器在数据参数中包含键值对,他们可以作为extra在意图中,用key作为额外的名称。GCM还包括一个从包含senderID作为字符串的东西中调用的extra。
这里有个例子,再次使用了 MyIntentReceiver类
private void handleMessage(Intent intent) {
// server sent 2 key-value pairs, score andtime
String score = intent.getExtra("score");
String time = intent.getExtra("time");
// generates a system notification todisplay the score and time
Developing and Testing Your Applications
下面是给开发和测试使用GCM的程序的一些建议:
1. 要开发和测试GCM程序。你需要在Android2.2的设备上运行和调试这个程序。这个设备包含基本的google服务。
2. 要在真机上面开发和调试,真机必须是Android2.2并且包含 Google Play Store。
3. 要在模拟器上开发和调试,需要通过 the Android SDK and AVD Manager下载Android2.2 Google APIs附加到你的SDK中,Android API 8。然后建模拟器的时候选择googleapi 8。具体来说,您需要下载的组件名为“Google APIs by Google Inc, Android API 8”。然后,你需要建立一个使用该系统的AVD。
4. 如果GCM对程序是一个至关重要的功能,必须在AndroidManifest.xml里设置android:minSdkVersion=”8”。确保程序装在能使程序正常运行的环境里。
Role of the Third-Party Application Server
在你写一个使用GCM的客户端程序之前,你必须有一个程序服务器并满足下面的要求:
1. 可以和客户端通信
2. 可以对GCM服务器发起HTTP请求
3. 可以处理请求并可以按需要排列消息,例如使用
exponentialback-off.
。
4. 可以存储API key和registration IDs。API key包含在发送消息的Post的header里。
能够存储API键和客户端注册id。API键包含在头的POST请求,发送消息。
Sends Messages
这一节主要讲第三方程序服务器怎么发送消息到一个或者多个手机设备。
1第三方应用程序服务器可以发送消息到一个设备或多个设备。一个消息发送到多个设备同时被称为多播消息。
2你有两个选择在如何构建请求和响应:纯文本或JSON。
3然而,发送组播消息,您必须使用JSON。纯文本将不会工作。
在第三方程序服务器发送消息到安卓程序之前,必须接受一个从它传来的注册ID。
Request format
发送一个消息,应用程序服务器发出一个POST请求到https://android.googleapis.com/gcm/send。
一个消息请求是由两部分组成:
HTTPheader and HTTP body。
HTTP header必须包含以下标题:
1 Authorization: key=YOUR_API_KEY
2Content-Type: application/json forJSON; application/x-www-form-urlencoded;
charset=UTF-8 for plain text.
Content-Type:application/json
Authorization:key=AIzaSyB-1uEai2WiUapxCs2Q0GZYzPu7Udno5aA
"registration_ids" : ["APA91bHun4MxP5egoKMwt2KZFBaFUH-1RYqx..."],
"data" : {
注意:如果content - type被忽略,这种格式是假定为纯文本。
HTTP主体内容取决于你使用JSON或纯文本。为JSON,它必须包含一个字符串代表有以下领域的JSON对象:
如果你想要测试你的要求不用传送信息到设备(JSON或纯文本),您可以设置一个可选的值为真的名为
dry_run
的HTTP参数,。不需要参数来运行这个请求结果将是几乎完全相同的,除了消息不会被传给设备。因此,响应会包含消息的fake IDs 和多播字段(参见响应格式)。
服务器响应:
对象数组代表消息处理的地位。对象作为请求使用相同的顺序列出。 (例如,对于每个在请求里的注册ID,其结果在响应里用相同的结果列出),他们可以有这些领域:
·
message_id
: 当消息被成功处理时字符串代表消息。
·
registration_id
:如果设置,那么意味着,GCM处理消息,但它还有另一个规范的注册ID对那个设备,因此,发送者在将来的请求中应该替换IDs (否则他们可能被拒绝)。这个字段是决不会被设置如果出现错误请求。
·
error
:
· 字符串来描述一个处理接收者消息时发生的错误,。可能的值和上表显示的是一样的。再加上“不可用”(即服务器繁忙,GCM不能为特定的接收方处理消息。因此它可以重试)。
如果
failure
和
canonical_ids的值
是0,就没有必要去解析剩余的响应。否则,我们建议您遍历结果字段和为列表中的对象做以下事情:
一如果消息id被设置,检查注册id:
1如果注册ID被设置, 在你的服务器数据库用新值替换原始ID (规范ID)。注意,原始ID不是结果的一部分,因此您需要从请求中传送的注册ID列表中获得它(使用相同的索引)。
二否则,得到error的值:
1如果它是
Unavailable
,你可以使用另一个请求重新发送它。
2如果它是
NotRegistered
,你应该把注册ID从服务器数据库移除,因为应用程序被设备重新安装了。
3否则,注册ID在请求中传输的时候有些错误。这可能是不可恢复的错误,也需要从服务器数据库删除注册。
See
Interpreting an error response
for all possible error values.
当一个纯文本请求成功(HTTP状态代码200),响应主体包含1或2行形式的键/值对。第一行总是可用的,它的内容是
id=
ID of sent message
或者
Error=
GCM error code
.。第二行,如果可用,有格式
registration_id=
canonical ID
of
registration_id=
canonicalID
。
第二行是可选的,只有当第一行没有错误的时候它可以被发送。我们建议用处理纯文本响应的方式处理JSON响应:
一如果第一行开始于ID,检查第二行:
1如果第二行开始于
registration_id,
得到它的值在你的服务器数据库中替换注册IDs
二否则,得到error的值
1如果它是
NotRegistered
,从你的服务器数据库移除你的注册ID
2否则,可能有一个不可恢复的错误。
注意:纯文本请求永远不会返回
Unavailable
作为错误代码,他们将返回一个500 HTTP状态
Interpreting an error response
这里是用于处理当试着发送信息到设备上时可能发生的不同类型的错误的建议:
MissingRegistration ID
检查一个包含注册id的请求(无论是在纯文本消息中的gistration_id参数,或者在JSON中的registration_ids领域里)
错误代码是MissingRegistration时发生 。
Invalid Registration ID
检查你传到服务器的注册ID的格式。确保它匹配手机接收的在
com.google.android.c2dm.intent.REGISTRATION
意图里的注册ID以及确保你不是截断它或者增加额外的字符。
错误代码是InvalidRegistration时发生
MismatchedSender
注册ID被绑定到特定的发送者组,当一个程序注册使用GCM时,它必须允许指定发件人发送消息。确保你使用这些中的一个当你试着发送消息到设备时。如果你转换到了一个不同的发送者,这个存在的注册IDs将不工作。
当错误代码是
MismatchSenderId
时发生
UnregisteredDevice
现有的注册ID在很多情况下可能不再是有效的,包括
1如果应用程序通过分配com.google.android.c2dm.intent.UNREGISTER意图手动注销
2 如果应用程序自动取消登记,这可以发生(但并不保证)如果用户卸载应用程序。
3 如果注册ID到期,谷歌可能刷新注册IDs。
对于所有这些情况下,您应该从第三方服务器删除此注册ID和停止使用它来发送消息。 错误代码是NotRegistered时发生。
Message TooBig
包含在一个消息中的负载数据的整个大小不能超过4096字节,注意,这包括键和值的总大小。
错误代码是
MessageTooBig
时发生。
未注册的设备
现有的registeration ID在以下几种情况下可能失效:
· 如果应用程序通过发出一个意图(com.google.android.c2dm.intent.UNREGISTER)被手动注销.
· 如果应用程序被自动注销,这个有可能发生(但不能保证)是否用户卸载了应用程序.
· 如果 registeration ID过期,谷歌可能会决定刷新registeration IDs.
对于所有这些情况,你应该从第三方服务器删除registeration ID并且停
止使用它来发送信息.错误代码是NotRegistered的时候发生.
信息量太大
在消息中包含的有效载荷数据的总大小不能超过4096字节. 注意它包含键和值两者的大小.
当错误代码是MessageTooBig的时候发生.
存活的有效时间
存活的时间字段的值必须是一个从0到2419200秒(4周)的整数.错误代码为InvalidTtl的时候发生.
你想使用的发送消息的发送人账户可能无法验证.可能的原因是:
· 验证信息的头部丢失或者是无效的语法.
· 作为key发送的无效的project ID.
· key有效但是GCM服务器禁用.
· 发送请求的服务器不在服务器Key IPs的白名单中.
检查你发送的验证信息头部里的标记是否和你的项目相联系的正确的APIkey.你可以通过以下命令来检查你的APIkey的有效性:
#api_key=YOUR_API_KEY
#curl --header "Authorization: key=$api_key" --headerContent-Type:"application/json" https://android.googleapis.com/gcm/send -d"{\"registration_ids\":[\"ABC\"]}"
如果你收到一个401 HTTP状态代码,说明APIkey是无效的. 否则你会看到下面的一些信息:
{"multicast_id":6782339717028231855,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}
如果你想确认registeration ID的有效性,你可以用registeration ID来替换“ABC”.当HTTP状态码是401的时候发生.
服务器无法及时处理请求.你应当重新尝试同样的请求,但是你必须遵守以下规定:
· 尊重重新尝试后的头部如果它包含在GCM服务器的回复中.
· 在你的重试机制中实施指数回退. 这意味着每一次重试之后有个指数增长的延时(例如. 你第一次重试时等待了1秒钟,下次至少等待2秒钟,然后是4秒钟,以此类推). 如果你发送多条信息,通过一个增加的随机量让每一个独立独立延时来避免在同一时间为所有的信息发送一个新的请求.
Senders that cause problems risk beingblacklisted.
当HTTP状态码为503的时候发生, 或者当结果数组的一个JSON对象的错误字段是Unavailable.
内部服务器错误
当尝试处理请求的时候服务器遇到了一个错误.你可以重新尝试相同的请求(服从超时部分列出来的要求),但是如果错误仍然存在,请向报告android-gcm组此问题.
Senders that cause problems risk being blacklisted.
Happens when the HTTP status code is 500, or when the
error
field of a JSON object in the results array is
InternalServerError
.