一、基础知识

当我们开发需要和服务器交互的应用程序时,基本上都需要获取服务器端的数据,比如《地震及时通》就需要及时获取服务器上最新的地震信息。要获取服务器上不定时更新的信息一般来说有两种方法,第一种是客户端使用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. Android应用实现Push推送消息原理

            本文介绍在Android中实现推送方式的基础知识及相关解决方案.推送功能在手机开发中应用的场景是越来起来了,不说别的,就我 们手机上的新闻客户端就时不j时的推送过来新的消息,很方便的阅 ...

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

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

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

    最近做的.net项目(Windows Service)需要向Android手机发送推送消息,真是有点困难,没有搞过就不停的搜文档,最后看到了一个开源项目PushSharp,可以在.net平台推送IOS ...

  4. 【Android应用开发】 推送原理解析 极光推送使用详解 (零基础精通推送)

    作者 : octopus_truth 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/45046283 推送技术产生场景 : -- ...

  5. Android 几种消息推送方案总结

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/6241354.html 首先看一张国内Top500 Android应用中它们用到的第三方推送以及所占数量: 现 ...

  6. 1、Android Studio集成极光推送(Jpush) 报错 java.lang.UnsatisfiedLinkError: cn.jpush.android.service.PushProtoco

    Android studio 集成极光推送(Jpush) (华为手机)报错, E/JPush: [JPushGlobal] Get sdk version fail![获取sdk版本失败!] W/Sy ...

  7. android 实现mqtt消息推送,以及不停断线重连的问题解决

    前段时间项目用到mqtt的消息推送,整理一下代码,代码的原型是网上找的,具体哪个地址已经忘记了. 代码的实现是新建了一个MyMqttService,全部功能都在里面实现,包括连服务器,断线重连,订阅消 ...

  8. Android开发之第三方推送JPush极光推送知识点详解 学会集成第三方SDK推送

    作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 下面是一些知识点介绍,后期将会带领大家进行代码实战: 一.Android实现推送方式解决方案: 1.推 ...

  9. Android中实现消息推送(JPush)

    1,去JPush官网注册一个账号,创建你的app的应用,并且拿到你应用的AppKey 2,在JPush官网下载对应的sdk,解压出来,将libs文件下的所有的文件全部复制到你工程的libs文件中 3, ...

随机推荐

  1. 网络编程 --- URLConnection --- 读取服务器的数据 --- java

    使用URLConnection类获取服务器的数据 抽象类URLConnection表示一个指向指定URL资源的活动连接,它是java协议处理器机制的一部分. URL对象的openConnection( ...

  2. VS2013中修改.dll工程项目的.lib和.dll的输出路径

    一个dll工程,生成的两个东西是我们需要的:.lib和.dll,在实际开发过程中我们往往希望这两个东西直接输出到特定文件夹,对于这两个的修改: 设置好之后,对于调用该dll的exe工程,将exe的输出 ...

  3. 实例化spring容器

    方法一:在类路径下寻找配置来实例化容器 ApplicationContext ctx = new String[]{"beans.xml"}); 方法二:在文件系统路径下寻找配置文 ...

  4. Scrum流程

    敏捷Scrum流程图: Sprint Planing Meeting: 1.Next Spring Goal; 2.Sprint Backlog; 3.Updated Product Backlog; ...

  5. hadoop的simple认证

    目前Hadoop的稳定版本为1.2.1,我们的实验就在hadoop-1.2.1上进行 Hadoop 版本:1.2.1 OS 版本: Centos6.4 环境配置 机器名 Ip地址 功能 用户 Hado ...

  6. Use weakref module in a cache or mapping

    The weakref module allows the Python programmer to create weak references to objects. In the followi ...

  7. How to Keep Alive SSH Sessions

    How to Keep Alive SSH Sessions Many NAT firewalls time out idle sessions after a certain period of t ...

  8. C++11显式虚函数重载

    [C++11显式虚函数重载] 在子类中给重载的虚函数加上override, 可以让编译器检察基类是否有这一虚函数.此功能适用于当基类原有的虚函数发生变化,即相当于编译期检察. 而基类,可以给函数加上f ...

  9. Mcafee两个Mac版本之间的区别

    近期打算为Mac安装个杀毒软件,由于自己windows平台下用的是VSE,所以Mac平台也首选Mcafee家的东西了.到Mcafee官网下载点一看,有以下几个版本可以用在Mac上: 有点懵了,查看了一 ...

  10. Python字典 (dictionary)

    字典dict,是Python唯一的标准mapping类型,也是内置在Python解释器中的. mapping object把一个可哈希的值(hashable value)映射到一个任意的object上 ...