第一篇:
原文地址:http://bigcat.easymorse.com/?p=1210
先大概解释一下,在接下来的文档中会给出详细实现代码。
基本分为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有很多………)
- 应用程序对应的网站向google的C2DM Server发出一个1KB大小的消息,这个消息的主要目的是触发手机上应用程序的动作,而非直接传递信息。
- C2DM Server将这个包转发给相应的手机。
- 手机收到之后,触发C2DM的intent,调用相应的程序处理。
- BlackBerry的短信触发机制:Android不需要运营商支持,大大提高了应用的灵活性。
- iPhone的类似机制:由于Android天生的Intent机制,C2DM不需要应用程序聆听,系统收到消息后会自动启动应用程序,大大降低了对性能、耗电的要求,对用户而言也更加方便。
用户使用C2DM付出的代价主要有两点:
- 与google C2DM Server保持连接带来的电力和流量开销。事实上流量开销极小,可以不计。由于C2DM是内建在Android目前(用于同步联系人、日程表、gmail等)的同步机制中的,因此不会带来额外的电力开销。
- 应用程序启动后动作带来的电力和流量开销。这一点主要取决于应用程序自身的设计,还有就是网络条件带来的影响(如果网络质量不佳,会产生多次连接和连接不上的情况)。另外,由于应用程序安装后需要与C2DM服务器注册,因此客户可以选择关闭应用程序的C2DM功能。
总体而言,C2DM算是目前Push信息最佳的解决方案了。Google Code上的chrometophone是第一个基于C2DM的实现,在桌面的Chrome浏览器安装相应的扩展,并在Android 2.2以上的手机上安装了chrometophone的apk之后,就可以在桌面浏览器上将当前网址或Google Maps的地址发送给手机,直接在手机上打开,而这一切只需要一键。
第三篇:
http://bigcat.easymorse.com/?p=1185
Android C2DM手机客户端实现
在Android中利用C2DM实现push消息到手机中给出了C2DM的实现架构,这里写了一个最简单的客户端例子,项目目录如下:
说一下实现步骤:
- 编写manifest文件如下:
<!– Only this application can receive the messages and registration result –>
<permission android:name=”com.easymorse.myc2dm.permission.C2D_MESSAGE” android:protectionLevel=”signature” />
<uses-permission android:name=”com.easymorse.myc2dm.permission.C2D_MESSAGE” /><!– This app has permission to register and receive message –>
<uses-permission android:name=”com.google.android.c2dm.permission.RECEIVE” /><!– Send the registration id to the server –>
<uses-permission android:name=”android.permission.INTERNET” /><application android:icon=”@drawable/icon” android:label=”@string/app_name”>
<activity android:name=”.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
<service android:name=”.C2DMReceiver” />
<!– Only C2DM servers can send messages for the app. If permission is not set – any other app can generate it –>
<receiver android:name=”com.google.android.c2dm.C2DMBroadcastReceiver” android:permission=”com.google.android.c2dm.permission.SEND”>
<!– Receive the actual message –>
<intent-filter>
<action android:name=”com.google.android.c2dm.intent.RECEIVE” />
<category android:name=”com.easymorse.myc2dm” />
</intent-filter>
<!– Receive the registration id –>
<intent-filter>
<action android:name=”com.google.android.c2dm.intent.REGISTRATION” />
<category android:name=”com.easymorse.myc2dm” />
</intent-filter>
</receiver>
</application>
红色标记的部分需要根据自己的情况修改为相应的包名。
- 引入google的C2DM java类包,如下:
- 注册service到客户端:
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对应的值是通过http://code.google.com/intl/zh-CN/android/c2dm/signup.html连接申请过的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://bigcateasymorse.googlecode.com/svn/trunk/android-c2dm-demo/
一.基础知识
在前一部分,我们使用curl命令来代替了服务器端的实现,虽然在测试时使用curl命令是一个很是简单方便的模拟方式,但实际使用中我们需要把C2DM相关的服务器部分功能结合到已有的框架中,因此需要使用具体的代码来实现。
第三方服务器端部分的功能主要是通过C2DM服务器向客户端发送要推送的数据。
为了发送数据,第三方服务器需要向这个地址https://android.apis.google.com/c2dm/send发送一个POST请求,其中POST的内容包含:
registration_id:是客户端发送过来的registration_id值。必须包含。
collapse_key:一个任意的字符串,用来表示一组相似的消息。当Android设备由离线到上线时,之前使用相同collapse_key推送的消息,只有最后一条才会推送给Android设备。设置这个值用来避免Android设备上线时收到太多已经过时的消息。必须包含。
data.<key>:要推送的数据内容,以键值对的方式组织。当客户端程序接收时,就通过键值<key>来获取对应的内容。一条推送消息中包含的键值对数目没有限制,虽然整体的数据大小有限制。可选。
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权限的方法实现:
- //获取Auth权限值
- public static String getAuthToken(String url, String params) throws IOException{
- String auth = null;
- //要POST的数据
- byte[] postData = params.getBytes();
- //构造POST请求
- 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));
- //写入POST数据
- 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){
- //如果请求成功,提取Auth值
- int authStartIndex = responseDataBuidler.indexOf("Auth=");
- auth = responseDataBuidler.substring(authStartIndex+5);
- //System.out.println(auth);
- } else{
- //如果失败,打印出失败的结果
- System.out.println(responseDataBuidler);
- }
- return auth;
- }
关于获取ClientLogin的说明参考谷歌的官方文档,方法的第一个参数url为请求的地址:https://www.google.com/accounts/ClientLogin,第二个参数params为POST时带的内容,包括accountType(申请权限的账户类型),Email(账户邮箱地址),Passwd(账户邮箱密码),service(申请权限的服务类型,这里申请C2DM服务,值为ac2dm),source(表示我们应用程序的一个简短字符串)。这里我传入的值为"accountType=HOSTED_OR_GOOGLE&Email=android.c2dm.demo@gmail.com&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=http://www.google.com/login/captcha
Error=CaptchaRequired
CaptchaToken=DQAAAGgA...dkI1LK9
CaptchaUrl=Captcha?ctoken=HiteT4b0Bk5Xg18_AcVoP6-yFkHPibe7O9EqxeiI7lUSN
因此这里我们根据返回的responseCode判断,如果是200则表明请求成功,从返回的数据中提取我们需要的Auth值。如果不是200则表明请求出错,这里只是简单的打印出出错的信息。实际使用中需要更加错误的信息做相应的处理,比如是服务器端没响应的话那就等待一段时间重新尝试,等等。
获取了Auth权限值,接了下来我们就可以给C2DM服务器发送我们想要其Push的数据了,实现向C2DM服务器发送数据的方法为:- //向C2DM服务器发送需要Push的数据
- public static boolean sendPushMessage(String url, String registration_id, String collapse_key, String auth, Map<String, String> data) throws IOException{
- boolean flag = false;
- //构造POST的数据
- 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();
- //构造POST请求
- 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);
- //写入POST数据
- 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://android.apis.google.com/c2dm/send,因为使用https的方式会提示出错,因此使用http的方式,URL为http://android.apis.google.com/c2dm/send, 使用POST方式提交要包含的内容有registration_id(Android应用程序注册获得的id),collapse_key
(用任意一个字符传表示一组类似的消息,如果Android设备离线了,那等其上线后这组消息中只有最后一条消息才会被Push给Android设备,这样重新上线后的Android设备不会收到太多之前过时的消息),ClientLogin
auth(上一步获取的使用C2DM功能的账户权限),data(这是一个键值对形式表示的数据,形式比如为data.msg=ichliebejiajia,其中键值需要和Android设备上的程序统一化,以便其可以获取解析,这是真正需要Push给Android设备的数据)。
图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 = "https://www.google.com/accounts/ClientLogin";
- 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 = "http://android.apis.google.com/c2dm/send";
- public static String Registration_ID = "APA91bGUBoSvt3G5Ny9t0IGLmIKAKYX6G6VHwSQHh3tP2fqcaQ0N4GPdKh5B3RDUHFCFF06YwT8ifOP_cOy5BAWyCLHL8d8NpuIW9AqXt9h2JSBVF2MitZA";
- /**
- * @param args
- */
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- 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<String, String> data = new HashMap<String, String>();
- data.put("data.msg", message);//这里和客户端代码统一,使用的key为msg,并且只push一组数据
- if(sendPushMessage(C2DMServerURL, Registration_ID, "1", authToken, data)){
- System.out.println("Send Successfully");
- }else{
- System.out.println("Send Failed");
- }
- }
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- <div style="text-align: left;">
- </div><div style="text-align: left;"> }</div>
在开始处我们定义了几个相关的静态变量,其中在实际使用的时候AuthTokenParams中的Email和Passwd需要换成你自己申请C2DM时的邮箱和密码,Registration_ID也要换成你的Android设备向C2DM注册时返回的值。
在main方法里,首先获得Auth值,然后提示从终端中输入要发送给C2DM服务器Push的信息,最后调用sendPushMessage方法进行实现。
打开之前我们使用过的Android模拟器,然后运行C2DMMessageServer程序,运行后首先会去获取ClientLogin的Auth值,获取成功的话会显示200的返回码,并且提示可以输入想要Push的信息了:
![](http://hi.csdn.net/attachment/201107/26/0_1311684752Yh3X.gif)
图2 输入要Push的信息
然后我们可以试着输入我们想要Push的数据,比如依次输入两个数据:
![](http://hi.csdn.net/attachment/201107/26/0_131168487565jv.gif)
图3 输入了两个要Push的信息
我们可以在DDMS中看到分别收到了这两个Push的数据:
![](http://hi.csdn.net/attachment/201107/26/0_1311684939VB0H.gif)
图4 DDMS中显示接收到的Push信息
并且Android模拟器中的通知栏中也会依次显示对应的通知:
![](http://hi.csdn.net/attachment/201107/26/0_1311685864TUwQ.gif)
![](http://hi.csdn.net/attachment/201107/26/0_1311685868mDra.gif)
图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://download.csdn.net/source/3471530
一.基础知识
在前一部分中,我们从整体上快速介绍并实现了下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 {
- //注册C2DM服务
- 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)); // boilerplate
- registrationIntent.putExtra("sender", senderID);
- context.startService(registrationIntent);
- }
- //取消C2DM服务
- 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);
- }
- //保存注册成功获得的registration_id值
- 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();
- }
- //获取保存的registration_id值
- public static String getRegistrationID(Context context){
- final SharedPreferences prefs = context.getSharedPreferences(
- "c2dm_preference",
- Context.MODE_PRIVATE);
- String registrationId = prefs.getString("dm_registration", "");
- return registrationId;
- }
- //当取消C2DM服务时,清空之前保存着的registration_id值
- 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();
- }
- //当SERVICE_NOT_AVAILABLE时,需要回退一定时间后重新启动注册
- //获取当前的回退等待时间
- static long getBackoff(Context context) {
- final SharedPreferences prefs = context.getSharedPreferences(
- "c2dm_preference",
- Context.MODE_PRIVATE);
- //默认回退等待时间为30000微秒
- 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) {
- // TODO Auto-generated method stub
- if(intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")){
- //如果是注册返回的Intent
- handleRegistration(context, intent);
- }else if(intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")){
- //如果是接收到C2DM推送消息的Intent
- handleMessage(context, intent);
- }
- }
当是接收到注册返回的Intent时,就调用handleRegistration方法:
- //注册后的回调处理
- private void handleRegistration(final Context context, Intent intent){
- //如果是注册成功,则Intent中包含"registration_id"键对应的值
- final String registrationId = intent.getStringExtra("registration_id");
- //如果是注册失败,则Intent中包含"error"键对应的值
- String error = intent.getStringExtra("error");
- //如果是取消注册,则Intent中包含"unregistered"键对应的值
- String removed = intent.getStringExtra("unregistered");
- //打印出接收到的registraton_id,为了调试是查看
- Log.v(TAG, "handleRegistration");
- Log.v(TAG, "dmControl: registrationId = " + registrationId +
- ", error = " + error + ", removed = " + removed);
- //
- if(removed != null){
- //如果存在"unregistered"键对应的值,则表示是取消注册
- onUnregistrated(context);
- return;
- }else if(error != null){
- //如果存在"error"键对应的值,则表示注册失败
- onError(context, error);
- return;
- }else{
- //如果以上两种情况都不存在,则表示注册成功了
- onRegistrated(context, registrationId);
- }
- }
通过获取注册后返回的Intent中数据,来判断是注册失败(error键值存储的内容不为空)、取消注册(unregistered键值存储的内容不为空)还是注册成功(registration_id键值存储的内容不为空)。
当是取消注册时,调用onUnregistrated方法进行处理:
- //取消注册后的处理函数
- private void onUnregistrated(Context context){
- Log.v(TAG, "C2DMReceiver Unregister");
- //取消注册的话,同时也清除保存着的registrationId值
- C2DMRegistration.clearRegistrationId(context);
- }
主要就是清除之前保存在本地的registrationId值
当是注册失败时,调用onError方法进行处理:
- //出错的处理函数
- private void onError(Context context, String errorId){
- Log.v(TAG, "C2DMReceiver Error with the reason: " + errorId);
- //首先清除保存着的registrationId值
- C2DMRegistration.clearRegistrationId(context);
- //判断出错的原因,一共有六种原因:SERVICE_NOT_AVAILABLE,ACCOUNT_MISSING,AUTHENTICATION_FAILED,
- //TOO_MANY_REGISTRATIONS,INVALID_SENDER,PHONE_REGISTRATION_ERROR
- if("SERVICE_NOT_AVAILABLE".equals(errorId)){
- //只有SERVICE_NOT_AVAILABLE这个是C2DM服务器没有响应的原因,可以等待一段时间后重新发送注册请求
- //其他原因都是属于客户端设备没有处理好的原因
- long backoffTimeMs = C2DMRegistration.getBackoff(context);
- Intent retryIntent = new Intent("com.google.android.c2dm.intent.RETRY");
- PendingIntent retryPIntent = PendingIntent.getBroadcast(context,
- 0 /*requestCode*/, retryIntent, 0 /*flags*/);
- 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);
- //注册成功的话,保存获取的registrationId值
- C2DMRegistration.setRegistraionID(context, registrationId);
- //然后把registrationId值发送给我们自己的第三方服务器
- }
注册成功后可以把registration_id值保存在本地。同时重要的是还要发送给我们自己的第三方服务器。发送的方式可以使用HTTP POST的方式等。当把registration_id值发送给了第三方服务器后,完整的注册过程才算完成,之后第三方服务器就可以使用这个registration_id来给我们的客户端程序推送消息了。
C2DM服务器正式的推送消息也在这个类里进行处理。当接收到推送的消息时,就调用handleMessage方法:
- //接收到C2DM推送信息的回调处理
- 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) {
- // TODO Auto-generated method stub
- if(intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")){
- //如果是注册返回的Intent
- handleRegistration(context, intent);
- }else if(intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")){
- //如果是接收到C2DM推送消息的Intent
- handleMessage(context, intent);
- }
- }
- //注册后的回调处理
- private void handleRegistration(final Context context, Intent intent){
- //如果是注册成功,则Intent中包含"registration_id"键对应的值
- final String registrationId = intent.getStringExtra("registration_id");
- //如果是注册失败,则Intent中包含"error"键对应的值
- String error = intent.getStringExtra("error");
- //如果是取消注册,则Intent中包含"unregistered"键对应的值
- String removed = intent.getStringExtra("unregistered");
- //打印出接收到的registraton_id,为了调试是查看
- Log.v(TAG, "handleRegistration");
- Log.v(TAG, "dmControl: registrationId = " + registrationId +
- ", error = " + error + ", removed = " + removed);
- //
- if(removed != null){
- //如果存在"unregistered"键对应的值,则表示是取消注册
- onUnregistrated(context);
- return;
- }else if(error != null){
- //如果存在"error"键对应的值,则表示注册失败
- onError(context, error);
- return;
- }else{
- //如果以上两种情况都不存在,则表示注册成功了
- onRegistrated(context, registrationId);
- }
- }
- //接收到C2DM推送信息的回调处理
- private void handleMessage(Context context, Intent intent){
- onMessage(context, intent);
- }
- //和C2DM推送信息相关的具体处理函数
- //注册成功后的处理函数
- private void onRegistrated(Context context, String registrationId){
- Log.v(TAG, "C2DMReceiver Register with the registrationId = " + registrationId);
- //注册成功的话,保存获取的registrationId值
- C2DMRegistration.setRegistraionID(context, registrationId);
- //然后把registrationId值发送给我们自己的第三方服务器
- }
- //取消注册后的处理函数
- private void onUnregistrated(Context context){
- Log.v(TAG, "C2DMReceiver Unregister");
- //取消注册的话,同时也清除保存着的registrationId值
- C2DMRegistration.clearRegistrationId(context);
- }
- //出错的处理函数
- private void onError(Context context, String errorId){
- Log.v(TAG, "C2DMReceiver Error with the reason: " + errorId);
- //首先清除保存着的registrationId值
- C2DMRegistration.clearRegistrationId(context);
- //判断出错的原因,一共有六种原因:SERVICE_NOT_AVAILABLE,ACCOUNT_MISSING,AUTHENTICATION_FAILED,
- //TOO_MANY_REGISTRATIONS,INVALID_SENDER,PHONE_REGISTRATION_ERROR
- if("SERVICE_NOT_AVAILABLE".equals(errorId)){
- //只有SERVICE_NOT_AVAILABLE这个是C2DM服务器没有响应的原因,可以等待一段时间后重新发送注册请求
- //其他原因都是属于客户端设备没有处理好的原因
- }
- }
- //接收到推送消息后的处理函数
- 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 {
- /** Called when the activity is first created. */
- private static final String TAG = "AndroidC2DMDemo";
- public static final String SENDER_ID = "android.c2dm.demo@gmail.com"; //使用C2DM服务的用户账户
- public static final String MESSAGE_KEY_ONE = "msg"; //和服务器商量好的接收消息的键值key
- //
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- //
- Log.v(TAG, "Start");
- if(C2DMRegistration.getRegistrationID(this).equals("")){
- //如果本地没有保存registration_id值,则向C2DM服务器注册
- Log.v(TAG, "Register C2DM");
- C2DMRegistration.register(this, SENDER_ID);
- }else{
- //否则说明已经向C2DM服务器注册过了
- Log.v(TAG, "C2DM registered");
- }
- }
- }
完成了Java代码部分,最后还要在Manifest.xml文件中声明和C2DM相关的权限等信息。
为了使用C2DM特性,Manifest.xml需要包含以下几个部分:
1. <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
这个说明程序有注册和接收C2DM消息的权限
2. <uses-permission android:name="android.permission.INTERNET" />
这个在向第三方服务器发送registration_id值时需要使用
3. 设置和声明一个这样的权限:程序的包名 + ".permission.C2D_MESSAGE,如: <permission android:name="com.ichliebephone.c2dm.permission.C2D_MESSAGE"
android:protectionLevel="signature"></permission>
<uses-permission android:name="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这个权限,如:
<receiver android:name=".C2DMReceiver"
android:permission="com.google.android.c2dm.permission.SEND">
<!-- 可以接收实际的Push数据 -->
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.ichliebephone.c2dm" />
</intent-filter>
<!-- 可以接收注册后返回的registration_id -->
<intent-filter>
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="com.ichliebephone.c2dm" />
</intent-filter>
</receiver>
这表明这个程序能给接收到C2DM服务器发送的数据
5. 最后还要设置最小SDK版本为8:<uses-sdk android:minSdkVersion="8" />
完整的Manifest.xml文件为:
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.ichliebephone.c2dm"
- android:versionCode="1"
- android:versionName="1.0">
- <uses-sdk android:minSdkVersion="8" />
- <!-- 设置一个权限,使只有这个应用才能接收到对应Push的消息及注册时返回的结果 -->
- <permission android:name="com.ichliebephone.c2dm.permission.C2D_MESSAGE"
- android:protectionLevel="signature"></permission>
- <uses-permission android:name="com.ichliebephone.c2dm.permission.C2D_MESSAGE"/>
- <!-- 设置注册和接收C2DM Push消息的权限 -->
- <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
- <!-- 设置联网权限,在把registration_id发送给服务器的时候要用 -->
- <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>
- <!-- 这个程序能给接收到谷歌C2DM服务器发送的数据,并声明对应的权限 -->
- <receiver android:name=".C2DMReceiver"
- android:permission="com.google.android.c2dm.permission.SEND">
- <!-- 可以接收实际的Push数据 -->
- <intent-filter>
- <action android:name="com.google.android.c2dm.intent.RECEIVE" />
- <category android:name="com.ichliebephone.c2dm" />
- </intent-filter>
- <!-- 可以接收注册后返回的registration_id -->
- <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例子Chrome To Phone中的代码,只要包含其com.google.android.c2dm包中的三个文件,并且新建一个扩展C2DMBaseReceiver的子类来处理注册消息和推送消息的回调即可,其代码更加茁壮。不过通过以上的介绍说明,我们应该可以更好的理解C2DM客户端部分的实现了。
以后我们继续学习下C2DM服务器部分的实现。
文章对应的完整代码例子下载地址:
http://download.csdn.net/source/3462743