[color=red][b]声明: 如果您要转载, 请事先征得本人的同意后方可并且请您附上原文链接. 本人保留一切权利. 多谢![/b][/color]
Google从FroYo版本后引入了C2DM (Cloud to Device Messaging)框架:
[url]http://code.google.com/android/c2dm/index.html[/url]
[i]Android Cloud to Device Messaging (C2DM) is a service that helps developers send data from servers to their applications on Android devices. The service provides a simple, lightweight mechanism that servers can use to tell mobile applications to contact the server directly, to fetch updated application or user data. The C2DM service handles all aspects of queueing of messages and delivery to the target application running on the target device.[/i]
这个C2DM框架实际上就是一种Push机制, 当服务器端有changes(creates/updates/deletes)时, 服务器端立刻会通过Push机制通知客户端, 然后客户端会通过Sync机制从服务器端获取server-side diffs, 同时也会把client-side diffs发送给服务器端. 通过Push机制, 用户可以及时获得服务器端的改动, 因此有更好的用户体验.
注:
1. Push机制的实现有多种, 比如通过SMS, 以及现在介绍的C2DM.
2. Android上的Sync机制这里不做过多介绍. 实际上对应下面介绍的内容Google是把Push机制和Sync机制一起使用的.
那么, C2DM的实现原理是什么呢? XMPP!!! 和Android GTalk Client的协议实现一样, 都是XMPP, 并且用的都是同一个XMPP框架, XMPP Stack的实现用的是开源的Smack. 其实, 在FroYo之前, Google的Push机制直接就是在XMPP上面, 只不过在引入了C2DM后, 变成Push机制在C2DM上面, 然后C2DM在XMPP上面了.
[img]http://dl.iteye.com/upload/attachment/408542/312798a8-fea2-301c-8c1e-0d8ff6f9f0a1.png[/img]
下面, 通过介绍Android上Google Contacts/Calendar如何利用C2DM实现Push机制来进行说明. 说的顺序可能比较乱, 大家慢慢理解, 并且其中的code只是用来帮助大家来进行理解.
[b]1. 在AndroidManifest.xml上declare了一个broadcast receiver和一个对应的service用来接收C2DM发送的通知.[/b]
<receiver android:name=".subscribedfeeds.SubscribedFeedsBroadcastReceiver">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.google.android.gsf.subscribedfeeds" />
</intent-filter>
</receiver>
<service android:name=".subscribedfeeds.SubscribedFeedsIntentService" />
Google Sync是通过Atom Feed协议进行的. 这样当Server端有changes后, 会通过C2DM框架发送"com.google.android.c2dm.intent.RECEIVE" action给SubscribedFeedsBroadcastReceiver, 而SubscribedFeedsBroadcastReceiver在onReceive()方法启动SubscribedFeedsIntentService. 其可能的代码为:
public class SubscribedFeedsBroadcastReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")) {
intent.setClass(context, SubscribedFeedsIntentService.class);
context.startService(intent);
}
}
}
关于"com.google.android.c2dm.intent.RECEIVE" action, 这是C2DM框架定义的标准action, 可以参考C2DM开发文档.
[b]
2. C2DM框架什么时候以及如何发送"com.google.android.c2dm.intent.RECEIVE" action.[/b]
这里用到的就是XMPP协议扩展. 这里扩展了XMPP的<message> packet, 在其中加入了自定义的elements. 当Google Server有changes后, 会通过XMPP发送<message>给Android上的XMPP client框架, XMPP client框架会解析<message> packet, 如果发现是关于Google Sync的<message> packet, 则发送"com.google.android.c2dm.intent.RECEIVE" action.
如何接收自定义的XMPP packets呢? 需要首先了解一下Smack的API, 这里不赘述.
2.1 类DataMessageManager
DataMessageManager是一个PacketListener, 当有特定的Packets到达时会调用它的processPacket()方法.
public class DataMessageManager implements PacketListener
{
......
}
2.2 注册DataMessageManager这个PacketListener
PacketTypeFilter filter = new PacketTypeFilter(DataMessage.class);
connection.addPacketListener(this, localPacketTypeFilter);
注意这里的PacketTypeFilter用的是DataMessage, 表示DataMessage这样的XMPP packets才是需要处理的Packets.
2.3 类DataMessage
public class DataMessage extends Message
{
......
private ArrayList<AppData> mAppDataList;
private String mCategory;
private boolean mFromTrustedServer = 0;
private String mPermission;
private String mToken;
......
}
其中override了一个方法:
public String getExtensionsXML() {
StringBuilder buf = new StringBuilder()
buf.append("<data").append(" xmlns=\"")append("google:mobile:data").append("\"");
if (getCategory() != null)
buf.append(" category=\"").append(getCategory()).append("\"");
if (getToken() != null)
buf.append(" token=\"").append(getToken()).append("\"");
if (getPermission() != null)
buf.append(" permission=\"").append(getPermission()).append("\"");
if (this.mFromTrustedServer)
buf.append(" from-trusted-server=\"true\"");
buf.append(">");
int size = getAppDataSize();
for (int i = 0; i < size; i++) {
AppData appData = (AppData)this.mAppDataList.get(i);
buf.append("<app-data key=\"").append(appData.getKey()).append("\" value=\"")
.append(appData.getValue()).append("\" />");
}
buf.append("</data>";
return buf.toString();
}
从这里可以发现, DataMessage对应的是类似下面的XMPP packets:
<message id="zUwxRf4-9" persistent_id="0:1295969265963580%e7a71353002ea1e0" from="google.com" type="headline"><data xmlns="google:mobile:data" category="GSYNC_TICKLE" token="cl_6" from-trusted-server="true"><app-data key="account" value="[email protected]" /></data></message>
也就是说, 当Server端有changes需要客户端来sync的时候, 会发送类似与上面的消息给客户端, 这个消息会被DataMessageManager处理.
而在DataMessageManager的processPacket()方法中, 会再做如下判断:
String category= dataMessage.getCategory();
if ("GSYNC_TICKLE".equals(str2))
category = "com.google.android.gsf.subscribedfeeds";
Intent intent = new Intent("com.google.android.c2dm.intent.RECEIVE");
intent.addCategory(category);
......
context.sendBroadcast(intent);
这个code会认为category为"GSYNC_TICKLE"的XMPP packets才是有关Google Sync的, 并做相应转换.
3. SubscribedFeedsIntentService被启动, 会调用下面的API进行Sync, 后面涉及到的就是Android的Sync机制了, 如果有时间再解释.
ContentResolver.requestSync(......);
大概的时序图为:
[img]http://dl.iteye.com/upload/attachment/408536/2e2403c1-aa98-35e1-865d-50b63c4f70f5.png[/img]