(新手翻译,请批评指正)译文链接:GCM Architectural Overview
GCM架构概览
Android的谷歌云消息服务(Google Cloud Messaging,简称GCM)是一个免费的服务,主要用来帮助开发者从自己的服务器发送消息到安装他们程序的Android设备,并且获取从用户的设备返回到云端的消息。发送的消息一般都是轻量的,用来告诉Android程序有新的数据需要从服务器拉取(例如,朋友上传的一个电影),或者是一个包含4kb及以下的有效载荷数据(所以应用程序如即时消息就可以直接处理掉这个数据)。GCM服务处理各种各样的消息队列,发送给给运行在目标设备上的目标程序。
GCM介绍了谷歌云服务连接服务器(Cloud Connection Server,简称CCS),你可以使用它来配合GCMHTTP服务/端点/APIS。CCS使用XMPP协议(Extensible Messaging and PresenceProtocol, 可扩展消息与存在协议),它提供异步的、双向的消息。更多的内容,见GCM Cloud Connection Server.
想直接使用GCM来处理你的Android程序,见Getting Started的指导。
快速一览
这里是GCM的最基本的特性:
这节展示GCM工作的概览。
下表总结了GCM中关键的术语和概念。划分为以下几类。
组件 |
|
手机设备 |
这是一个运行使用GCM的Android程序的设备。必须是Android2.2以上的的安装Google Play Store的设备,如果低于Android4.0.4版本的设备还必须有至少一个登录的谷歌账户。还可以选择另一种方式用来测试,使用运行在Android2.2以上带有Google APIs的虚拟机。 |
第三方程序服务器 |
一个程序服务器,开发者在他们程序中实现GCM的一个部分。第三方程序服务器通过GCM服务器发送数据到设备的Android程序。 |
GCM服务器 |
从第三方程序服务器获得消息并发送给设备的谷歌服务。 |
凭证 |
|
发送者ID (Sender ID) |
开始章节所说的你从API控制台获得的项目编号。发送者ID在注册过程的时候用来认证一个Android的程序是否被允许发送消息给设备。 |
程序ID (Application ID) |
注册接受消息的Android程序。Android程序能够根据manifest中的包名加以区分。这保证了消息对应了正确的Android程序。 |
注册ID (Registration ID) |
由GCM服务器发行的允许Android程序接收消息的ID。一旦Android程序有了注册ID,它就发送给第三方程序服务器,第三方服务器用来区已经注册了指定接受消息的Android程序。换句话说,一个注册ID已经和一个运行在独一无二的设备上的独一无二的程序绑定了。 |
谷歌用户账户 (Google User Account) |
如果设备运行在低于Android4.0.4版本的系统,为了让GCM工作,设备必须包含至少一个谷歌账户。 |
发送者认证令牌 (Sender Auth Token) |
第三方程序服务器保存了一个API key用来赋予程序服务器接入谷歌服务的权利。这个API key在发送消息的时候被包含在POST请求的头部。 |
通知键 (Notification Key) |
部分用户通知的特性,提供了一个在用户和和运行在他的多个设备的程序实例的映射。这个notification_key就是用来对所有注册ID已经和key关联的了的设备散发通知的令牌。更多和本话题相关的讨论,见User Notification。 |
通知键名(Notification Key Name) |
用户通知特性的一部分。这个notification_key_name就是一个名字或者识别符号(可以被第三方程序用作用户名),用来区分是否对指定的用户是独一无二的。它被第三方用来对单独的用户的注册ID分组。更多和本话题相关的讨论,见User Notifaction。 |
这些过程会在下面进行更详细的描述。
这是一个运行在已经注册了的手机设备上的Android程序接受消息时时间的顺序:
这个注册的intent(com.google.android.c2dm.intent.REGISTER)包含了发送者ID和程序ID。
注意:因为第一次运行的时候没有生命周期方法可以调用,所以注册的intent应该在onCreate()里发送,但是只能在程序没有注册的情况下。
Android程序应该保存这个ID,后来会用来(例如,在onCreate()里检查是否已经注册了)。注意谷歌可能周期性的刷新注册ID,所以你在设计Android程序的时候,要明白com.google.android.c2dm.intent.REGISTRATIONintent 可能要被多次调用。你的Android程序需要能够给与相应的响应。
注册ID一直持续到Android程序自己注销掉,或者直到谷歌为你的Android程序刷新了注册ID。
注意:当用户卸载一个程序的时候,不会自动注销GCM。它只会在GCM服务器尝试发送消息到设备,设备反馈程序被卸载或者没有设置一个广播接收器接受com.google.android.c2dm.intent.RECEIVEintents的时候才会注销。在哪个阶段,你的服务器应该标识设备是注销了的(服务器将会获得一个未注销的错误)。
注意注册ID完全从GCM服务器删除可能需要好几分钟。所以如果第三方服务器这段时间发送消息,会获得一个有效的消息ID,即使这样消息还是不会发送给设备。
程序服务器发送消息给Android程序的时候,下面的事情必须完成:
这里是程序服务器发送一个消息的事件顺序:
一个Android程序能够在它不想接受消息的时候可以注销GCM。
下面是一个设备上的Android程序接受消息的事件顺序:
当手机用户安装了一个支持GCM的Android程序时,Google Play Store将会通知他们这个程序包含支持GCM。他们要想安装这个程序就必须得同意使用这个特性。
在那能够使用GCM这个特性写客户端Android程序之前,你必须得有一个满足一下条件的服务器:
这张讨论第三方程序服务器如何发送消息到一个或者多个手机设备。注意一下几条:
在第三方程序服务器能够发送消息给一个Android程序之前,必须获得这个程序的注册ID。
为了发送消息,程序服务器发送一个到https://android.googleapis.com/gcm/send的POST请求。
消息请求由两部分组成:HTTP头(HTTP header)和HTTP体(HTTP body)。
HTTP头必须包含以下两个条目:
例如
Content-Type:application/json
Authorization:key=AIzaSyB-1uEai2WiUapxCs2Q0GZYzPu7Udno5aA
{
"registration_ids" :["APA91bHun4MxP5egoKMwt2KZFBaFUH-1RYqx..."],
"data" : {
...
},
}
注意:如果Content-Type被声乐了,就默认以纯文本发送。
HTTP体内容取决于你是用JSON还是纯文本。对JSON来说,必须包含一个包含JSON对象的字符串,JSON对象中有以下字段:
字段 |
描述 |
registration_ids |
保存一组接受消息的设备(注册ID)的字符串数组。包含至少一个最多1000个ID。为了发送一个广播消息,你必须使用JSON。为发送一个单独的消息到一个单独的设备,你可以使用包含一个注册ID的JSON对象,或者纯文本(见下面)。一个请求必须包含一个接收者—这个接收者可以是一个注册ID,一个数组的注册ID,或者一个notification_key。 |
notification_key |
一个映射单独用户和多个和用户关联的注册ID的字符串。这允许第三方服务器发送一个单独的消息给一个用户多个程序实例(特别实在多个设备上)。第三方程序服务器能够把notification_key作为消息的目标,而不是注册ID(或者一组注册ID)。对notification_key来说允许的最大的成员数量是10.更多和本话题相关的讨论,见User Notifications。可选字段。 |
notification_key_name |
一个指定用户独一无二的名字或者识别符号(可以被第三方程序用作用户名)。它被第三方用来对一个单独的用户的注册ID分组。如果对于一个项目ID你又多个应用程序的话,每个程序notification_key_name都是独一无二的。这保证了通知只会发送给预定的目标程序。更多的和本话题的讨论,见User Notifications。 |
collapse_key |
当设备离线的时候,用来处理一组类似的消息的任意的字符串(如“可用更新”),这样之后最后的消息才能发送给用户。这是用来防止当用户在线的时候发送给手机太多的消息。注意因为发送消息的顺序是不保证的,所以最后的消息,并不一定是服务器最后发出的。见Advanced Topics for more discussion of this topic。可选字段 |
data |
能够表示消息载荷数据中键值对的JSON对象。如果包含这个字段,载荷数据会被作为程序数据包含在intent里,key作为extra的名字。例如,"data":{"score":"3x1"} 会生成一个名字为score,value为3x1的intent。键值对的数量没有限制,但是消息的总体大小是有限的(4kb)。值可以是任何的JSON对象,但是我们建议使用字符串,因为GCM服务器总会把值变成字符串。如果你想包含对象或者其他非字符串的数据类型(如整型或布尔型),你只能自己转换了。还要注意key不能是一个保留字(任何以google开头的)。为了事情变得复杂一点,有一些保留字(如collapse_key)是允许在载荷数据中出现的。然而,如果请求也包含这个单词,请求中的这个值不会覆盖载荷数据中的值。所以不推荐使用这个表中的字段名,即使技术上是允许的。可选字段。 |
delay_while_idle |
如果包含这个字段,表示如果设备不是空闲的,消息不应该立即发送。服务器一直等到设备活动状态,然后只有最后的一个对应collapse_key值的消息才会发送。可选字段。默认的值是false,但是必须是一个JSON布尔值。 |
time_to_live |
如果设备离线的话消息会被曝存在GCM存储空间中多长时间(秒记)。可选字段(默认是4周,必须是一个JSON数据)。 |
restricted_package_name |
包含你程序包名的字符串。如果设置了,消息只会发送给匹配包名的注册ID。可选字段。 |
dry_run |
如果包含该字段,允许开发者在不发送消息的情况下测试请求。可选字段,默认值为false,必须是JSON布尔值。 |
如果你是使用的纯文本而不是JSON,消息的字段必须设置为HTTP体中的参数,而且他们的语法有些不同,如下:
字段 |
描述 |
registration_id |
必须包含接受消息的设备的注册ID。必须字段。 |
collapse_key |
和JSON一样(见前面的表)。可选字段 |
data. |
载荷数据,表示成有数据位前缀,key为后缀的参数。例如,一个参数data.score=3x1将会导致一个 extra名为score,key为3x1的字符串的intent。键值对参数数量没有限制,但是有消息总体大小的限制。还要注意key不能是一个保留字(任何以google开始的单词)。为了让事情变得稍微复杂,有些保留字(如collapse_key)技术上是允许的。然而,如果请求也包含该词,请求中的值不会覆盖载荷数据中的值。所以使用表中定义的字段是不推荐的,虽然他们技术上是允许的。可选字段。 |
delay_while_idle |
当是真的时候,应该表示为1或者true,其他都是假。默认是假。 |
time_to_live |
和JSON一样(见前面的表)。可选字段。 |
restricted_package_name |
和JSON一样(见前面的表)。可选字段。 |
dry_run |
和JSON一样(见前面的表)。可选字段。 |
如果你想测试你的请求(不管是JSON还是纯文本),而不像发送消息给设备,你可以设置一个可选的HTTP或JSON参数,dry_run,值为true。结果几乎和没有参数是一样的,除了消息不会发送给设备。最终,响应会包含消息的假ID和广播字段(见响应格式一节)。
请求举例
这是一个使用JSON最小的请求(只有接收者,没有任何参数的消息):
{ "registration_ids": [ "42" ] }
这是使用纯文本一样的示例:
registration_id=42
这是一个有6个接收者和载荷数据的示例:
{ "data": { "score": "5x1", "time": "15:10" }, "registration_ids": ["4", "8", "15", "16", "23", "42"] }
这是一个设置了6个接收者,还有一些可选字段的示例:
{ "collapse_key": "score_update", "time_to_live": 108, "delay_while_idle": true, "data": { "score": "4x8", "time": "15:16.2342" }, "registration_ids":["4", "8", "15", "16", "23", "42"] }
这是一个使用纯文本的同样的消息(只有一个接收者):
collapse_key=score_update&time_to_live=108&delay_while_idle=1&data.score=4x8&data.time=15:16.2342®istration_id=42
注意:如果你的组织有防火墙,约束了互联网发送或接收消息,你应该配置一下以使GCM的连接是允许的,以便你的Android设备能够接收到消息。打开的端口为:5228,5229和5230。GCM一般只是用5228,但是有时候会使用5229和5230。GCM不提供特别的IP,所以你应该允许你的防火墙能够接收外部对IP地址的连接,这些IP地址在谷歌ASN15169的IP块中。
当尝试发送消息时有两种可能的结果:
当消息被正确处理的时候,HTTP响应的状态码是200,而且HTTP体中包含了更多消息状态的信息(包括可能的错误)。当请求被拒绝的时候,HTTP相应包含一个非200的状态码(如400,401或503)。
下面的表总结了HTTP响应头可能包含的状态码,点击故障排解链接,查看解决各种类型错误的建议。
响应 |
描述 |
200 |
消息被成功处理。响应体包含更多关于消息状态的细节,但是格式要看是JSON还是纯文本。见成功请求说明一节,查看更多细节。 |
400 |
只对JSON请求适用。表明请求不能被解析为JSON,或者包含了不可用的字段(如,需要数值的时候传递一个字符串)。确切失败的原因在响应里,在重新请求之前,这个问题应该被处理。 |
401 |
验证发送者账户额时候有错误。故障排解。 |
5xx |
状态码在500-599之间(如500或503)的错误表明GCM服务器当尝试处理请求的时候,或者服务器暂时不可用的时候(例如,超时),有内部错误。发送者必须过段时间再重新尝试,在响应里里加入任何稍后尝试的头。程序服务器必须实现二进制指数回避算法。故障排解。 |
正确相应的说明
当一个JSON请求成功了(HTTP状态码200),响应体包含一个包含如下字段的JSON对象:
字段 |
描述 |
multicast_id |
多发消息独一无二的标识(数字)。 |
success |
处理时没有错误的消息的数量。 |
failure |
不能被处理的消息的数量。 |
canonical_ids |
包含一个典型注册ID的结果的数量。更多关于本话题的讨论见Advanced Topics |
results |
表示消息被处理消息状态的对象数组。对象顺序和请求的顺序一样(也就是说,对于请求中的每个注册ID,它的结果也在相应的同样的位置),他们有以下字段:
|
如果错误的值和canonical_ids都是0,就没有必要解析响应其他的字段了。不然,我们建议你循环访问结果字段并对对象做下面的操作:
当一个纯文本的请求成功了(HTTP状态码200),响应体包含1或2行的键值对。第一行一般时可用的,他的内容一般是id=ID或者Error=GCM错误码。如果第二行有的话,个职位registration_id=canonicalID。第二行是可选的,而且知呢个在第一行没有错误的情况下发送。我们建议和处理JSON类似的方法一样处理纯文本相应:
错误响应的说明
这是处理发送消息到一个设备时可能出现的各种类型的错误的推荐方法。
缺少注册ID
检查确定请求包含注册ID(在纯文本消息的registration_id参数中或者JSON的registration_id字段中)
发生在错误码是MissingRegistration时。
不可用的注册ID
检查发送给服务器的注册ID的格式。确保和手机在com.google.android.c2dm.intent.REGISTRATIONintent接收到的注册ID匹配,没有截断或者添加字符。
发生在错误码是InvalidRegistration时。
不匹配的发送者
和一个特定的发送这组绑定的注册ID。当一个程序注册了GCM,它必须指定哪个发送者是允许发送消息的。确保你发送消息到设备时用的是这其中的一个。如果你切换到不同的发送者,已经存在的注册ID不可用。
发生在错误码为MismatchSenderId时。
未注册的设备
一个已经存在的注册ID在以下情况可能是不可用的:
· 程序自己通过发送com.google.android.c2dm.intent.UNREGISTERintent注销了。
· 程序自动注销了,这可能会发生(不保证)在用户卸载程序的时候。
· 注册ID到期了。谷歌可能会刷新注册ID。
· 程序更新,但是新版本的没有配置广播接收器接受com.google.android.c2dm.intent.RECEIVEintents。
对于以上所有的例子,你应该从第三方服务器中删除注册ID,并且停止使用它发消息。
发生在错误码为NotRegistered时。
消息太大
消息中载荷数据的总体大小不能超过4096字节。注意这个同时包含键值的大小
发生在错误码为MessageTooBig时。
不可用的数据键(key)
载荷数据包含一个键(如任何以google为前缀的值),被GCM内部使用在com.google.android.c2dm.intent.RECEIVE Intent 中,不能被使用。注意一些单词(如collapse_key)也被GCM使用,但是允许在载荷数据中使用,这种情况下载荷数据值会被GCM值覆盖掉。
发生在错误码为InvalidDataKey时。
不可用的存活时间
存活时间字段的值必须是一个整型,表示从0到2,419,200(4周)之间一个持续时间。
发生在错误码为InvalidTtl时。
验证身份错误
用来发送消息的发送者账户不能被验证。可能的原因:
· 验证头部丢失或者语法错误
· 设置的key是不可用的项目编号
· Key可用但是GCM服务不可用
· 从服务器发起的请求不在服务器Key IP的白名单里。
检查发送时验证头部的标记,你的项目编号关联的API是否正确。你可以运行下面的命令来检测你的API key是否可用:
# api_key=YOUR_API_KEY # curl --header "Authorization: key=$api_key" --header Content-Type:"application/json" https://android.googleapis.com/gcm/send -d "{\"registration_ids\":[\"ABC\"]}"
如果你收到了401的HTTP状态码,你的API key不可用。否者你应该看到类似下面的结果:
{"multicast_id":6782339717028231855,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}
如果你想确定注册ID的可用性,你可以替换把"ABC"替换成注册ID。
发生在HTTP状态码为401时。
超时
服务器不能即时的处理请求。你因该重试相同的请求,但是你必须遵守一下要求:
· 如果没有被包含在GCM服务器返回的响应里,加上Retry-After的头。
· 在你的重试机制中实现二进制指数退避算法。这意味着每次重试失败之后,以指数方式增加延时(例如,如果你第一次等待了1s,然后第二次等待至少2s,然后4s,继续下去)。如果你在发送多发消息,使用一个额外随机的数量分别对每个延时,防止同时又产生一个对所有消息的请求。
引起问题的发送者可能会被列入黑明白
发生在HTTP状态码为501到599之间时,或者在结果数组里的JSON对象的错误字段不可用时。
服务器内部错误
当尝试处理请求的时候,服务器晕倒了错误。你应该重试相同的请求(遵循超时一节里的要求),但是如果一直错误,请向android-gcm组报告错误。
发生在HTTP状态为500,或者结果数组中的JSON对象错误字段为InternalServerError时。
不可用的包名
消息发送给一个注册ID时,它的包名和请求中传递的值不匹配。
发生在错误码为InvalidPackageName时。
响应举例
这节展示了几个被成功处理的消息的相应的实例。见请求举例,响应的示例基于请求示例:
这是一个发送给一个接收者的JSON消息示例,响应中没有canonical ID字段:
{ "multicast_id": 108, "success": 1, "failure": 0, "canonical_ids": 0, "results": [ { "message_id": "1:08" } ] }
或者请求是纯文本的格式:
id=1:08
这是发送给6个接收人(ID分别为4,8,15,16,23和42)的消息成功诶处理后的响应。1个 canonicalregistration ID 返回,还有3个错误:
{ "multicast_id": 216, "success": 3, "failure": 3, "canonical_ids": 1, "results": [ { "message_id": "1:0408" }, { "error": "Unavailable" }, { "error": "InvalidRegistration" }, { "message_id": "1:1516" }, { "message_id": "1:2342", "registration_id": "32" }, { "error": "NotRegistered"} ] }
这个例子中:
或者如果上述第四个使用纯文本发送的消息:
Error=InvalidRegistration
如果上述第五个消息也是使用纯文本发送的:
id=1:2342 registration_id=32
为了查看GCM程序的统计或者其他错误信息:
你将看到你所有程序列表页。
现在你在统计页面了。
注意:谷歌API控制台的状态对GCM不可用。你必须使用开发者控制台。