本人想把Android所要运用到的所有的东西都好好的梳理一遍,那今个儿就和大伙说说消息推送;想必大家都玩过小米手机,细心的人会发现,小米手机时不时的就会发一些通知告诉你要系统升级了、某某App有新版本了等等,你说他们是咋弄的呢?其实呀,往简单点说,他们运用的就是消息推送,当然,他们那种也许会很复杂,但下面所和大家探索的消息推送已经够我们用的了!!

那么,废话就不多说了,接下来就和大家一起探索android开发之消息推送!

第一步:下载SDK

下载网址:http://docs.jpush.io/guideline/android_guide/

android gcm实现消息推送 安卓开发 消息推送_android

下载完成并解压之后,你会看到如下图所示:

android gcm实现消息推送 安卓开发 消息推送_android gcm实现消息推送_02


说明:doc是说明文档、example是示例代码、libs是工具包、AndroidManifest.xml是示例配置文件,在该文件中详细配置了如何在应用中集成JPush的配置方式、ChangeLog.txt是更新日志。

第二步:注册

登录Jpush官网注册用户,申请AppKey:

android gcm实现消息推送 安卓开发 消息推送_消息推送_03

android gcm实现消息推送 安卓开发 消息推送_android_04

第三步:集成项目

1、添加jar包和so动态库

  • 解压缩 jpush-sdk_v1.x.y.zip 集成压缩包
  • 复制 libs/jpush-sdk-release1.x.y.jar 到工程 libs/ 目录下
  • 复制 libs/armeabi/libjpush.so 到工程 libs/armeabi 目录下
  • 如果您的项目有 libs/armeabi-v7a 这个目录,请把 libjpush.so 也复制一份到这个目录。
  • 如果在libs文件夹下面没有armeabi和armeabi-v7a这两个文件夹怎么办?自己创建同名的文件夹就可以了,然后再把jar和so动态库复制过来就可以了。
  • 如果你使用的是idea工具开发的话,导入so库,需要这样:
    1、打开项目下的build.gradle

2、配置 AndroidManifest.xml

根据 SDK 压缩包里的 AndroidManifest.xml 样例文件,来配置应用程序项目的 AndroidManifest.xml 。

主要步骤为:

  • 复制备注为 “Required” 的部分
  • 将备注为替换包名的部分,替换为当前应用程序的包名
  • 将AppKey替换为在Portal上注册该应用的的Key,例如(9fed5bcb7b9b87413678c407)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="您应用的包名"
    android:versionCode="173"
    android:versionName="1.7.3"
    >
    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="17" />
    <!-- Required 自定义用来收发消息的相关权限 -->               
    <permission
        android:name="您应用的包名.permission.JPUSH_MESSAGE"
        android:protectionLevel="signature" />

    <!-- Required  一些系统要求的权限,如访问网络等-->
    <uses-permission android:name="您应用的包名.permission.JPUSH_MESSAGE" />
    <uses-permission android:name="android.permission.RECEIVE_USER_PRESENT" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>  
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />


    <!-- Optional for location -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />


    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">




        <!-- Required SDK核心功能-->
        <activity
            android:name="cn.jpush.android.ui.PushActivity"
            android:theme="@android:style/Theme.Translucent.NoTitleBar"
            android:configChanges="orientation|keyboardHidden" >
            <intent-filter>
                <action android:name="cn.jpush.android.ui.PushActivity" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="您应用的包名" />
            </intent-filter>
        </activity>
        <!-- Required  SDK核心功能-->
        <service
            android:name="cn.jpush.android.service.DownloadService"
            android:enabled="true"
            android:exported="false" >
        </service>


        <!-- Required SDK 核心功能-->
        <service
            android:name="cn.jpush.android.service.PushService"
            android:enabled="true"
            android:exported="false">
            <intent-filter>
                <action android:name="cn.jpush.android.intent.REGISTER" />
                <action android:name="cn.jpush.android.intent.REPORT" />
                <action android:name="cn.jpush.android.intent.PushService" />
                <action android:name="cn.jpush.android.intent.PUSH_TIME" />

            </intent-filter>
        </service>

        <!-- Required SDK核心功能-->
        <receiver
            android:name="cn.jpush.android.service.PushReceiver"
            android:enabled="true" >
            <intent-filter android:priority="1000">
                <action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED_PROXY" />   <!--Required  显示通知栏 -->
                <category android:name="您应用的包名" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.USER_PRESENT" />
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>
             <!-- Optional -->
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_ADDED" />
                <action android:name="android.intent.action.PACKAGE_REMOVED" />
                <data android:scheme="package" />
            </intent-filter>

        </receiver>

        <!-- Required SDK核心功能-->
        <receiver android:name="cn.jpush.android.service.AlarmReceiver" />

        <!-- User defined.    用户自定义的广播接收器-->
        <receiver
            android:name="您自己定义的Receiver"
            android:enabled="true">
            <intent-filter>
                <action android:name="cn.jpush.android.intent.REGISTRATION" /> <!--Required  用户注册SDK的intent-->
                <action android:name="cn.jpush.android.intent.UNREGISTRATION" />  
                <action android:name="cn.jpush.android.intent.MESSAGE_RECEIVED" /> <!--Required  用户接收SDK消息的intent-->
                <action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED" /> <!--Required  用户接收SDK通知栏信息的intent-->
                <action android:name="cn.jpush.android.intent.NOTIFICATION_OPENED" /> <!--Required  用户打开自定义通知栏的intent-->
                <action android:name="cn.jpush.android.intent.CONNECTION" /><!-- 接收网络变化 连接/断开 since 1.6.3 -->
                <category android:name="您应用的包名" />
            </intent-filter>
        </receiver>


        <!-- Required  . Enable it you can get statistics data with channel -->
        <meta-data android:name="JPUSH_CHANNEL" android:value="developer-default"/>
        <meta-data android:name="JPUSH_APPKEY" android:value="您的Appkey" /> <!--  </>值来自开发者平台取得的AppKey-->

    </application>
</manifest>

上面配置文件中详细说明了如何进行配置,”您的Appkey” “您应用的包名” “您自己定义的Receiver” 出现这三个字段的地方均需要开发者根据自己的应用进行响应的替换。”Required”标识的地方是必须要在配置文件中配置的。

3、开发自定义广播接收器

配置文件中有一个广播接收器,什么作用呢?主要就是接收极光推送服务器发送的广播。

那么,接下来让我们一起登录官网阅读文档:http://docs.jpush.io/client/android_api/#receiver

android gcm实现消息推送 安卓开发 消息推送_消息推送_05

这个是极光推送官网对于这个Receiver的功能的定义。如果开发者不进行这个广播接收的定义,它的功能是固定的。当开发者对于该广播接收者进行定义它的功能就很灵活了。当接收到推送推送的消息之后,让客户端如何进行响应,完全由开发者自己决定。
至于这个Receiver的配置已经固定,开发者只需要实现功能即可。

每个 Receiver action 详细解释:

android gcm实现消息推送 安卓开发 消息推送_android gcm实现消息推送_06

自定义的Receiver的代码如下:

/**
 * Created by ZaneLove on 2015/3/16.
 * 自定义接收器
 * 如果不定义这个 Receiver,则:
 * 1) 默认用户会打开主界面
 * 2) 接收不到自定义消息
 */
public class MyReceiver extends BroadcastReceiver {
    private static final String TAG = "JPush";

    @Override
    public void onReceive(Context context, Intent intent) {
        Bundle bundle = intent.getExtras();
//        Log.e(TAG, "[MyReceiver] onReceive - " + intent.getAction() + ", extras: " + printBundle(bundle));
        if (JPushInterface.ACTION_REGISTRATION_ID.equals(intent.getAction())) {
            String regId = bundle.getString(JPushInterface.EXTRA_REGISTRATION_ID);
            Log.e(TAG, "[MyReceiver] 接收Registration Id : " + regId);

        } else if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) {
            Log.e(TAG, "[MyReceiver] 接收到推送下来的自定义消息: " + bundle.getString(JPushInterface.EXTRA_MESSAGE));
            processCustomMessage(context, bundle);

        } else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) {
            Log.e(TAG, "[MyReceiver] 接收到推送下来的通知");
            int notifactionId = bundle.getInt(JPushInterface.EXTRA_NOTIFICATION_ID);
            Log.e(TAG, "[MyReceiver] 接收到推送下来的通知的ID: " + notifactionId);

        } else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) {
            Log.e(TAG, "[MyReceiver] 用户点击打开了通知");
            //打开自定义的Activity,当然这个并不是强制要求的,当用户点击推送消息是,可以不做处理的
            Intent i = new Intent(context, TestActivity.class);
            i.putExtras(bundle);
            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP );
            context.startActivity(i);

        } else if (JPushInterface.ACTION_RICHPUSH_CALLBACK.equals(intent.getAction())) {
            Log.e(TAG, "[MyReceiver] 用户收到到RICH PUSH CALLBACK: " + bundle.getString(JPushInterface.EXTRA_EXTRA));
            //在这里根据 JPushInterface.EXTRA_EXTRA 的内容处理代码,比如打开新的Activity, 打开一个网页等..

        } else if(JPushInterface.ACTION_CONNECTION_CHANGE.equals(intent.getAction())) {
            boolean connected = intent.getBooleanExtra(JPushInterface.EXTRA_CONNECTION_CHANGE, false);
            Log.w(TAG, "[MyReceiver]" + intent.getAction() +" connected state change to "+connected);
            //这个获取的是当前登录用户的连接状态。这里的连接状态是指用户与Jpush推送服务器的连接状态。

        } else {
            Log.e(TAG, "[MyReceiver] Unhandled intent - " + intent.getAction());
        }
    }

    // 打印所有的 intent extra 数据 这里只是打印获取的数据的方法
    private static String printBundle(Bundle bundle) {
        StringBuilder sb = new StringBuilder();
        for (String key : bundle.keySet()) {
            if (key.equals(JPushInterface.EXTRA_NOTIFICATION_ID)) {
                sb.append("\nkey:" + key + ", value:" + bundle.getInt(key));
            }else if(key.equals(JPushInterface.EXTRA_CONNECTION_CHANGE)){
                sb.append("\nkey:" + key + ", value:" + bundle.getBoolean(key));
            }
            else {
                sb.append("\nkey:" + key + ", value:" + bundle.getString(key));
            }
        }
        return sb.toString();
    }

    /**
     *send msg to MainActivity
     *这个方法是获取到推送的消息之后再次发送广播到其他的广播接收器当中,
     *目的就是传递获取的广播数据,进行相应的处理,实现相应的功能,开发者在这里
     *完全可以自己实现自己想要的功能。不一定非要使用这种方式。
     **/
    private void processCustomMessage(Context context, Bundle bundle) {
        /*if (MainActivity.isForeground) {
            String message = bundle.getString(JPushInterface.EXTRA_MESSAGE);
            String extras = bundle.getString(JPushInterface.EXTRA_EXTRA);
            Intent msgIntent = new Intent(MainActivity.MESSAGE_RECEIVED_ACTION);
            msgIntent.putExtra(MainActivity.KEY_MESSAGE, message);
            if (!ExampleUtil.isEmpty(extras)) {
                try {
                    JSONObject extraJson = new JSONObject(extras);
                    if (null != extraJson && extraJson.length() > 0) {
                        msgIntent.putExtra(MainActivity.KEY_EXTRAS, extras);
                    }
                } catch (JSONException e) {
                }
            }
            context.sendBroadcast(msgIntent);//发送广播
        }*/
    }
}

4、设置别名和标签

登录:http://docs.jpush.io/client/android_api/#api_1,先看看Jpush官网对于别名和标签的描述:

android gcm实现消息推送 安卓开发 消息推送_包名_07

其实说白了,我的理解就是让jpush官网的消息推送服务器知道它要把消息推送给哪一个或者哪一些人手机app中。极光推送服务器就是通过别名和标签区别每一个使用开发者开发的app的用户的。有了这样的区别或者说是标识,激光推送就可以定向发送,甚至也可以批量发送。
理解了这一点,那么就可以着手设置别名和标签了。

public static void setAliasAndTags(Context context,String alias,Set tags,TagAliasCallback callback) 
这个接口同时设置别名和标签。

public static void setAlias(Context context, String alias, TagAliasCallback callback) 
这个接口用于设置别名。

public static void setTags(Context context, Set tags, TagAliasCallback callback) 
这个接口用于设置标签。 

这三个接口都是jpush的工具包中的JPushInterface类下的静态方法,可以直接调用。

官网示例代码:

//这行代码即实现了设置别名和标签。由于别名和标签都是用于给
//Jpush服务器区分用户的,不一定非要同时设置别名和标签。也就是说
//只设置了别名也是可以的。并且也不要求别名和标签的唯一性。
//开发者可以根据自己的需要进行设置。
//例如 我只设置别名,并且别名是唯一的,只用给jpush服务器给哪些别名发送推送就可以了。
//或者说设置了别名并且唯一,但是根据标签进行了分组,同一个标签下面包含了多个别名,按照标签就可以实现群发功能了。
JPushInterface.setAliasAndTags(getApplicationContext(), “aliasAndTag”, null, mAliasCallback);

/**
*TagAliasCallback类是JPush开发包jar中的类,用于
*设置别名和标签的回调接口,成功与否都会回调该方法
*同时给定回调的代码。如果code=0,说明别名设置成功。
**/
private final TagAliasCallback mAliasCallback = new TagAliasCallback() {
    @Override
    public void gotResult(int code, String alias, Set<String> tags) {
        String logs ;
        switch (code) {
        case 0:
            logs = "Set tag and alias success";
            Log.i(TAG, logs);
            break;
        case 6002:
            logs = "Failed to set alias and tags due to timeout. Try again after 60s.";
            Log.i(TAG, logs);
            if (ExampleUtil.isConnected(getApplicationContext())) {
                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_ALIAS, alias), 1000 * 60);
            } else {
                Log.i(TAG, "No network");
            }
           break;
       default:
           logs = "Failed with errorCode = " + code;
           Log.e(TAG, logs);
       }
       ExampleUtil.showToast(logs, getApplicationContext());
   }
};

那么,问题来了?何时设置别名呢?

如果设置别名或者标签,并且想要设置唯一性,可以在使用app的用户成功登录之后进行设置。此时有关用户的登录名已经有了,可以给定用户的登录名或者userId这样具有唯一性的标识作为别名或者标签。
JPush官网并没有强制别名的设置时机,所以开发者根据需要进行设置即可。在本人开发的应用当中,是在用户自己根据用户名和密码成功登录之后,从后台获取到用户的信息之后,用userId来设置别名的。因为userId每个用户是不同的,具备唯一性,即使是群发,那么可以从app把要发送的用户的userId发送给后台,后台再把群发的userId提交给jpush服务器,然后jpush服务器向app推送消息。这样就完成了消息的推送,并且只推送到想要发送的用户的app端。经过这样的流程,想必各位已经知道了别名的用途了。

因此,假设MainActivity 就是启动之后的主界面,代码如下:

package com.zanelove.jpushdemo.jpush;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Window;
import android.widget.Toast;
import cn.jpush.android.api.JPushInterface;
import cn.jpush.android.api.TagAliasCallback;

import java.util.Set;


public class MainActivity extends Activity {
    private static final int  GET_MSG_SUC = 0;
    private String TAG = MainActivity.class.getName();
    private static final int MSG_SET_ALIAS = 1001;

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case GET_MSG_SUC:
                /**
                 *这里设置了别名,是因为了我开发的app中,是在这里获取的用户登录的信息
                 *并且此时已经获取了用户的userId,然后就可以用用户的userId来设置别名了
                 */
                    Log.e(TAG, "触发广播,接收到广播信息,说明必备数据获取成功");
                    setAlias(19940316);//设置极光推送的别名
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        //判断当前手机网络状态的工具类,没什么特殊的,网上很多,就不详细写了

        if(!(0==0)){
            //友好提示
        }else {
            // JPush初始化
            JPushInterface.setDebugMode(false); // 设置true 开启日志,发布时需关闭日志false即可
            JPushInterface.init(this); // 初始化 Jpush 固定的方法,JPushInterface类中的静态方法
            Message message = Message.obtain();
            message.what = 0;
            handler.sendMessage(message);
        }
    }

    @Override
    protected void onPause() {
        JPushInterface.onPause(MainActivity.this);
        super.onPause();
    }

    @Override
    protected void onResume() {
//      极光推送服务会恢复正常工作
        JPushInterface.onResume(MainActivity.this);
        super.onResume();
    }

    /**
     * Jpush设置
     */
    private void setAlias(int userId) {
        String alias = String.valueOf(userId);
        // 调用JPush API设置Alias
        mHandler2.sendMessage(mHandler2.obtainMessage(MSG_SET_ALIAS, alias));
        Log.d(TAG, "设置Jpush推送的别名alias=" + alias);
    }

    @SuppressLint("HandlerLeak")
    private final Handler mHandler2 = new Handler() {//专门用了一个Handler对象处理别名的注册问题
        @Override
        public void handleMessage(android.os.Message msg) {
            super.handleMessage(msg);
            Log.d(TAG, "设置激光推送的别名-mHandler2");
            JPushInterface.setAliasAndTags(getApplicationContext(), (String) msg.obj, null, mAliasCallback);
            Toast.makeText(MainActivity.this, "成功", Toast.LENGTH_SHORT).show();
        }
    };

    private final TagAliasCallback mAliasCallback = new TagAliasCallback() {
        @Override
        public void gotResult(int code, String alias, Set<String> tags) {
            String logs;
            switch (code) {
                case 0:
                    logs = "Set tag and alias success极光推送别名设置成功";
                    Log.e(TAG, logs);
                    break;
                case 6002:
                    logs = "Failed to set alias and tags due to timeout. Try again after 60s.极光推送别名设置失败,60秒后重试";
                    Log.e(TAG, logs);
                    mHandler2.sendMessageDelayed(mHandler2.obtainMessage(MSG_SET_ALIAS, alias), 1000 * 60);
                    break;
                default:
                    logs = "极光推送设置失败,Failed with errorCode = " + code;
                    Log.e(TAG, logs);
                    break;
            }
        }
    };
}

这里只是示例了别名设置的时机和别名的注册。是不是很简单?确实不难。可能有人会问怎么没有Receiver广播接收器?这个开发者自定义的广播接收器我们已经在配置文件中配置好了是不是?广播接收器有两种配置方式,一种是在配置文件中进行配置,另一种实在代码中进行注册,并在onDestory中注销。但是jpush给定的AndroidManifest.xml示例中已经对开发者自定义的广播接收器进行了配置,所以在MainActivity中并没有对并广播接收器的注册。

当jpush服务器推送消息到客户端app,配置好的广播接收器会自动的响应,执行onReceive方法,根据类型进行处理。

至此全部完成了对jpush消息推送的处理工作了,当然,jpush的功能还有很多,例如统计分析 API、清除通知 API、
设置允许推送时间 API、设置通知静默时间 API、通知栏样式定制 API、设置保留最近通知条数 API、富文本页面 Javascript 回调API、本地通知API等这些功能开发者都可以进行设置,把这些功能添加到自己的应用当中。
关于这些功能,请参考jpush官网api:http://docs.jpush.io/client/android_api/#api_8

附带:当用户点击通知状态栏之后,发送的广播,并会跳转到指定的Activity中:

/**
 * Created by ZaneLove on 2015/3/16.
 */
public class TestActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test_layout);
    }
}

示例代码戳Here