第一篇:
原文地址:
C2DM功能要求Android版本在2.2以上,同时设备中需要安装Android Market。整个架构包括3部分,手机端应用程序,支持手机端应用程序的服务器端程序,google的C2DM服务器。结构图如下:
先大概解释一下,在接下来的文档中会给出详细实现代码。
基本分为6步:
- 手机端app带着google account向C2DM服务器注册;
- C2DM服务器返回Register ID到手机端;
- 手机端将获取的Register ID发送到第三方的服务器端;
- 通过浏览器等其他方式将需要发送的data发送到第三方服务器端;
- 第三方服务器将Register ID,data发送到C2DM服务器(要求第三方服务器需要有处理消息队列的能力);
- C2DM服务器将收到的data发送到手机端。
第二篇:
原文地址:http://cykoradise.com/blog/tag/c2dm/
从Android 2.2开始,提供了Cloud To Device Messaging(C2DM)的API。Android
Developers上提供了一篇关于其实现机制的文章,作者是个华裔(google有很多………)
对于用户而言,C2DM的实现非常简单:
-
应用程序对应的网站向google的C2DM Server发出一个1KB大小的消息,这个消息的主要目的是触发手机上应用程序的动作,而非直接传递信息。
-
C2DM Server将这个包转发给相应的手机。
-
手机收到之后,触发C2DM的intent,调用相应的程序处理。
与竞争对手的方案比较,Android的实现有以下优点:
-
BlackBerry的短信触发机制:Android不需要运营商支持,大大提高了应用的灵活性。
-
iPhone的类似机制:由于Android天生的Intent机制,C2DM不需要应用程序聆听,系统收到消息后会自动启动应用程序,大大降低了对性能、耗电的要求,对用户而言也更加方便。
用户使用C2DM付出的代价主要有两点:
-
与google C2DM Server保持连接带来的电力和流量开销。事实上流量开销极小,可以不计。由于C2DM是内建在Android目前(用于同步联系人、日程表、gmail等)的同步机制中的,因此不会带来额外的电力开销。
-
应用程序启动后动作带来的电力和流量开销。这一点主要取决于应用程序自身的设计,还有就是网络条件带来的影响(如果网络质量不佳,会产生多次连接和连接不上的情况)。另外,由于应用程序安装后需要与C2DM服务器注册,因此客户可以选择关闭应用程序的C2DM功能。
总体而言,C2DM算是目前Push信息最佳的解决方案了。Google Code上的是第一个基于C2DM的实现,在桌面的Chrome浏览器安装相应的扩展,并在Android
2.2以上的手机上安装了chrometophone的apk之后,就可以在桌面浏览器上将当前网址或Google Maps的地址发送给手机,直接在手机上打开,而这一切只需要一键。
第三篇:
在中给出了C2DM的实现架构,这里写了一个最简单的客户端例子,项目目录如下:
说一下实现步骤:
com.easymorse.myc2dm.permission.C2D_MESSAGE” android:protectionLevel=”signature” />
com.easymorse.myc2dm.permission.C2D_MESSAGE” />
android:label=”@string/app_name”>
=”.C2DMReceiver” />
com.easymorse.myc2dm” />
com.easymorse.myc2dm” />
红色标记的部分需要根据自己的情况修改为相应的包名。
MainActivity中的主要代码如下:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// C2DMessaging.register(this, Config.C2DM_SENDER);
Intent registrationIntent = new Intent(”com.google.android.c2dm.intent.REGISTER”);
registrationIntent.putExtra(”app”, PendingIntent.getBroadcast(this, 0, new Intent(), 0)); // boilerplate
registrationIntent.putExtra(”sender”, DeviceRegistrar.SENDER_ID);
startService(registrationIntent);
}
其中sender对应的值是通过连接申请过的gmail帐号。
- 通过IntentService接受Register id和C2DM服务器push的消息。
public class C2DMReceiver extends C2DMBaseReceiver {
public C2DMReceiver() {
super(DeviceRegistrar.SENDER_ID);
Log.v(”tag”,”c2dm>>>>>C2DMReceiver”);
}
@Override
public void onRegistered(Context context, String registration) {
DeviceRegistrar.registerWithServer(context, registration);
Log.v(”tag”,”c2dm>>>>>C2DMReceiver>>>>>>>onRegistered and the id is : “+registration);
}
@Override
public void onUnregistered(Context context) {
String deviceRegistrationID = null;
DeviceRegistrar.unregisterWithServer(context, deviceRegistrationID);
Log.v(”tag”,”c2dm>>>>>C2DMReceiver>>>>>>>onUnregistered”);
}
@Override
public void onError(Context context, String errorId) {
context.sendBroadcast(new Intent(”com.google.ctp.UPDATE_UI”));
}
@Override
public void onMessage(Context context, Intent intent) {
Log.v(”tag”,”c2dm>>>>>C2DMReceiver>>>>>>>onMessage”);
Bundle extras = intent.getExtras();
if (extras != null) {
String url = (String) extras.get(”url”);
Log.v(”tag”,”c2dm>>>>>C2DMReceiver>>>>>>>onMessage>>>>url is “+url);
Editor sharedata = getSharedPreferences(
“myc2dm”, 0).edit();
sharedata.putString(”serverdata”,url);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new Notification(R.drawable.icon,
“收到消息”, System.currentTimeMillis());
notification.setLatestEventInfo(getApplicationContext(), “消息内容”,
url, PendingIntent.getActivity(
C2DMReceiver.this, 0,
new Intent(C2DMReceiver.this,MainActivity.class), 0));
notification.flags|=Notification.FLAG_AUTO_CANCEL;
notification.defaults |= Notification.DEFAULT_SOUND;
manager.notify(1, notification);
}
}
}
onRegistered接受C2DM发送的Register id,onMessage接受C2DM发送的data数据,这里可以根据自己情况做出相应的处理,这里给出简单的信息提示。
例子比较简单,需要完善的功能是在DeviceRegistar中将收到的Register id发送到自己的服务器端。
项目源码见:
第四篇:
原文地址:http://blog.csdn.net/ichliebephone/article/details/6635913
一.基础知识
在前一部分,我们使用curl命令来代替了服务器端的实现,虽然在测试时使用curl命令是一个很是简单方便的模拟方式,但实际使用中我们需要把C2DM相关的服务器部分功能结合到已有的框架中,因此需要使用具体的代码来实现。
第三方服务器端部分的功能主要是通过C2DM服务器向客户端发送要推送的数据。
为了发送数据,第三方服务器需要向这个地址发送一个POST请求,其中POST的内容包含:
registration_id:是客户端发送过来的registration_id值。必须包含。
collapse_key:一个任意的字符串,用来表示一组相似的消息。当Android设备由离线到上线时,之前使用相同collapse_key推
送的消息,只有最后一条才会推送给Android设备。设置这个值用来避免Android设备上线时收到太多已经过时的消息。必须包含。
data.:要推送的数据内容,以键值对的方式组织。当客户端程序接收时,就通过键值来获取对应的内容。一条推送消息中包含的键值对数目没有限制,虽然整体的数据大小有限制。可选。
delay_while_idle:如果包含这项,则表明当Android设备idle时,C2DM服务不会立即把消息推送给设备而是等到设备重新变回active。可选。
Authorization: GoogleLogin auth=[AUTH_TOKEN]:HTTP头部要包含的信息,是为SenderID申请的C2DM(服务代码为ac2dm)服务权限,这个需要提前获取。必须包含。
因此第三方服务器就是构造这样的POST请求,然后向C2DM服务器发送。
在这部分中,我们就使用java代码的方式实现之前使用curl模拟的第三方服务器功能。
二.实例开发
创建一个Java工程,工程名为C2DMMessageServer,新建包名com.ichliebephone.server.c2dmmessage,并新建一个类C2DMMessageServer。
第三方服务器端的和C2DM相关的功能可以分为两个,第一个是获取注册使用C2DM功能的用户账号的ClientLogin权限Auth值;第二个是按格式给C2DM服务器发送要Push的数据。
我们先来看下获取Auth权限的方法实现:
-
-
public static String getAuthToken(String url, String params) throws IOException{
-
String auth = null;
-
-
byte[] postData = params.getBytes();
-
-
URL requestUrl = new URL(url);
-
HttpURLConnection connection = (HttpURLConnection)requestUrl.openConnection();
-
connection.setDoOutput(true);
-
connection.setUseCaches(false);
-
connection.setRequestMethod("POST");
-
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
-
connection.setRequestProperty("Content-Length", Integer.toString(postData.length));
-
-
OutputStream out = connection.getOutputStream();
-
out.write(postData);
-
out.flush();
-
out.close();
-
-
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
-
String responseLine;
-
StringBuilder responseDataBuidler = new StringBuilder();
-
while((responseLine=reader.readLine())!=null){
-
responseDataBuidler.append(responseLine);
-
}
-
int responseCode = connection.getResponseCode();
-
System.out.println("auth responseCode = "+responseCode);
-
if(responseCode == 200){
-
-
int authStartIndex = responseDataBuidler.indexOf("Auth=");
-
auth = responseDataBuidler.substring(authStartIndex+5);
-
-
} else{
-
-
System.out.println(responseDataBuidler);
-
}
-
return auth;
-
}
关于获取ClientLogin的说明参考谷歌的,方法的第一个参数url为请求的地址:,
第二个参数params为POST时带的内容,包括accountType(申请权限的账户类型),Email(账户邮箱地址),Passwd(账户邮箱
密码),service(申请权限的服务类型,这里申请C2DM服务,值为ac2dm),source(表示我们应用程序的一个简短字符串)。这里我传入
的值为"accountType=HOSTED_OR_GOOGLE&Email=android.c2dm.demo@gmail.com&
amp;Passwd=androidc2dmdemo&service=ac2dm&source=bupt-c2dmdemo-1.0";
在getAuthToken方法中首先新建HttpURLConnection连接,构造POST请求,设置好参数,写入POST数据,然后获取并处理请求返回的内容。根据ClientLogin的官方文档,成功返回的response形式为:
HTTP/1.0 200 OK
Server: GFE/1.3
Content-Type: text/plain
SID=DQAAAGgA...7Zg8CTN
LSID=DQAAAGsA...lk8BBbG
Auth=DQAAAGgA...dk3fA5N
没有成功请求返回的response形式为:
HTTP/1.0 403 Access Forbidden
Server: GFE/1.3
Content-Type: text/plain
Url=
Error=CaptchaRequired
CaptchaToken=DQAAAGgA...dkI1LK9
CaptchaUrl=Captcha?ctoken=HiteT4b0Bk5Xg18_AcVoP6-yFkHPibe7O9EqxeiI7lUSN
因此这里我们根据返回的responseCode判断,如果是200则表明请求成功,从返回的数据中提取我们需要的Auth值。如果不是200则表明请
求出错,这里只是简单的打印出出错的信息。实际使用中需要更加错误的信息做相应的处理,比如是服务器端没响应的话那就等待一段时间重新尝试,等等。
获取了Auth权限值,接了下来我们就可以给C2DM服务器发送我们想要其Push的数据了,实现向C2DM服务器发送数据的方法为:
-
-
public static boolean sendPushMessage(String url, String registration_id, String collapse_key, String auth, Map data) throws IOException{
-
boolean flag = false;
-
-
StringBuilder postDataBuidler = new StringBuilder();
-
postDataBuidler.append("registration_id").append("=").append(registration_id);
-
postDataBuidler.append("&").append("collapse_key").append("=").append(collapse_key);
-
for(Object keyObject:data.keySet()){
-
String key = (String)keyObject;
-
if(key.startsWith("data.")){
-
String value = data.get(key);
-
postDataBuidler.append("&").append(key).append("=").append(value);
-
}
-
-
}
-
byte[] postData = postDataBuidler.toString().getBytes();
-
-
URL requestUrl = new URL(url);
-
HttpURLConnection connection = (HttpURLConnection)requestUrl.openConnection();
-
connection.setDoOutput(true);
-
connection.setUseCaches(false);
-
connection.setRequestMethod("POST");
-
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
-
connection.setRequestProperty("Content-Length", Integer.toString(postData.length));
-
connection.setRequestProperty("Authorization", "GoogleLogin auth="+auth);
-
-
OutputStream out = connection.getOutputStream();
-
out.write(postData);
-
out.flush();
-
out.close();
-
-
int responseCode = connection.getResponseCode();
-
System.out.println("c2dm responseCode = "+responseCode);
-
if(responseCode == 200){
-
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
-
String responseLine;
-
StringBuilder responseDataBuidler = new StringBuilder();
-
while((responseLine=reader.readLine())!=null){
-
responseDataBuidler.append(responseLine);
-
}
-
if(responseDataBuidler.toString().startsWith("id=")){
-
flag = true;
-
}
-
System.out.println(responseDataBuidler);
-
} else if(responseCode == 503){
-
System.out.println("The server is temporarily unavailable, please try later");
-
} else if(responseCode == 401){
-
System.out.println(" the ClientLogin AUTH_TOKEN used to validate the sender is invalid");
-
}
-
return flag;
-
}
关于第三方服务器向C2DM发送数据的说明可以查看C2DM的,其中发送的URL为,因为使用https的方式会提示出错,因此使用http的方式,URL为,
使用POST方式提交要包含的内容有registration_id(Android应用程序注册获得的id),collapse_key(用任意一个字符传表示一组类似的消息,如果Android设备离线了,那等其上线后这组消息中只有最后一条消息才会被Push给Android设备,这样重新上线后的Android设备不会收到太多之前过时的消息),ClientLogin auth(上
一步获取的使用C2DM功能的账户权限),data(这是一个键值对形式表示的数据,形式比如为data.msg=ichliebejiajia,其中键
值需要和Android设备上的程序统一化,以便其可以获取解析,这是真正需要Push给Android设备的数据)。
在sendPushMessage方法中,首先构造POST的数据,然后创建HttpURLConnection连接,以POST的方式发送请求,最后
接收并处理请求返回的数据。根据官方文档的说明,当返回的responseCode为200,并且返回的数据以id=开头时,才算数据发送成功。
图1 C2DM服务器返回的结果
当responseCode为200,但返回的数据不是以id=开头时,则表明向C2DM发送数据错误,返回的数据包含错误代码,Error=[错误代码]。错误代码可能为:
QuotaExceeded:超过发送的配额了,等待一会后重新尝试。在C2DM服务注册页面,我们可以看到有两项是填写和配额相关的内容:一项是估计
每天发送的总条数(Estimated total number of messages per
day?);另一项是每秒钟发送的峰值条数(Estimated peak queries per second (QPS))。
DeviceQuotaExceeded:给某一个特定的设备发送了太多的信息,等待一会后重新尝试。
InvalidRegistration:缺少或者是错误的registration_id。如果是这个错误,那服务器应该停止给这个registration_id对应的设备发送信息。
NotRegistered:registration_id值不再有效,比如客户端程序的使用者关闭了推送通知接收功能或者卸载了客户端程序时。此时服务器也应该停止给这个registration_id对应的设备发送信息。
MessageTooBig:发送的信息太大了,因为信息的长度限制为不大于1024字节。需要减少信息的长度。
MissingCollapseKey:POST的内容中缺少collapse_key值。
当responseCode是503时,表示C2DM服务器没有响应,此时可以等待一段时间后重新尝试。
当responseCode为401时,表示ClientLogin的Auth值无效。
除了发送成功外,其他情况我们打印出对应的错误说明。在实际使用时,我们需要更加具体的错误情况来做具体的处理,比如当提示超过配额(如果为峰值配额)时,等待一段时间重新尝试;当服务器没有响应时,等待一个回退时间再重新尝试,等等。
实现了获取ClientLogin的Auth值和向C2DM服务器发送需要Push的数据两个功能后,接下来我们就可以使用代码的方式来完成下之前用curl命令模拟的第三方服务器功能。C2DMMessageServer的main方法为:
-
public static String ClientLoginURL = "";
-
public static String AuthTokenParams = "accountType=HOSTED_OR_GOOGLE&Email=android.c2dm.demo@gmail.com&Passwd=androidc2dmdemo&service=ac2dm&source=bupt-c2dmdemo-1.0";
-
public static String C2DMServerURL = "";
-
public static String Registration_ID = "APA91bGUBoSvt3G5Ny9t0IGLmIKAKYX6G6VHwSQHh3tP2fqcaQ0N4GPdKh5B3RDUHFCFF06YwT8ifOP_cOy5BAWyCLHL8d8NpuIW9AqXt9h2JSBVF2MitZA";
-
-
-
-
public static void main(String[] args) {
-
-
System.out.println("The C2DMMessageServer is started...");
-
try {
-
String authToken = getAuthToken(ClientLoginURL, AuthTokenParams);
-
if(authToken==null){
-
System.out.println("Can not get the Auth");
-
return;
-
}
-
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
-
while(true){
-
System.out.println("Please input the message to push:(Exit with q)");
-
String message = reader.readLine();
-
if(message.equalsIgnoreCase("q")){
-
break;
-
}
-
Map data = new HashMap();
-
data.put("data.msg", message);
-
if(sendPushMessage(C2DMServerURL, Registration_ID, "1", authToken, data)){
-
System.out.println("Send Successfully");
-
}else{
-
System.out.println("Send Failed");
-
}
-
-
}
-
} catch (IOException e) {
-
-
e.printStackTrace();
-
}
-
-
"text-align: left;">
-
"text-align: left;"> }
在开始处我们定义了几个相关的静态变量,其中在实际使用的时候AuthTokenParams中的Email和Passwd需要换成你自己申请C2DM
时的邮箱和密码,Registration_ID也要换成你的Android设备向C2DM注册时返回的值。
在main方法里,首先获得Auth值,然后提示从终端中输入要发送给C2DM服务器Push的信息,最后调用sendPushMessage方法进行实现。
打开之前我们使用过的Android模拟器,然后运行C2DMMessageServer程序,运行后首先会去获取ClientLogin的Auth值,获取成功的话会显示200的返回码,并且提示可以输入想要Push的信息了:
图2 输入要Push的信息
然后我们可以试着输入我们想要Push的数据,比如依次输入两个数据:
图3 输入了两个要Push的信息
我们可以在DDMS中看到分别收到了这两个Push的数据:
图4 DDMS中显示接收到的Push信息
并且Android模拟器中的通知栏中也会依次显示对应的通知:
图5 模拟器通知栏总显示接收到的Push信息
这样我们就单独实现了之前用curl两次命令实现的Server端的两个功能,现在只要运行Server端程序,输入想要输入的信息,然后Android设备上就可以收到相应的Push信息了。
三.总结
在上面这部分介绍中,我们实现了第三方服务器端向C2DM服务器发送要推送的数据的相关功能。主要是实现了获取ClientLogin的Auth值和构
造向C2DM发送的HTTP
POST请求,不过在实际使用中,还要增加接收客户端程序发送过来的registration_id值,并且对registration_id值和
ClientLogin的Auth值进行保存。同时可能还要从其他地方获取并处理需要进行推送的数据等。不过熟悉了和C2DM相关的最主要的部分之后,如
果你本来就从事服务器端开发的话,应该是比较好实现C2DM推送功能并且整合到你的系统中。
通过这三部分的学习,我们对Android的C2DM特性有了一定的了解,在需要使用推送功能时可以考虑C2DM的方式。不过目前C2DM还处于实验室
状态,在实际的大规模Push使用中可能还有一些限制,比如单次Push消息的大小限制在1024字节;对一个sender能给Push的总条数有限制;
并且一个sender对某一个特定的Android设备的Push条数也有限制等。
以上我们学习了C2DM的总体功能及实现,具体的细节还需要我们在实际使用中进一步学习。
文章对应的完整代码例子下载地址:
第五篇:
原文 地址:http://blog.csdn.net/ichliebephone/article/details/6626864
一.基础知识
在前一部分中,我们从整体上快速介绍并实现了下Android C2DM的Push功能,在接下来的部分里,我们先来回顾一下C2DM相关的整体上的知识,然后具体介绍说明实现的过程。
在前面的C2DM框架说明中,我们已经知道,要实现Android的C2DM推送功能,需要涉及到三个功能实体:
1. Android设备:推送消息的接收端,在上面会运行我们的客户端程序
2. 第三方服务器:这是我们自己控制的服务器,推送消息的发送端,利用C2DM服务器发送我们要推送的消息
3. C2DM服务器:这是Google已经实现好的服务器,接收我们服务器的数据并把他们发送给对应的Android设备
这三个功能实体部分,其中C2DM服务器是谷歌开发并且已经实现好的,我们只需按其要求的格式与其进行交互即可。我们自己要开发的为另两个实体部分:Android设备上运行的客户端程序的开发和实现第三方服务器上的功能。
并且在整个完整的C2DM推送过程中,要涉及到一些验证用的信息:
1. Sender ID:这是我们前面说过的在注册的账号,这个Sender ID主要用来当Android设备上的客户端程序向C2DM服务器注册的时候验证其有使用C2DM服务的权限。
2. Application ID:使用C2DM功能的完整应用程序名,主要用来确保接收到的Push信息绑定到正确的应用程序。
3. Registration ID:这是Android设备上的客户端程序向C2DM服务器注册成功后返回的ID,然后客户端程序需要把这个ID发送给第三方服务器,然后第三方服务器使用这个ID值来向这个设备推送信息。
4. Google User Account:需要在Android设备上登录的谷歌账户,因为C2DM服务是通过已经建立连接的谷歌后台服务来找到对应消息要推送的设备。这个账户验证信息只要在设备上登陆即可,不需要在客户端程序中出现。
5. Sender Auth Token:这是第一个Sender ID对应的使用C2DM服务的权限,在第三方服务器的程序中向Google申请,并且向C2DM服务器发送要推送的消息时要附带这个信息。
这5个和验证相关的信息中,前4个在Android设备上的客户端程序中都有相关,第三方服务器上的程序要使用第3个和第5个验证信息。
最后我们再从整体上来看下Cloud-To-Device Message的主要处理过程,更概括的话可以分为三个步骤:
1. 使能C2DM功能:第一步为Android设备上的客户端程序向C2DM服务器注册,允许接收C2DM的推送消息。
2. 发送推送消息:第二步为第三方服务器通过C2DM服务器向Android设备发送推送信息。
3. 接收推送信息:第三步为Android设备上的客户端程序接收来自C2DM服务器的推送消息。
其中第一步和第三步是在Android设备上的客户端程序中实现,第二步是在第三方服务器上实现。
我们知道完整的C2DM推送功能要涉及Android设备客户端和第三方服务器两方面程序的开发,下面我们首先来具体学习客户端部分的代码开发。
二.客户端开发说明
客户端要实现两个步骤,使能C2DM功能和接收推送消息。
使能C2DM功能,即客户端程序向Google的C2DM服务器注册C2DM服务,使程序允许接收推送消息,过程包含以下三个步骤:
1. 首先客户端程序需要向C2DM服务器启动注册需要的registration Intent。
这个registration Intent(com.google.android.c2dm.intent.REGISTER)需要包含两个内容信息:一个是Sender ID;另一个是Application ID;即我们上面说到的验证信息的前两个。
2. 如果注册成功,C2DM服务器会广播一个com.google.android.c2dm.intent. REGISTRATION Intent,我们的客户端程序需要响应并接收这个Intent,并且从其中获取注册成功返回的Registration ID。
为了后面的使用,客户端程序需要保存这个Registration ID。因为Google可能不定时更新Registration ID值,并通过REGISTRATION Intent进行告知,因此我们的程序需要能进行对应的响应,获取新的Registration ID值并更新保存。
3. 为了完成注册过程,最后一步是我们的客户端程序需要把获得的Registration ID值发送给我们的第三方服务器,并且一般来说第三方服务器要把Registration ID值保存在数据库中。
客户端程序也可以发送com.google.android.c2dm.intent.UNREGISTER Intent取消注册,从而不再接收C2DM服务器发送的推送信息。
Android设备接收推送信息的过程包含以下三个步骤:
1. Android系统获取C2DM服务器推送过来的信息,并且从信息内容中提取键值对数据。
2. Android系统向对应的客户端程序发送com.google.android.c2dm.intent.RECEIVE Intent并在其中包含键值对数据。
3. 客户端程序响应RECEIVE Intent并从中提取出键值对数据,最后根据之前就和发送数据的第三方服务器端商量好的键值,提取对应的数据。
前面介绍了很多相关知识,接下来我们重新实现一下客户端的代码。
三.实例开发
为了能继续使用之前的Sender ID邮箱及已经注册好的应用程序名等信息,我们还是创建一样名为AndroidC2DMDemo的工程。先删除原来的工程或者把Eclipse切换到另一个Workspace下。
创建AndroidC2DMDemo工程,并且包名为com.ichliebephone.c2dm,Min SDK Version选择8。
为了使用C2DM服务,客户端程序要进行两部分处理,
1. 在Manifest.xml文件中声明和C2DM相关的权限。
2. 在Java代码中实现C2DM相关的功能,如前面说的:
a)和C2DM注册相关的代码
b)接收C2DM服务器推送信息相关的代码
下面我们先来实现Java代码部分。
新建一个类C2DMRegistration,用来实现C2DM注册相关功能。
-
public class C2DMRegistration {
-
-
-
public static void register(Context context, String senderID){
-
Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");
-
registrationIntent.putExtra("app", PendingIntent.getBroadcast(context, 0, new Intent(), 0));
-
registrationIntent.putExtra("sender", senderID);
-
context.startService(registrationIntent);
-
}
-
-
public static void unregister(Context context){
-
Intent unregIntent = new Intent("com.google.android.c2dm.intent.UNREGISTER");
-
unregIntent.putExtra("app", PendingIntent.getBroadcast(context, 0, new Intent(), 0));
-
context.startService(unregIntent);
-
}
-
-
static void setRegistraionID(Context context, String registrationId){
-
final SharedPreferences prefs = context.getSharedPreferences(
-
"c2dm_preference",
-
Context.MODE_PRIVATE);
-
Editor editor = prefs.edit();
-
editor.putString("dm_registration", registrationId);
-
editor.commit();
-
}
-
-
public static String getRegistrationID(Context context){
-
final SharedPreferences prefs = context.getSharedPreferences(
-
"c2dm_preference",
-
Context.MODE_PRIVATE);
-
String registrationId = prefs.getString("dm_registration", "");
-
return registrationId;
-
}
-
-
static void clearRegistrationId(Context context) {
-
final SharedPreferences prefs = context.getSharedPreferences(
-
"c2dm_preference",
-
Context.MODE_PRIVATE);
-
Editor editor = prefs.edit();
-
editor.putString("dm_registration", "");
-
editor.commit();
-
}
-
-
-
static long getBackoff(Context context) {
-
final SharedPreferences prefs = context.getSharedPreferences(
-
"c2dm_preference",
-
Context.MODE_PRIVATE);
-
-
return prefs.getLong("back_off", 30000);
-
}
-
-
static void setBackoff(Context context, long backoff) {
-
final SharedPreferences prefs = context.getSharedPreferences(
-
"c2dm_preference",
-
Context.MODE_PRIVATE);
-
Editor editor = prefs.edit();
-
editor.putLong("back_off", backoff);
-
editor.commit();
-
}
-
}
这个类主要实现了下向C2DM服务器发起和取消注册,并且本地保存、清除和获取注册成功获得的registration_id值,同时还有一个和重新启动注册相关的回退时间值的设置与获取。
其中注册方法很简单,就是发送一个com.google.android.c2dm.intent.REGISTER的Intent,其中包含两个参数,一个是在C2DM网页上注册的Sender ID邮箱,另一个是程序的ID值。
取消注册的方法就是发送一个带有程序ID值的com.google.android.c2dm.intent.UNREGISTER这样的Intent。
并且使用Perference键值对的方式保存获取的registration_id值。
接着再新建一个类C2DMReceiver,用来处理接收到的C2DM服务器的数据。
客户端程序会接收到C2DM服务器的两种类型数据,并且这两种类型的数据都是通过Intent的方式来处理的。一种类型是向C2DM服务器注册后的回调
数据,这时Intent对应的Action为com.google.android.c2dm.intent.REGISTRATION;另一种类型是
C2DM正式的推送数据,此时Intent对应的Action为com.google.android.c2dm.intent.RECEIVE。
因为C2DMReceiver主要是用来接收Intent,因此需要扩展自BroadcastReceiver。对应的onReceive方法主要就是判断接收C2DM的两种类型数据:
-
@Override
-
public void onReceive(Context context, Intent intent) {
-
-
if(intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")){
-
-
handleRegistration(context, intent);
-
}else if(intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")){
-
-
handleMessage(context, intent);
-
}
-
}
当是接收到注册返回的Intent时,就调用handleRegistration方法:
-
-
private void handleRegistration(final Context context, Intent intent){
-
-
final String registrationId = intent.getStringExtra("registration_id");
-
-
String error = intent.getStringExtra("error");
-
-
String removed = intent.getStringExtra("unregistered");
-
-
Log.v(TAG, "handleRegistration");
-
Log.v(TAG, "dmControl: registrationId = " + registrationId +
-
", error = " + error + ", removed = " + removed);
-
-
if(removed != null){
-
-
onUnregistrated(context);
-
return;
-
}else if(error != null){
-
-
onError(context, error);
-
return;
-
}else{
-
-
onRegistrated(context, registrationId);
-
}
-
-
}
通过获取注册后返回的Intent中数据,来判断是注册失败(error键值存储的内容不为空)、取消注册(unregistered键值存储的内容不为空)还是注册成功(registration_id键值存储的内容不为空)。
当是取消注册时,调用onUnregistrated方法进行处理:
-
-
private void onUnregistrated(Context context){
-
Log.v(TAG, "C2DMReceiver Unregister");
-
-
C2DMRegistration.clearRegistrationId(context);
-
}
主要就是清除之前保存在本地的registrationId值
当是注册失败时,调用onError方法进行处理:
-
-
private void onError(Context context, String errorId){
-
Log.v(TAG, "C2DMReceiver Error with the reason: " + errorId);
-
-
C2DMRegistration.clearRegistrationId(context);
-
-
-
if("SERVICE_NOT_AVAILABLE".equals(errorId)){
-
-
-
long backoffTimeMs = C2DMRegistration.getBackoff(context);
-
Intent retryIntent = new Intent("com.google.android.c2dm.intent.RETRY");
-
PendingIntent retryPIntent = PendingIntent.getBroadcast(context,
-
0 , retryIntent, 0 );
-
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-
am.set(AlarmManager.ELAPSED_REALTIME,
-
backoffTimeMs, retryPIntent);
-
-
backoffTimeMs *= 2;
-
C2DMRegistration.setBackoff(context, backoffTimeMs);
-
}
-
-
-
}
如果是注册失败,可以从Intent中获取”error”键值对应数据,根据这个数据值可以查看失败的原因。失败的原因有:
1. SERVICE_NOT_AVAILABLE:Google的服务器未响应。客户端程序可以等待一段时间重新尝试。
2. ACCOUNT_MISSING:Android设备上缺少Google账户。在Android设备上登录一个Google账户后再重新尝试。
3. INVALID_SENDER:Sender ID邮箱C2DM服务器不认识。这个需要把Sender ID邮箱号在C2DM网页上进行注册。
4. PHONE_REGISTRATION_ERROR:当前Android设备不支持C2DM服务。使用2.2及以上版本Android系统来重新尝试。
除了以上四个常见的原因外还有AUTHENTICATION_FAILED和TOO_MANY_REGISTRATIONS原因。
SERVICE_NOT_AVAILABLE是C2DM服务器临时没有响应,可以在代码中进行重新尝试注册。其他都是设备端的原因,都不是可以在代码中解决的。
注册的回调中除了以上两个结果外,就是注册成功返回registration_id的结果了,调用onRegistrated方法:
-
-
private void onRegistrated(Context context, String registrationId){
-
Log.v(TAG, "C2DMReceiver Register with the registrationId = " + registrationId);
-
-
C2DMRegistration.setRegistraionID(context, registrationId);
-
-
}
注册成功后可以把registration_id值保存在本地。同时重要的是还要发送给我们自己的第三方服务器。发送的方式可以使用HTTP
POST的方式等。当把registration_id值发送给了第三方服务器后,完整的注册过程才算完成,之后第三方服务器就可以使用这个
registration_id来给我们的客户端程序推送消息了。
C2DM服务器正式的推送消息也在这个类里进行处理。当接收到推送的消息时,就调用handleMessage方法:
-
-
private void handleMessage(Context context, Intent intent){
-
onMessage(context, intent);
-
}
在这个方法里调用了onMessage回调方法:
-
-
private void onMessage(Context context, Intent intent){
-
Log.v(TAG, "C2DMReceiver Message");
-
Bundle extras = intent.getExtras();
-
if(extras!=null){
-
-
String msg = (String)extras.get(AndroidC2DMDemo.MESSAGE_KEY_ONE);
-
Log.v(TAG, "The received msg = "+msg);
-
-
NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
-
Notification notification = new Notification(R.drawable.icon, msg, System.currentTimeMillis());
-
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, AndroidC2DMDemo.class), 0);
-
notification.setLatestEventInfo(context, context.getString(R.string.app_name), msg, contentIntent);
-
notificationManager.notify(0, notification);
-
}
-
}
前面说过,Android设备接收推送消息有三个步骤,不过前两步都已经在Android2.2及之后的系统中处理,我们的客户端程序只要处理第三步:即从com.google.android.c2dm.intent.RECEIVE Intent中提取键值对数据。键值是应该和第三方服务器端商量好的。获取键值对应的数据方式很简单。为了方便查看,下面的代码是把获取的数据在通知栏处显示出来。
处理C2DM数据的C2DMReceiver的完整代码为:
-
public class C2DMReceiver extends BroadcastReceiver{
-
-
private static final String TAG="C2DMReceiver";
-
-
@Override
-
public void onReceive(Context context, Intent intent) {
-
-
if(intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")){
-
-
handleRegistration(context, intent);
-
}else if(intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")){
-
-
handleMessage(context, intent);
-
}
-
}
-
-
private void handleRegistration(final Context context, Intent intent){
-
-
final String registrationId = intent.getStringExtra("registration_id");
-
-
String error = intent.getStringExtra("error");
-
-
String removed = intent.getStringExtra("unregistered");
-
-
Log.v(TAG, "handleRegistration");
-
Log.v(TAG, "dmControl: registrationId = " + registrationId +
-
", error = " + error + ", removed = " + removed);
-
-
if(removed != null){
-
-
onUnregistrated(context);
-
return;
-
}else if(error != null){
-
-
onError(context, error);
-
return;
-
}else{
-
-
onRegistrated(context, registrationId);
-
}
-
-
}
-
-
private void handleMessage(Context context, Intent intent){
-
onMessage(context, intent);
-
}
-
-
-
private void onRegistrated(Context context, String registrationId){
-
Log.v(TAG, "C2DMReceiver Register with the registrationId = " + registrationId);
-
-
C2DMRegistration.setRegistraionID(context, registrationId);
-
-
}
-
-
private void onUnregistrated(Context context){
-
Log.v(TAG, "C2DMReceiver Unregister");
-
-
C2DMRegistration.clearRegistrationId(context);
-
}
-
-
private void onError(Context context, String errorId){
-
Log.v(TAG, "C2DMReceiver Error with the reason: " + errorId);
-
-
C2DMRegistration.clearRegistrationId(context);
-
-
-
if("SERVICE_NOT_AVAILABLE".equals(errorId)){
-
-
-
}
-
-
-
}
-
-
private void onMessage(Context context, Intent intent){
-
Log.v(TAG, "C2DMReceiver Message");
-
Bundle extras = intent.getExtras();
-
if(extras!=null){
-
-
String msg = (String)extras.get(AndroidC2DMDemo.MESSAGE_KEY_ONE);
-
Log.v(TAG, "The received msg = "+msg);
-
-
NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
-
Notification notification = new Notification(R.drawable.icon, msg, System.currentTimeMillis());
-
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, AndroidC2DMDemo.class), 0);
-
notification.setLatestEventInfo(context, context.getString(R.string.app_name), msg, contentIntent);
-
notificationManager.notify(0, notification);
-
}
-
}
-
}
主要就是处理了向C2DM注册后的数据和正式推送数据的接收。在实际使用中,需要添加向我们自己的服务器发送registration_id值。
然后还需要在AndroidC2DMDemo中启动向C2DM服务器注册:
-
public class AndroidC2DMDemo extends Activity {
-
-
private static final String TAG = "AndroidC2DMDemo";
-
public static final String SENDER_ID = "android.c2dm.demo@gmail.com";
-
public static final String MESSAGE_KEY_ONE = "msg";
-
-
@Override
-
public void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.main);
-
-
Log.v(TAG, "Start");
-
if(C2DMRegistration.getRegistrationID(this).equals("")){
-
-
Log.v(TAG, "Register C2DM");
-
C2DMRegistration.register(this, SENDER_ID);
-
}else{
-
-
Log.v(TAG, "C2DM registered");
-
}
-
}
-
}
完成了Java代码部分,最后还要在Manifest.xml文件中声明和C2DM相关的权限等信息。
为了使用C2DM特性,Manifest.xml需要包含以下几个部分:
1.
这个说明程序有注册和接收C2DM消息的权限
2.
这个在向第三方服务器发送registration_id值时需要使用
3. 设置和声明一个这样的权限:程序的包名 + ".permission.C2D_MESSAGE,如: "com.ichliebephone.c2dm.permission.C2D_MESSAGE"
android:protectionLevel="signature">
"com.ichliebephone.c2dm.permission.C2D_MESSAGE"/>
这表明只有这个应用才能接收到对应Push的消息及注册时返回的结果。
4. 包含com.google.android.c2dm.intent.RECEIVE 和
com.google.android.c2dm.intent.REGISTRATION这两个Action的接收器Receiver,并且类别设置为
程序的包名,同时还需要有com.google.android.c2dm.SEND这个权限,如:
".C2DMReceiver"
android:permission="com.google.android.c2dm.permission.SEND">
"com.google.android.c2dm.intent.RECEIVE" />
"com.ichliebephone.c2dm" />
"com.google.android.c2dm.intent.REGISTRATION" />
"com.ichliebephone.c2dm" />
这表明这个程序能给接收到C2DM服务器发送的数据
5. 最后还要设置最小SDK版本为8:
完整的Manifest.xml文件为:
-
xml version="1.0" encoding="utf-8"?>
-
<manifest xmlns:android=""
-
package="com.ichliebephone.c2dm"
-
android:versionCode="1"
-
android:versionName="1.0">
-
<uses-sdk android:minSdkVersion="8" />
-
-
<permission android:name="com.ichliebephone.c2dm.permission.C2D_MESSAGE"
-
android:protectionLevel="signature">permission>
-
<uses-permission android:name="com.ichliebephone.c2dm.permission.C2D_MESSAGE"/>
-
-
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
-
-
<uses-permission android:name="android.permission.INTERNET" />
-
-
<application android:icon="@drawable/icon" android:label="@string/app_name">
-
<activity android:name=".AndroidC2DMDemo"
-
android:label="@string/app_name">
-
<intent-filter>
-
<action android:name="android.intent.action.MAIN" />
-
<category android:name="android.intent.category.LAUNCHER" />
-
intent-filter>
-
activity>
-
-
-
<receiver android:name=".C2DMReceiver"
-
android:permission="com.google.android.c2dm.permission.SEND">
-
-
<intent-filter>
-
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
-
<category android:name="com.ichliebephone.c2dm" />
-
intent-filter>
-
-
<intent-filter>
-
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
-
<category android:name="com.ichliebephone.c2dm" />
-
intent-filter>
-
receiver>
-
-
application>
-
manifest>
完整的工程目录为:
图1 工程目录
最后创建带有Google API的Android2.2版本及以上的AVD,启动模拟器,在Accounts & Sync中添加账户,就可以运行程序了。
和前一部分的测试方法类似,运行程序后,会在DDMS中看到获取的registrationId值:
图2 获取的registrationId值
然后使用获取的registrationId值利用curl命令模拟第三方服务器向C2DM服务器发送要推送的信息:
图3 使用curl向C2DM服务器发送推送信息
然后一会我们就可以在DDMS中看到客户端程序收到的推送数据:
图4 获取到的推送数据
同时Android模拟器中也会在通知栏上显示接收到的推送数据:
图5 模拟器接收到的推送数据
通过测试结果可知,我们实现了Android的C2DM推送功能。
四. 总结
以上我们简单介绍了Android的C2DM推送功能实现过程中,在Android设备上的客户端部分需要实现的内容,及实际的实现过程。不过因为只是
用来说明实现过程,因此代码写的尽量简单,并且为了更容易查看,把各种常量字符串等也直接写在代码中了。如果在实际使用中,可以参考Google的
C2DM例子中的代码,只要包含其com.google.android.c2dm包中的三个文件,并且新建一个扩展
C2DMBaseReceiver的子类来处理注册消息和推送消息的回调即可,其代码更加茁壮。不过通过以上的介绍说明,我们应该可以更好的理解C2DM
客户端部分的实现了。
以后我们继续学习下C2DM服务器部分的实现。
文章对应的完整代码例子下载地址: