一、基础知识

当我们开发需要和服务器交互的应用程序时,基本上都需要获取服务器端的数据,比如《地震及时通》就需要及时获取服务器上最新的地震信息。要获取服务器上不定时更新的信息一般来说有两种方法,第一种是客户端使用Pull(拉)的方式,隔一段时间就去服务器上获取信息,看是否有更新的信息出现。第二种就是服务器使用Push(云端推送)的方式,当服务器端有新信息了,则把最新的信息Push到客户端上。

虽然Pull和Push两种方式都能实现获取服务器端更新信息的功能,但是明显来说Push is better than pull。因为Pull方式不仅浪费客户端的流量,而且更浪费电量。

Android从2.2版本开始增加了Cloud to Device Messaging(C2DM)框架,在系统中支持了Push功能,基于Android平台使用Push功能更加简单了。虽然C2dm目前还处在实验室阶段,不过小规模的使用是没有问题的。

下面我们就来感受一下Android的C2dm功能。

二、C2DM框架

使用Android的C2DM功能有几个要求:

1. 需要Android2.2及以上的系统版本。
2. 使用C2DM功能的Android设备上需要设置好Google的账户。
3. 需要在这里注册使用C2DM功能的用户邮箱账号(最好为C2DM单独注册一个Gmail邮箱账号)。

接下来我们来看下C2dm的完整过程,这里借用一下Google官方推出的Chrome To Phone过程图来说明下。


图1 C2DM操作过程图

要使用C2DM来进行Push操作,基本上要使用以下6个步骤:

1、注册:Android设备把使用C2DM功能的用户账户(比如android.c2dm.demo@gmail.com)和App名称发送给C2DM服务器。

2、C2dm服务器会返回一个registration_id值给Android设备,设备需要保存这个registration_id值。

3、Android设备把获得的registration_id和C2DM功能的用户账户(android.c2dm.demo@gmail.com)发送给自己的服务器,不过一般用户账户信息因为和服务器确定好的,所以不必发送。

这样Android设备就完成了C2DM功能的注册过程,接下来就可以接收C2DM服务器Push过来的消息了。

4、服务器获得数据。这里图中的例子Chrome To Phone,服务器接收到Chrome浏览器发送的数据。数据也可以是服务器本地产生的。这里的服务器是Google AppEngine(很好的一项服务,可惜在国内被屏了),要换成自己的服务器。服务器还要获取注册使用C2DM功能的用户账户(android.c2dm.demo@gmail.com)的ClientLogin权限Auth。

5、服务器把要发送的数据和registration_id一起,并且头部带上获取的Auth,使用POST的方式发送给C2dm服务器。

6、C2DM服务器会以Push的方式把数据发送给对应的Android设备,Android设备只要在程序中按之前和服务器商量好的格式从对应的key中获取数据即可。

这样我们就大概明白了C2dm的工作流程,下面我们就结合一个实例来具体的说明以上6个步骤。

三.实例开发

我们要创建的程序名称为AndroidC2DMDemo,包名为com.ichliebephone.c2dm。

开始之前我们先去C2DM网页上注册一下使用C2DM功能的用户账户。


图2 应用程序名

其中应用程序名要填写带包名的完整名称,比如这里为om.ichliebephone.c2dm. AndroidC2DMDemo。


图3 C2DM用户账户注册

这里的contact邮箱使用一个你能接收到邮件的邮箱即可,下面的Role(sender)account邮箱最好单独注册一个Gmail邮箱来使用C2DM服务。我们这里使用的是专门注册的android.c2dm.deno@gmail.com邮箱。

提交后,过一段时间就会收到Google发送过来的确认邮件,然后你就可以使用C2DM的Push服务了。

介绍了这么多,我们先来快速完成一个实例,只完成Android设备端的注册部分,不包含向服务器发送registration_id和服务器向C2DM服务器发送数据的具体代码,这部分只是用Ubuntu下的curl命令来模拟,主要是快速亲自体验一下Push的结果。

创建一个Android工程AndroidC2DMDemo,并且包含进Google的开源例子Chrome To Phone中的c2dm包com.google.android.c2dm,包中包含三个Java类,分别为:

第一个类为C2DMBaseReceiver:

package com.google.android.c2dm;
import java.io.IOException;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.util.Log;
/**
* Base class for C2D message receiver. Includes constants for the
* strings used in the protocol.
*/
/**
* 接收和处理C2DM消息的基类
* */
public abstract class C2DMBaseReceiver extends IntentService {
// 和C2DM Push的Intent内容相关
// 重新向C2DM服务器注册
private static final String C2DM_RETRY = "com.google.android.c2dm.intent.RETRY";
// 向C2DM服务器注册后的回调处理
public static final String REGISTRATION_CALLBACK_INTENT = "com.google.android.c2dm.intent.REGISTRATION";
// 接收到C2DM服务器的推送消息
private static final String C2DM_INTENT = "com.google.android.c2dm.intent.RECEIVE";
// Logging tag
private static final String TAG = "C2DM";
// Extras in the registration callback intents.
// 向C2DM注册返回的intent中包含的key
public static final String EXTRA_UNREGISTERED = "unregistered";
public static final String EXTRA_ERROR = "error";
public static final String EXTRA_REGISTRATION_ID = "registration_id";
// 向C2DM注册出错的原因
public static final String ERR_SERVICE_NOT_AVAILABLE = "SERVICE_NOT_AVAILABLE";
public static final String ERR_ACCOUNT_MISSING = "ACCOUNT_MISSING";
public static final String ERR_AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED";
public static final String ERR_TOO_MANY_REGISTRATIONS = "TOO_MANY_REGISTRATIONS";
public static final String ERR_INVALID_PARAMETERS = "INVALID_PARAMETERS";
public static final String ERR_INVALID_SENDER = "INVALID_SENDER";
public static final String ERR_PHONE_REGISTRATION_ERROR = "PHONE_REGISTRATION_ERROR";
// wakelock
private static final String WAKELOCK_KEY = "C2DM_LIB";
private static PowerManager.WakeLock mWakeLock;
private final String senderId;
/**
* The C2DMReceiver class must create a no-arg constructor and pass the
* sender id to be used for registration.
*/
public C2DMBaseReceiver(String senderId) {
// senderId is used as base name for threads, etc.
super(senderId);
this.senderId = senderId;
}
// 下面几个是接收到C2DM Push过来的信息后的回调函数,都可以在继承的子类中处理
/**
* Called when a cloud message has been received.
*/
/**
* 接收到C2DM服务器Push的消息后的回调函数,需要在继承的子类中处理
* */
protected abstract void onMessage(Context context, Intent intent);
/**
* Called on registration error. Override to provide better error messages.
* This is called in the context of a Service - no dialog or UI.
*/
/**
* 出错的回调函数
* */
public abstract void onError(Context context, String errorId);
/**
* Called when a registration token has been received.
*/
/**
* 注册后的回调函数
* */
public void onRegistered(Context context, String registrationId)
throws IOException {
// registrationId will also be saved
}
/**
* Called when the device has been unregistered.
*/
/**
* 取消注册的回调函数
* */
public void onUnregistered(Context context) {
}
// IntentService的方法
@Override
public final void onHandleIntent(Intent intent) {
try {
Context context = getApplicationContext();
if (intent.getAction().equals(REGISTRATION_CALLBACK_INTENT)) {
handleRegistration(context, intent);// 处理注册后的回调
} else if (intent.getAction().equals(C2DM_INTENT)) {
onMessage(context, intent);// 处理C2DM Push消息的回调
} else if (intent.getAction().equals(C2DM_RETRY)) {
C2DMessaging.register(context, senderId); // 重新注册
}
} finally {
// Release the power lock, so phone can get back to sleep.
// The lock is reference counted by default, so multiple
// messages are ok.
// If the onMessage() needs to spawn a thread or do something else,
// it should use it's own lock.
mWakeLock.release();
}
}
/**
* Called from the broadcast receiver. Will process the received intent,
* call handleMessage(), registered(), etc. in background threads, with a
* wake lock, while keeping the service alive.
*/
static void runIntentInService(Context context, Intent intent) {
if (mWakeLock == null) {
// This is called from BroadcastReceiver, there is no init.
PowerManager pm = (PowerManager) context
.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
WAKELOCK_KEY);
}
mWakeLock.acquire();
// Use a naming convention, similar with how permissions and intents are
// used. Alternatives are introspection or an ugly use of statics.
String receiver = context.getPackageName() + ".C2DMReceiver";
intent.setClassName(context, receiver);
context.startService(intent);
}
// 处理注册后的回调
private void handleRegistration(final Context context, Intent intent) {
final String registrationId = intent
.getStringExtra(EXTRA_REGISTRATION_ID);
String error = intent.getStringExtra(EXTRA_ERROR);
String removed = intent.getStringExtra(EXTRA_UNREGISTERED);
Log.v(TAG, "handleRegistration");
// 打印出接收到的registraton_id
Log.v(TAG, "dmControl: registrationId = " + registrationId
+ ", error = " + error + ", removed = " + removed);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "dmControl: registrationId = " + registrationId
+ ", error = " + error + ", removed = " + removed);
}
if (removed != null) {
// Remember we are unregistered
C2DMessaging.clearRegistrationId(context);
onUnregistered(context);
return;
} else if (error != null) {
// we are not registered, can try again
C2DMessaging.clearRegistrationId(context);
// Registration failed
Log.e(TAG, "Registration error " + error);
onError(context, error);
if ("SERVICE_NOT_AVAILABLE".equals(error)) {
long backoffTimeMs = C2DMessaging.getBackoff(context);
Log.d(TAG, "Scheduling registration retry, backoff = "
+ backoffTimeMs);
Intent retryIntent = new Intent(C2DM_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);
// Next retry should wait longer.
backoffTimeMs *= 2;
C2DMessaging.setBackoff(context, backoffTimeMs);
}
} else {
try {
onRegistered(context, registrationId);
C2DMessaging.setRegistrationId(context, registrationId);
} catch (IOException ex) {
Log.e(TAG, "Registration error " + ex.getMessage());
}
}
}
}

第二个类为C2DMBroadcastReceiver:

package com.google.android.c2dm;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* Helper class to handle BroadcastReciver behavior.
* - can only run for a limited amount of time - it must start a real service 
* for longer activity
* - must get the power lock, must make sure it's released when all done.
*/
/**
* 帮助类,帮忙处理BroadcastReciver过程
* */
public class C2DMBroadcastReceiver extends BroadcastReceiver {
@Override
public final void onReceive(Context context, Intent intent) {
// To keep things in one place.
C2DMBaseReceiver.runIntentInService(context, intent);
setResult(Activity.RESULT_OK, null /* data */, null /* extra */);
}
}

第三个类为C2DMessaging:

package com.google.android.c2dm;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
/**
* Utilities for device registration.
*
* Will keep track of the registration token in a private preference.
*/
/**
* 和注册相关的一些实用函数
* */
public class C2DMessaging {
public static final String EXTRA_SENDER = "sender";
public static final String EXTRA_APPLICATION_PENDING_INTENT = "app";
public static final String REQUEST_UNREGISTRATION_INTENT = "com.google.android.c2dm.intent.UNREGISTER";
public static final String REQUEST_REGISTRATION_INTENT = "com.google.android.c2dm.intent.REGISTER";
public static final String LAST_REGISTRATION_CHANGE = "last_registration_change";
public static final String BACKOFF = "backoff";
public static final String GSF_PACKAGE = "com.google.android.gsf"; // GSF为GoogleServicesFramework.apk的缩写
// package
static final String PREFERENCE = "com.google.android.c2dm";
private static final long DEFAULT_BACKOFF = 30000;
/**
* Initiate c2d messaging registration for the current application
*/
public static void register(Context context, String senderId) {
Intent registrationIntent = new Intent(REQUEST_REGISTRATION_INTENT);
registrationIntent.setPackage(GSF_PACKAGE);
registrationIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT,
PendingIntent.getBroadcast(context, 0, new Intent(), 0));
registrationIntent.putExtra(EXTRA_SENDER, senderId);
context.startService(registrationIntent);
// TODO: if intent not found, notification on need to have GSF
}
/**
* Unregister the application. New messages will be blocked by server.
*/
public static void unregister(Context context) {
Intent regIntent = new Intent(REQUEST_UNREGISTRATION_INTENT);
regIntent.setPackage(GSF_PACKAGE);
regIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT,
PendingIntent.getBroadcast(context, 0, new Intent(), 0));
context.startService(regIntent);
}
/**
* Return the current registration id.
* If result is empty, the registration has failed.
* @return registration id, or empty string if the registration is not
*         complete.
*/
public static String getRegistrationId(Context context) {
final SharedPreferences prefs = context.getSharedPreferences(
PREFERENCE, Context.MODE_PRIVATE);
String registrationId = prefs.getString("dm_registration", "");
return registrationId;
}
public static long getLastRegistrationChange(Context context) {
final SharedPreferences prefs = context.getSharedPreferences(
PREFERENCE, Context.MODE_PRIVATE);
return prefs.getLong(LAST_REGISTRATION_CHANGE, 0);
}
static long getBackoff(Context context) {
final SharedPreferences prefs = context.getSharedPreferences(
PREFERENCE, Context.MODE_PRIVATE);
return prefs.getLong(BACKOFF, DEFAULT_BACKOFF);
}
static void setBackoff(Context context, long backoff) {
final SharedPreferences prefs = context.getSharedPreferences(
PREFERENCE, Context.MODE_PRIVATE);
Editor editor = prefs.edit();
editor.putLong(BACKOFF, backoff);
editor.commit();
}
// package
static void clearRegistrationId(Context context) {
final SharedPreferences prefs = context.getSharedPreferences(
PREFERENCE, Context.MODE_PRIVATE);
Editor editor = prefs.edit();
editor.putString("dm_registration", "");
editor.putLong(LAST_REGISTRATION_CHANGE, System.currentTimeMillis());
editor.commit();
}
// package
static void setRegistrationId(Context context, String registrationId) {
final SharedPreferences prefs = context.getSharedPreferences(
PREFERENCE, Context.MODE_PRIVATE);
Editor editor = prefs.edit();
editor.putString("dm_registration", registrationId);
editor.commit();
}
}

代码中已添加了部分中文注释,可以先大概了解下,等整个工程建立完了在一起解释。

然后创建我们自己的包com.ichliebephone.c2dm,包含两个类,一个是工程的入口AndroidC2DMDemo:

package com.ichliebephone.c2dm;
import com.google.android.c2dm.C2DMessaging;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
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");
// 向C2DM服务器注册
C2DMessaging.register(this, SENDER_ID);
}
}

很简单,就是开始向C2DM服务器进行注册。

另一个类为C2DMBaseReceiver的子类C2DMReceiver:

package com.ichliebephone.c2dm;
import java.io.IOException;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.google.android.c2dm.C2DMBaseReceiver;
//接收C2DM服务器Push的消息,包括注册返回的registration_id消息,推送的数据消息等
public class C2DMReceiver extends C2DMBaseReceiver {
private static final String TAG = "C2DMReceiver";
//
public C2DMReceiver() {
super(AndroidC2DMDemo.SENDER_ID);
}
public C2DMReceiver(String senderId) {
super(senderId);
// TODO Auto-generated constructor stub
}
// 接收到Push消息的回调函数
@Override
protected void onMessage(Context context, Intent intent) {
// TODO Auto-generated method stub
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) getSystemService(NOTIFICATION_SERVICE);
Notification notification = new Notification(R.drawable.icon, msg,
System.currentTimeMillis());
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, AndroidC2DMDemo.class), 0);
notification.setLatestEventInfo(this, getString(R.string.app_name),
msg, contentIntent);
notificationManager.notify(0, notification);
}
}
@Override
public void onError(Context context, String errorId) {
// TODO Auto-generated method stub
Log.v(TAG, "C2DMReceiver error");
}
@Override
public void onRegistered(Context context, String registrationId)
throws IOException {
// TODO Auto-generated method stub
super.onRegistered(context, registrationId);
Log.v(TAG, "C2DMReceiver Register");
}
@Override
public void onUnregistered(Context context) {
// TODO Auto-generated method stub
super.onUnregistered(context);
Log.v(TAG, "C2DMReceiver UnRegister");
}
}

在这个类中我们主要在接收到Push的回调函数onMessage中对消息进行了接收,并且使用Notification的方式显示在状态栏上。

我们完整的工程目录是这样的:


图4 工程目录

最后我们还要在AndroidManifest.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" />
<!--Only this application can receive the message and registration result -->
<!-- 设置一个权限,使只有这个应用才能接收到对应Push的消息及注册时返回的结果 -->
<permission android:name="com.ichliebephone.c2dm.permission.C2D_MESSAGE"
android:protectionLevel="signature"></permission>
<uses-permission android:name="com.ichliebephone.c2dm.permission.C2D_MESSAGE" />
<!-- This application has the permission to register and receive c2dm message -->
<!-- 设置注册和接收C2DM Push消息的权限 -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<!-- Send and registration id to the server -->
<!-- 设置联网权限,在把registration_id发送给服务器的时候要用 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- App must have this permission to use the library -->
<!-- 其他和获取手机中用户账户相关的权限 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<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>
<!-- In order to use the c2dm library, an application must declare a class 
with the name C2DMReceiver, in its own package, extending com.google.android.c2dm.C2DMBaseReceiver 
It must also include this section in the manifest. -->
<!-- 为了使用c2dm包com.google.android.c2dm及其对应的3个类,我们需要声明一个 继承com.google.android.c2dm.C2DMBaseReceiver类的子类C2DMReceiver, 
并且要在这声明下 -->
<service android:name=".C2DMReceiver" />
<!-- Only google service can send data messages for the app. If permission 
is not set - any other app can generate it -->
<!-- 谷歌的C2DM服务只为这个程序发送数据,声明对应的权限 -->
<receiver android:name="com.google.android.c2dm.C2DMBroadcastReceiver"
android:permission="com.google.android.c2dm.permission.SEND">
<!-- Receive the actual message -->
<!-- 可以接收实际的Push数据 -->
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.ichliebephone.c2dm" />
</intent-filter>
<!-- Receive the registration id -->
<!-- 可以接收注册后返回的registration_id -->
<intent-filter>
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="com.ichliebephone.c2dm" />
</intent-filter>
</receiver>
</application>
</manifest>

因为C2DM功能只有2.2及以上的Android系统才支持,因此创建一个2.2及以上的AVD,然后在”设置->账户与同步”里还要设置好Google Account,如下图所示:


图5 设置Android设备中的Google账户

然后就可以运行程序了,我们会在DDMS输出中看到获得的registration_id:


图6 获得的registration_id

如果第一次运行没有出现,试着再运行一次。

有了registration_id,我们的服务器端就可以向C2DM端发送需要Push的数据了,这里进行简单化处理下,在Ubuntu下直接使用curl命令来模拟服务器功能向C2DM发送数据。

我们先来获取C2DM的ClientLogin权限Auth,在Ubuntu终端下输入:

lingaohe@lingaohe-laptop:~$ curl -d "accountType=HOSTED_OR_GOOGLE&Email=android.c2dm.demo@gmail.com&Passwd=androidc2dmdemo&service=ac2dm&source=bupt-c2dmdemo-1.0" https://www.google.com/accounts/ClientLogin

这个表示以POST的方式向https://www.google.com/accounts/ClientLogin发送数据,其中把Email和Passwd换成你自己在C2DM网页上注册的邮箱号和密码。

如果你的邮箱已在C2DM网页上注册,并且密码没有错误的话就会返回需要的Auth内容:

SID=DQAAAKYAAADcTtHbBBNcZJEOfkfVRycD_ZOIidwsQ3UwIY7cSrYWaY6uhlfo0l9gRPB-mQxP4K2T5tWiG--vWVmSTeq5p8SPwgnsYvfzj7bkNiPPIy4xRimVVfBmAHnZgLohw7gHMKi5DS6kK-Ut5tNzdTkI0I2tUDF0ryQ7MnPpI6Sj-gUCyBXmvKatHHDnNTTV78XdGIx7FYej1DyqGsPsYo3bCstHgltjv3cd2Hs7D4yrpUWHZw
LSID=DQAAAKgAAABCpaoUE4XvxM24Cofntw1IUGx5fKxX-m7aqTL0zhunP0OjzJ2sn9ywmPa1BMZ2cF2IchuxHFLVzaSQfydAmiHZJGXLgaUorpIN6yz1e0VFWKmS6j4wGjZOos3QoJ9rkha0jKbOiHfBesADjxk-qjJ24TJ0RL-xkZHQyzS69YlA1KyzqIKjAMCzgqaDfCwhqxylJzizJksO2h8xpAFXZ38d_grm8XYZtzejiCiAMAR65A
Auth=DQAAAKoAAACRF4pgYULnXULoWgbwfdqmMiRhfZYa1l-LW_rwGD7cofov4L4c2bVrtCOXbEbkju_hhqdAonpMkrb5icptt28fU8c-s-u1y2MXNYDxPIdQzfA2t6oI3NTmyj35MpsR1NKL4TN7ZVEn6z9NueuiKAqLHukZYh1YMGkGC8M6rVvA7AWPW36064XCQED7KLVNp_pGT00lrni7UdZKZWEy0FT-EVR-OxDyHWw6C-5Kmfkisw

返回的内容包括SID,LSID和Auth三个部分,其中Auth是我们需要的内容。

有了Auth和registration_id值后,我们就可以继续用curl命令模拟我们自己服务器的功能向C2DM发送要推送的数据:

lingaohe@lingaohe-laptop:~$ curl -H "Authorization:GoogleLogin auth=DQAAAKoAAACRF4pgYULnXULoWgbwfdqmMiRhfZYa1l-LW_rwGD7cofov4L4c2bVrtCOXbEbkju_hhqdAonpMkrb5icptt28fU8c-s-u1y2MXNYDxPIdQzfA2t6oI3NTmyj35MpsR1NKL4TN7ZVEn6z9NueuiKAqLHukZYh1YMGkGC8M6rVvA7AWPW36064XCQED7KLVNp_pGT00lrni7UdZKZWEy0FT-EVR-OxDyHWw6C-5Kmfkisw" -d "registration_id=APA91bGUBoSvt3G5Ny9t0IGLmIKAKYX6G6VHwSQHh3tP2fqcaQ0N4GPdKh5B3RDUHFCFF06YwT8ifOP_cOy5BAWyCLHL8d8NpuIW9AqXt9h2JSBVF2MitZA&collapse_key=1&data.msg=ichliebejiajia" https://android.apis.google.com/c2dm/send

其中发送的数据部分为data.msg=ichliebejiajia,表示发送的数据内容为ichliebejiajia,键值为msg,键值得和Android终端上的程序统一好,以便终端上可以获取。如果发送成功,会返回一个id值,比如:

id=0:1308623423080544%6c5c15c200000031
lingaohe@lingaohe-laptop:~$

这时我们的服务器就已经把数据发送给C2DM服务器了,Android设备上一会就能接收到C2DM服务器Push的数据。

在我们的例子中我们可以看到DDMS中打印出的消息:


图7 获取到的Push数据

同时Android模拟器的状态栏上会有对应的通知显示:


图8 Android模拟器接收到的Push数据

这样我们就快速实现了下Android的C2DM框架的Push功能。进一步的具体解释说明及服务器端的代码处理我们以后再学习。

Android C2DM学习 - 云端推送相关推荐

  1. .net平台借助第三方推送服务在推送Android消息(极光推送) 转

    分类: .net外部工具接口(3) .net知识精华(29) 版权声明:本文为博主原创文章,未经博主允许不得转载. 最近做的.net项目(Windows Service)需要向Android手机发送推 ...

  2. Android中的消息推送

    转载于Android中的消息推送 前段时间做了一个应用,需要用到服务器端向Android或者是Iphone终端主动发送命令.随后客户端做出相应的反应.当时没有找到最佳的方案,一直搁置着.今天看到网上有 ...

  3. 浅谈iOS和Android后台实时消息推送的原理和区别

    http://www.52im.net/thread-286-1-1.html 前言 iOS和Android上的实时消息推送差异很大,往小了说是技术实现的差异,往大了说是系统实现理念的不同.实时消息推 ...

  4. android系统下消息推送机制

    一.推送方式简介: 当前随着移动互联网的不断加速,消息推送的功能越来越普遍,不仅仅是应用在邮件推送上了,更多的体现在手机的APP上.当我们开发需要和服务器交互的应用程序时,基本上都需要获取服务器端的数 ...

  5. 无线WiFi视频传输,无线视频流云端推送方案,天工测控WiFi模块SKW99

    文章目录 前言 一.200W高清视频传输方案 1.方案背景 2.200W高清视频传输方案介绍 3.无线高清视频传输方案优势 二.无线视频流云端推送方案 1.方案背景 2.无线视频流云端推送方案介绍 3 ...

  6. android o 全机型推送,氢OS(Android O)官方更新推送 一加两款机型完成适配

    原标题:氢OS(Android O)官方更新推送 一加两款机型完成适配 在如今智能手机硬件"横行"的今天,软件系统更新关注度下降,当然苹果的iOS系统除外.在今年8月谷歌正式发布了 ...

  7. android 消息推送方法,一种基于Android系统的消息推送方法技术方案

    [技术实现步骤摘要] 本专利技术涉及一种基于Android系统的消息推送方法,属于计算机 技术介绍 推送功能在手机应用开发中越来越重要,已经成为手机开发的必选项.消息推送,就是在互联网上通过定期传送用 ...

  8. android极光推送判断消息,通过极光推送给Android所有用户发送推送消息

    https://www.cnblogs.com/yueguanguanyun/p/8485381.html 所需jar包,在maven中添加下列依赖: cn.jpush.api jpush-clien ...

  9. Android上的Mosquitto推送

    本文所用到的所有材料都在:http://pan.baidu.com/share/link?shareid=663006743&uk=1964506139 首先讲Android上的Mosquit ...

最新文章

  1. JS Range 对象的使用
  2. 构建之法第四章读后感
  3. sqlserver tds协议学习_数据安全交换协议来了,或将推动AI大步迈向3.0时代
  4. Adobe CTO:Android将超预期获50%份额
  5. mysql primary unique_MySQL中的INDEX,PRIMARY,UNIQUE,FULLTEXT之间的区别?
  6. struts2.2跟jstl怎么整合
  7. Yosemite上手动增加基于POP3协议的QQ邮箱账记
  8. hadoop jar 找不到main class_10年老架构,教你HadoopJob使用第三方依赖jar文件,不来就后悔吧...
  9. kum酷喵机器人框架pro2.2开源
  10. DBUtils详细介绍+实例
  11. 体检导检系统服务器系统,全科智能导检系统
  12. 用ENSP配置ospf
  13. 【鱼眼镜头2】[鱼眼畸变模型]:评估了五个模型:radial,division,FOV,多项式(如双三次]和rational模型。【需要修改】
  14. 二叉树的几个基本性质
  15. 2020-07-08 HTTP协议学习
  16. 多款开源免费网络相册/相册系统源码推荐
  17. Numpy删除指定行
  18. 高数 02.04隐函数的导数
  19. 基于FDC2214的手势检测系统
  20. 小酌Django2——博客文章发布

热门文章

  1. 一次编译libmono.so的记录
  2. myeclipse和输入法冲突的问题
  3. java final keyword
  4. delphi下的MVC架构-eMVC
  5. makefile进阶(四)-- 条件判断 (转)
  6. ASP.NET 程序中常用的三十三种代码(9)
  7. 乔布斯的斯坦福大学演讲:你必须要找到你所爱的东西
  8. 连咖啡首席产品经理左弘帆:all in小程序前你想清楚了吗?
  9. Python中下划线---完全解读
  10. url特殊字符转义及解决方法