GCM(谷歌云推送)客户端服务器端开发全指南(客户端)

最近因为手头上的工作所以耽误了一下指南客户端篇的编写,而且客户端的功能实现是比较复杂的,处理的逻辑也是比较多的,所以也花了点时间去研究了一下。

没有看我之前两篇博客的朋友这个demo是实现不了的,建议先去看了再来看这篇

GCM(谷歌云推送)客户端服务器端开发全指南(服务器篇)
GCM(谷歌云推送)客户端服务器端开发全指南

现在我已经假设你的push服务器已经搭建好了,API KEY和SENDER也已经拿到手了,这样就可以继续我们今天的开发,因为Google已经放弃了Eclipse,所以在GCM的官网上你已经找不到有关Eclipse的教程,但是配置文件的设置等方法我们还是能参照的。

好的,我们正式开始客户端的开发,首先你需要的是gcm.jar这个jar包,这个jar包提供regID的注册、消息的处理等方法,楼主曾经在百度上找,但找到的都是比较旧的版本,在结合我的demo后会出现预料之外的错误,所以楼主决定从SDK Manager上去下载。

找到你androidSDK的目录,并点击进去,找到SDK Manager.exe并打开
GCM(谷歌云推送)客户端服务器端开发全指南(客户端)_第1张图片

在Extras中找到Google Cloud Messaging for Android Library并安装,如果你没有这个选项,把Obsolete勾选上你就能看到了,这个单词是“过时的”意思,我想你应该能猜到什么原因了吧。

当你下载安装好了之后,你就能在
andrioidSDK\sdk\extras\google\gcm\samples\gcm-demo-client\libs
找到gcm.jar这个jar包
GCM(谷歌云推送)客户端服务器端开发全指南(客户端)_第2张图片

把这个jar包拷贝到你的项目libs文件夹里面就可以使用了

然后我们就可以开始写代码了,首先我把注册的流程划分了一下

经过以上流程图的分析,相信大家对我注册regID的流程有一定的了解

以下是我的MianActivity类:

package com.x.gcmdemo;

import com.google.android.gcm.GCMRegistrar;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends Activity {

    TextView messagetext;
    private boolean supperGCM = false;
    private String regId = "";
    private SaveRegIdThead saveRegIdThead;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        CheckGoogleService.mContext = getApplicationContext();
        //检验是否支持GCM,若支持则在最后赋true给supperGCM
        if(CheckGoogleService.checkDevice()){if(CheckGoogleService.checkManifest()){supperGCM = true;}}
        setContentView(R.layout.activity_main);
        findById();
        //判断是否支持GCM,支持则开始注册
        if(supperGCM){goRegister();}
    }

    private void goRegister() {
        //注册一个全局广播,播放收到的消息
        registerReceiver(mGcmConntionStatuReceiver,new IntentFilter(CommonUtilitie.getInstance().getSendBroadAction()));

        //获取regID
        regId = GCMRegistrar.getRegistrationId(this);
        Log.v("MainActivity", "regId = " + regId);

        if(regId.equals("")){
            //如果regID获取的值为空,则去注册一个
             GCMRegistrar.register(this, CommonUtilitie.getInstance().getSenderId());
           //其实到这里我们的设备已经可以接收到google推送的消息了,但是根据google 的建议,我们最好把 regID保存到我们的服务器中,不要保存到本地,这里有个安全的问题
        }else{
            //GCMRegistrar提供了一个方法可以检查GCM是否已经保存到自己的server
            if(GCMRegistrar.isRegisteredOnServer(this)){
                //若已经保存,则通知在等待消息
                messagetext.setText(R.string.registered + "\n");
            }else{
                //若没有保存,启动保存线程,保存regID
                messagetext.setText(R.string.registered + "\n");
                saveRegIdThead = new SaveRegIdThead(this);
                saveRegIdThead.execute(regId);
            }
        }
    }



    private void findById() {
        messagetext = (TextView) findViewById(R.id.messagetext);
    }



    private final BroadcastReceiver mGcmConntionStatuReceiver = new BroadcastReceiver(){
        @Override
        public void onReceive(Context context, Intent intent) {
            //这里的全局广播用于显示保存regID到服务器的进度以及一些提示
            String Message = intent.getExtras().getString(CommonUtilitie.getInstance().getIntentMessage());
            messagetext.setText(messagetext.getText().toString() + Message + "\n");
        }
    };

    @Override
    protected void onDestroy() {
        //注销所有的实例以腾出内存
        if(supperGCM){
            if (saveRegIdThead != null) {
                saveRegIdThead.cancel(true);
            }
            GCMRegistrar.onDestroy(this);
        }
         unregisterReceiver(mGcmConntionStatuReceiver);
        super.onDestroy();
    }
}

代码片上我都添加了详细的注释,这里大概说明一下我调用的类和他们处理的事情:

  • CheckGoogleService
    这个类是攻来检查设备支不支持GCM的

  • GCMRegistrar
    这个类是gcm.jar提供给我们用来处理regID的逻辑的,包括注册、注销、记录等。

  • CommonUtilitie
    CommonUtilitie是我自己写的一个全局单例工具类,可以让任意类使用,里面维护着一些要用到的信息。

  • SaveRegIdThead
    SaveRegIdThead是用于异步保存regID的类.

然后让我们先看一下CheckGoogleService

package com.x.gcmdemo;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.google.android.gcm.GCMConstants;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;


/** *这个类是检查当前设备支不支持google service,<br> * Google自带的类如果检测到不支持就会 Throw the exception<br> * 我读过他的源码后发现不支持的情况下最多只是收不到消息,<br> * 并不会影响程序的执行,所以决定rewrite他的代码,<br> * 但是考虑到每个人想到的逻辑可能不一样,google的程序员选择抛出异常一定有他的理由<br> * 所以rewrite写成返回true or false,false就不进行注册<br> * * @author xie * */
public class CheckGoogleService {

    static Context mContext;
    private static final String GSF_PACKAGE = "com.google.android.gsf";
    private static String TAG = "CheckGoogleService";


    //检查当前手机设备是否支持,Android2.2以上
    public static boolean checkDevice(){
        int version = Build.VERSION.SDK_INT;
        if(version < 8){
            Toast.makeText(mContext, "Your phone's version is too low.", Toast.LENGTH_LONG).show();
            return false;
        }

        PackageManager packageManager = mContext.getPackageManager();

        try {
            //尝试提取google包信息
            packageManager.getPackageInfo(GSF_PACKAGE, 0);
        } catch (NameNotFoundException e) {
            Toast.makeText(mContext, "Your phone isn't install google service.", Toast.LENGTH_LONG).show();
            return false;
        }
        return true;
    }

    public static boolean checkManifest(){
        PackageManager packageManager = mContext.getPackageManager();
        String packageName = mContext.getPackageName();
        String permissionName = packageName + ".permission.C2D_MESSAGE";
        // 检查是否已经添加了权限
        try {
            packageManager.getPermissionInfo(permissionName,
                    PackageManager.GET_PERMISSIONS);
        } catch (NameNotFoundException e) {
            Toast.makeText(mContext, "No permission to access,are you add 'permission.C2D_MESSAGE' in AndroidManifest.xml?", Toast.LENGTH_LONG).show();
            return false;
        }       

        // 检查接收器是否正常工作
        PackageInfo receiversInfo = null;
        try {
            receiversInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_RECEIVERS);
        } catch (NameNotFoundException e) {
            Toast.makeText(mContext, "RECEIVERS has some exception", Toast.LENGTH_LONG).show();
            return false;
        }

        ActivityInfo[] receivers = receiversInfo.receivers;
        if (receivers == null || receivers.length == 0) {

            return false;
        }
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "number of receivers for " + packageName + ": " +
                    receivers.length);
        }


        Set<String> allowedReceivers = new HashSet<String>();
        for (ActivityInfo receiver : receivers) {
            if (GCMConstants.PERMISSION_GCM_INTENTS.equals(receiver.permission)) {
                allowedReceivers.add(receiver.name);
            }
        }
        if (allowedReceivers.isEmpty()) {
            Toast.makeText(mContext, "No receiver allowed to receive " + GCMConstants.PERMISSION_GCM_INTENTS, Toast.LENGTH_LONG).show();
            return false;
        }

        checkReceiver(mContext, allowedReceivers,
                GCMConstants.INTENT_FROM_GCM_REGISTRATION_CALLBACK);
        checkReceiver(mContext, allowedReceivers,
                GCMConstants.INTENT_FROM_GCM_MESSAGE);


        return true;
    }


    private static void checkReceiver(Context context,
            Set<String> allowedReceivers, String action) {
        PackageManager pm = context.getPackageManager();
        String packageName = context.getPackageName();
        Intent intent = new Intent(action);
        intent.setPackage(packageName);
        List<ResolveInfo> receivers = pm.queryBroadcastReceivers(intent,
                PackageManager.GET_INTENT_FILTERS);
        if (receivers.isEmpty()) {
            throw new IllegalStateException("No receivers for action " +
                    action);
        }
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "Found " + receivers.size() + " receivers for action " +
                    action);
        }
        // make sure receivers match
        for (ResolveInfo receiver : receivers) {
            String name = receiver.activityInfo.name;
            if (!allowedReceivers.contains(name)) {
                Toast.makeText(context,"Receiver " + name +" is not set with permission " +GCMConstants.PERMISSION_GCM_INTENTS, Toast.LENGTH_LONG).show();
            }
        }
    }

}

这个类涉及的是关于包的信息的提取,这里不再多阐述,我们继续看一下CommonUtilitie

package com.x.gcmdemo;

import android.content.Context;
import android.content.Intent;

/** * * 建立一个工具类,工具类维护着处理regid、消息保存的url等逻辑,作为一个工具,可以被多人使用,所以用单例模式创建 * */
public class CommonUtilitie {

        private static CommonUtilitie instance = null;
        //保存regID的服务器的URL
        private final String SERVICE_URL = "http://192.168.1.114:9002/saveRegID.asmx";
        //SenderId
        private final String SENDER_ID = "你的SENDERID";
        //发送接受广播使用的action
        private final String SENDBROADACTION = "textchangemessage";

        private final String INTENTMESSAGE = "com.x.gcmdemo.INTENTMESSAGE";

        public static CommonUtilitie getInstance(){
            if(instance==null){
                synchronized(CommonUtilitie.class){
                    if(instance==null){
                        instance=new CommonUtilitie();
                    }
                }
            }
            return instance;
        }


        public String getIntentMessage(){
            return INTENTMESSAGE;
        }

        public String getSendBroadAction(){
            return SENDBROADACTION;
        }

        public String getServiceUrl(){
            return SERVICE_URL;
        }

        public String getSenderId(){
            return SENDER_ID;
        }

        public void SendMessageToMessageText(Context context,String message){
            Intent intent = new Intent(SENDBROADACTION);
            intent.putExtra(INTENTMESSAGE, message);
            context.sendBroadcast(intent);
        }
}

工具类也在上面已经说明了,而且根据单词的意思也能看出具体用途,这里也不多说,让我们继续看下去,接着是SaveRegIdThead类:

package com.x.gcmdemo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Map.Entry;

import com.google.android.gcm.GCMRegistrar;

import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;

/** * <p>这个类继承了AsyncTask并实现异步访问网络</p> * * */
public class SaveRegIdThead extends AsyncTask<String,String, String>{

    private final int MAX_ATTEMPTS_CONNECTION = 5;

    private final String TAG = "SaveRegIdThead";

    private Context mContext;

    private static final int BACKOFF_MILLI_SECONDS = 2000;

    private static final Random random = new Random();

    SaveRegIdThead(Context context){
        this.mContext = context;
    }

    @Override
    protected String doInBackground(String... regId) {

        String result="";
        String serverUrl = CommonUtilitie.getInstance().getServiceUrl();
        Map<String, String> params = new HashMap<String, String>();
        params.put("regid", regId[0]);

        publishProgress("准备网络.....");
        //防止被网络抖动的原因影响到我们的访问,我们这里设置一个数,让线程至少2秒之后再去访问网络
        long backoff = BACKOFF_MILLI_SECONDS + random.nextInt(1000);

        for(int i = 1; i <=MAX_ATTEMPTS_CONNECTION ; i++){
            Log.v(TAG, backoff + "");
            try{
                publishProgress(mContext.getString(R.string.server_connect_tip, i, MAX_ATTEMPTS_CONNECTION-i));
                publishProgress("正在链接.....");
                result = goSave(serverUrl, params);
                //成功保存到服务器通知google
                GCMRegistrar.setRegisteredOnServer(mContext, true);
                publishProgress("成功保存到服务器");
            }catch(IOException e){

                if(i==MAX_ATTEMPTS_CONNECTION){
                    publishProgress("多次尝试失败,请检查网络链接");
                    break;
                }
                try {
                    Thread.sleep(backoff);
                } catch (InterruptedException e1) {
                    Thread.currentThread().interrupt();
                }
                backoff *= 2;
            }
        }

        return result;
    }

    @Override
    protected void onProgressUpdate(String... values){
        CommonUtilitie.getInstance().SendMessageToMessageText(mContext,values[0]);
    }

    @Override
    protected void onPostExecute(String result){
        if(result!=null&&result.equals("")){
            CommonUtilitie.getInstance().SendMessageToMessageText(mContext,"regID保存处理完成,返回的代码是:" + result);
        }
    }

    private String goSave(String serverUrl, Map<String, String> params) throws IOException{
        URL url;
        String result = "";
        try {
            //这里的URL是需要自己开发保存regID功能的服务,如果没有也不影响我们功能的实现
            url = new URL(serverUrl);
        } catch (MalformedURLException e) {
            Toast.makeText(mContext,"invalid url: " + serverUrl, Toast.LENGTH_LONG).show();
            return "";
        }

        //使用迭代器一个个设置好httpbody的内容
        StringBuilder httpbodyBuilder = new StringBuilder();
        Iterator<Entry<String, String>> iterator = params.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<String, String> param = iterator.next();
            httpbodyBuilder.append(param.getKey()).append('=')
                    .append(param.getValue());
            if (iterator.hasNext()) {
                httpbodyBuilder.append('&');
            }
        }

        String httpbody = httpbodyBuilder.toString();
        byte[] bytes = httpbody.getBytes();

        //定义HttpURLConnection,google从android5.0开始就不再支持HttpClient了,习惯一下使用HttpURLConnection是好事
        HttpURLConnection conn = null;
        Log.v(TAG, "开始连接");
        try {
            conn = (HttpURLConnection) url.openConnection();
            conn.setDoOutput(true);
            conn.setUseCaches(false);
            conn.setFixedLengthStreamingMode(bytes.length);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
            OutputStream out = conn.getOutputStream();
            out.write(bytes);
            out.close();

            int status = conn.getResponseCode();
            if (status != 200) {
              throw new IOException("requect fail ,error is" + status);
            }
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
        return result;
    }

}

由于我们只需要一次访问网络(除非网络出了问题)我就没有使用第三方的网络访问框架,只是自己写了一个简单的网络访问方法。

到这里如果你的程序执行没问题的话,你已经能接收到消息了,现在我们来写接收消息的services,

package com.x.gcmdemo;

import com.google.android.gcm.GCMBaseIntentService;

import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.util.Log;


/** * <p>以下包含的是GCM返回会调用到的方法: * <ol> * <li>onRegistered(Context context, String regId): 收到注册Intent后此方法会被调用,GCM分配的注册ID会做为参数传递到设备/应用程序对。通常,你应该发送regid到你的服务器,这样服务器就可以根据这个regid发消息到设备上。 * <li>onUnregistered(Context context, String regId): 当设备从GCM注销时会被调用。通常你应该发送regid到服务器,这样就可以注销这个设备了。 * <li>onMessage(Context context, Intent intent): 当你的服务器发送了一个消息到GCM后会被调用,并且GCM会把这个消息传送到相应的设备。如果这个消息包含有效负载数据,它们的内容会作为Intent的extras被传送。 * <li> onError(Context context, String errorId): 当设备试图注册或注销时,但是GCM返回错误时此方法会被调用。通常此方法就是分析错误并修复问题而不会做别的事情。 * <li>onRecoverableError(Context context, String errorId): 当设备试图注册或注销时,但是GCM服务器无效时。GCM库会使用应急方案重试操作,除非这个方式被重写并返回false。这个方法是可选的并且只有当你想显示信息给用户或想取消重试操作的时候才会被重写。 * </ol> * </p> * */
public class GCMIntentService extends GCMBaseIntentService{


     private static final String TAG = "GCMIntentService";

        public GCMIntentService() {
            super(CommonUtilitie.getInstance().getSenderId());
        }

        @Override
        protected void onRegistered(Context context, String registrationId) {
            CommonUtilitie.getInstance().SendMessageToMessageText(context, "GCM注册成功!!");
        }

        @Override
        protected void onUnregistered(Context context, String registrationId) {
            //这个函数是你注销regID时调用的,一般用不到,Google有规定一个regID默认能使用的时间是7天,7天后regID自动无效,需要重新注册
            //这个时间也可以自己设置
        }


        @Override
        protected void onMessage(Context context, Intent intent) {
            //这个是接收到消息的回调方法
            String message = intent.getStringExtra("message");
            // 使用通知栏进行推送
            generateNotification(context, message);
        }


        @Override
        public void onError(Context context, String errorId) {
            CommonUtilitie.getInstance().SendMessageToMessageText(context, "注册错误,错误代码是:" + errorId);
        }

        @Override
        protected boolean onRecoverableError(Context context, String errorId) {
            CommonUtilitie.getInstance().SendMessageToMessageText(context, "onRecoverableError错误,错误代码是:" + errorId);
            return super.onRecoverableError(context, errorId);
        }

        /** * Issues a notification to inform the user that server has sent a message. */
        private static void generateNotification(Context context, String message) {

            NotificationManager notificationManager = (NotificationManager)
                    context.getSystemService(Context.NOTIFICATION_SERVICE);

            NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context);
            mBuilder.setContentTitle(context.getString(R.string.app_name))
                    .setSmallIcon(R.drawable.ic_launcher)
                    .setTicker("你有一条新消息")
                    .setStyle(new NotificationCompat.BigTextStyle())
                    .setContentText(message)
                    .setDefaults(Notification.DEFAULT_ALL)
                    .setWhen(System.currentTimeMillis())
                    .setAutoCancel(true);
            notificationManager.notify(0, mBuilder.build());

        }

}

另外,这里我们还有一些权限是需要添加给google service的,这在GCM的官网上有说明,然后以下就是AndroidManifest.xml的内容:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x.gcmdemo" android:versionCode="1" android:versionName="1.0" >

    <uses-sdk  android:minSdkVersion="8" android:targetSdkVersion="21" />

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<permission android:name="com.x.gcmdemo.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="com.x.gcmdemo.permission.C2D_MESSAGE" /> 
<!-- 允许App接收来自google的消息 -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

<!-- 获取google账号需要的权限 -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<!-- 即使在锁屏状态也能收到消息 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />


    <application  android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" >
        <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>


        <receiver android:name="com.google.android.gcm.GCMBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >

          <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
            <category android:name="com.x.gcmdemo" />
          </intent-filter>
</receiver> 
        <service android:name="com.x.gcmdemo.GCMIntentService" />

    </application>

</manifest>

好了,到这里你已经能接收来自GCM的消息了,赶快试一下

接下来又是源代码时刻了

源代码在此
资源上传不知干嘛了,上传到99%就卡在那里,一个小时都没反应,所以改用百度网盘
http://pan.baidu.com/s/1kUR1lu3

至此,GCM开发指南就全部讲完了,有没有自己的水平又提升了的感觉?有就对了,要趁着这个势头,继续向CSDN里的大神学习。

你可能感兴趣的:(android,gcm,GCM-谷歌云推送)