您现在的位置是:首页 > 文章详情

三分钟帮你集成极光推送——和那些你可能不知道的事

日期:2018-05-08点击:249

本文简介:本文前篇,可以帮助朋友们快速集成极光推送。本文后篇,是我自己项目实践的一些总结和心得,应该对读者们还是很有参考价值的,相信读完这篇文章,你会对极光推送有更加深入的理解,而不仅仅只是会集成而已。总之呢,集成第三方SDK,都不是很难的事情,仔细阅读文档,一步步来,遇到Bug,慢慢解决就行,实在解决不了,可以问问客服小哥哥或者小姐姐,重要的是,你得有着解决它的决心和耐心。

《一》JPush SDK的集成

简要介绍:
极光推送(JPush)是一个端到端的推送服务,使得服务器端消息能够及时地推送到终端用户手机上,让开发者积极地保持与用户的连接,从而提高用户活跃度、提高应用的留存率。

开发者集成 JPush Android SDK 到其应用里,JPush Android SDK 创建到 JPush Cloud 的长连接,为 App 提供永远在线的能力。 当开发者想要及时地推送消息到达 App 时,只需要调用 JPush API 推送,或者使用其他方便的智能推送工具,即可轻松与用户交流。
JPush Android SDK 是作为 Android Service 长期运行在后台的,从而创建并保持长连接,保持永远在线的能力。

假设你已经注册了极光的账号,登录进来了。点击立即体验,创建自己的应用。


img_fd763efcd717770649e38dcdf9e6d0a6.png
image.png
img_b10fd3e208c9c4a9ffca4031269566dd.png
image.png

App端集成需要用到的AppKey。


img_d9632deec4f6279e4149c666585edf13.png
image.png

添加集成代码,此处使用jcenter的方式集成。
一、确认android studio的 Project 根目录的主 gradle 中配置了jcenter支持。(新建project默认配置就支持)

 buildscript { repositories { jcenter() } ...... } allprojets { repositories { jcenter() } } 

二、在 module 的 gradle 中添加依赖和AndroidManifest的替换变量。

android { ...... defaultConfig { applicationId "com.xxx.xxx" //JPush上注册的包名. ...... ndk { //选择要添加的对应cpu类型的.so库。 abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a' // 还可以添加 'x86', 'x86_64', 'mips', 'mips64' } manifestPlaceholders = [ JPUSH_PKGNAME : applicationId, JPUSH_APPKEY : "你的appkey", //JPush上注册的包名对应的appkey. JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可. ] ...... } ...... } dependencies { ...... compile 'cn.jiguang.sdk:jpush:3.1.1' // 此处以JPush 3.1.1 版本为例。 compile 'cn.jiguang.sdk:jcore:1.1.9' // 此处以JCore 1.1.9 版本为例。 ...... } 

三、AndroidManifest.xml中添加

 <!--Jpush配置 所需权限start--> <!-- 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.WRITE_SETTINGS" /> <!--<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.ACCESS_WIFI_STATE" />--> <!-- Optional for location --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <!-- 用于开启 debug 版本的应用在6.0 系统上 层叠窗口权限 --> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!--<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" />--> <!--<uses-permission android:name="android.permission.GET_TASKS" />--> <!--Jpush配置 所需权限end--> <!--Jpush配置 start--> <application <!-- Rich push 核心功能 since 2.0.6--> <activity android:name="cn.jpush.android.ui.PopWinActivity" android:theme="@style/MyDialogStyle" android:exported="false"> </activity> <!-- Required SDK核心功能--> <activity android:name="cn.jpush.android.ui.PushActivity" android:configChanges="orientation|keyboardHidden" android:theme="@android:style/Theme.NoTitleBar" android:exported="false"> <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 核心功能--> <!-- 可配置android:process参数将PushService放在其他进程中 --> <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> <!-- since 3.0.9 Required SDK 核心功能--> <provider android:authorities="你的应用包名.DataProvider" android:name="cn.jpush.android.service.DataProvider" android:exported="false" /> <!-- since 1.8.0 option 可选项。用于同一设备中不同应用的JPush服务相互拉起的功能。 --> <!-- 若不启用该功能可删除该组件,将不拉起其他应用也不能被其他应用拉起 --> <service android:name="cn.jpush.android.service.DaemonService" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="cn.jpush.android.intent.DaemonService" /> <category android:name="你的应用包名" /> </intent-filter> </service> <!-- since 3.1.0 Required SDK 核心功能--> <provider android:authorities="你的应用包名.DownloadProvider" android:name="cn.jpush.android.service.DownloadProvider" android:exported="true" /> <!-- Required SDK核心功能--> <receiver android:name="cn.jpush.android.service.PushReceiver" android:enabled="true" android:exported="false"> <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" android:exported="false"/> <!-- User defined. For test only MyReceiver为用户自定义的广播接收器--> <receiver android:name=".util.MyReceiver" android:exported="false" android:enabled="true"> <intent-filter> <action android:name="cn.jpush.android.intent.REGISTRATION" /> <!--Required 用户注册SDK的intent--> <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> <!--Jpush配置 end--> 

四、自定义一个广播接收器MyReceiver(此处直接使用官方Demo中的MyReceiver)

 /** * 自定义接收器 * * 如果不定义这个 Receiver,则: * 1) 默认用户会打开主界面 * 2) 接收不到自定义消息 */ public class MyReceiver extends BroadcastReceiver { private static final String TAG = "JIGUANG-Example"; @Override public void onReceive(Context context, Intent intent) { try { Bundle bundle = intent.getExtras(); Logger.d(TAG, "[MyReceiver] onReceive - " + intent.getAction() + ", extras: " + printBundle(bundle)); if (JPushInterface.ACTION_REGISTRATION_ID.equals(intent.getAction())) { String regId = bundle.getString(JPushInterface.EXTRA_REGISTRATION_ID); Logger.d(TAG, "[MyReceiver] 接收Registration Id : " + regId); //send the Registration Id to your server... } else if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) { Logger.d(TAG, "[MyReceiver] 接收到推送下来的自定义消息: " + bundle.getString(JPushInterface.EXTRA_MESSAGE)); processCustomMessage(context, bundle); } else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) { Logger.d(TAG, "[MyReceiver] 接收到推送下来的通知"); int notifactionId = bundle.getInt(JPushInterface.EXTRA_NOTIFICATION_ID); Logger.d(TAG, "[MyReceiver] 接收到推送下来的通知的ID: " + notifactionId); } else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) { Logger.d(TAG, "[MyReceiver] 用户点击打开了通知"); //打开自定义的Activity Intent i = new Intent(context, TestActivity.class); i.putExtras(bundle); //i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP ); context.startActivity(i); } else if (JPushInterface.ACTION_RICHPUSH_CALLBACK.equals(intent.getAction())) { Logger.d(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); Logger.w(TAG, "[MyReceiver]" + intent.getAction() +" connected state change to "+connected); } else { Logger.d(TAG, "[MyReceiver] Unhandled intent - " + intent.getAction()); } } catch (Exception e){ } } // 打印所有的 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 if (key.equals(JPushInterface.EXTRA_EXTRA)) { if (TextUtils.isEmpty(bundle.getString(JPushInterface.EXTRA_EXTRA))) { Logger.i(TAG, "This message has no Extra data"); continue; } try { JSONObject json = new JSONObject(bundle.getString(JPushInterface.EXTRA_EXTRA)); Iterator<String> it = json.keys(); while (it.hasNext()) { String myKey = it.next(); sb.append("\nkey:" + key + ", value: [" + myKey + " - " +json.optString(myKey) + "]"); } } catch (JSONException e) { Logger.e(TAG, "Get message extra JSON error!"); } } 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 (extraJson.length() > 0) { msgIntent.putExtra(MainActivity.KEY_EXTRAS, extras); } } catch (JSONException e) { } } LocalBroadcastManager.getInstance(context).sendBroadcast(msgIntent); } } } 

五、在Application中的onCreate()方法中初始化极光推送

 // 初始化 JPush JPushInterface.init(this); //发布时关闭日志 JPushInterface.setDebugMode(false); 

六、最后不要忘了添加混淆代码哦!

请在工程的混淆文件proguard-rules.pro中添加以下配置: -dontoptimize -dontpreverify -dontwarn cn.jpush.** -keep class cn.jpush.** { *; } -keep class * extends cn.jpush.android.helpers.JPushMessageReceiver { *; } -dontwarn cn.jiguang.** -keep class cn.jiguang.** { *; } 

七、通过极光官网控制台推送,测试推送结果。


img_64ac3d1cecc2c49488de7a59edc55268.png
image.png

《二》推送方式分析

使用场景分析:推送消息,无疑就是两种情形,一种是全部推送,这种方式简单,群发就行了,Portal与API都支持向指定的 appKey 群发消息。但是一般实际的业务需求,都不仅仅是群发,还需要针对某一个人或者某一群人进行推送。例如:给会员推送一些新的内容,此时就需要针对会员这一群特定的人,来进行推送。简单的说,就是需要把JPush的注册用户与开发者App用户绑定起来,以达到精准推送的目的。

1.RegistrationID方式实现点对点的精准推送(把绑定关系保存到开发者应用服务器中)

集成了 JPush SDK 的应用程序在第一次成功注册到 JPush 服务器时,JPush 服务器会以广播的形式发送RegistrationID到应用程序,给客户端返回一个唯一的该设备的标识 - RegistrationID。首次注册成功,自定义的MyReceiver中会收到一条广播。

如下图,会在MyReceiver中收到RegistrationID的值。只要极光推送第一次注册成功了,后期不会再发 RegistrationID 的广播了。RegistrationID 就会被保留在手机内,下次即使你是无网状态进入APP,你也可以获取到这个RegistrationID。有了这个标识,App 编程可以把这个 RegistrationID 保存到自己的应用服务器上,然后就可以根据 RegistrationID 来向设备推送消息或者通知。所以建议可以在你的Application和MyReceiver中都对这个RegistrationID进行赋值。然后根据项目的业务需要,将RegistrationID和用户标识的对应关系,上传自己的服务端。


img_7a420cf9fc1d85c7edc95bc1b72d8858.png
image.png
public class MyApplication extends Application{ public static String registrationID; @Override public void onCreate() { // 初始化 JPush JPushInterface.init(this); registrationID = JPushInterface.getRegistrationID(this); Log.d("TAG", "接收Registration Id : " + registrationID); } } 

【注意】:
如果 App 不卸载,是直接覆盖安装,Android, iOS 上 RegistrationID 的值都不会变化。
如果 App 是卸载之后再次安装:Android 上 RegistrationID 基本不会变;
iOS 上如果启用了 IDFA 变化可能性不大,如果未启用 IDFA 则每次安装 RegistrationID 都会变;
参考:极光推送的设备唯一性标识 RegistrationID

如果使用此种推送方式,你可能会遇到的问题:假设在一个设备中登录不同的账号,那此时上传给服务器的都是同一个RegistrationID,因为设备没有变化。那么可能出现:本来服务器是根据A用户找到它的RegistrationID进行推送,但是B用户也是在A用户登录的设备登录的,RegistrationID跟A的一样,这样就导致B用户收到了推送给A用户的推送内容。

需要谨记:

使用 RegistrationID 推送的关键于,App 开发者需要在开发 App 时,获取到这个 RegistrationID,保存到 App 业务服务器上去,并且与自己的用户标识对应起来。建议 App 开发者尽可能做这个保存动作。因为这是最精确地定位到设备的。(RegistrationID 的方式,相对而言比较麻烦一点,因为需要自己的App服务端维护RegistrationID和用户的对应关系。暂时我还没有使用这种方式,真正投入到线上的项目,只是测试环境下调试过。)
值得一读:推送人群的选择 – 推送方式-技术篇

2.别名与标签推送(把绑定关系保存到 JPush 服务器端)

别名推送也是一种实现点对点推送的方式,用于给某特定用户推送消息。
功能介绍:
①为安装了应用程序的用户,取个别名来标识。以后给该用户 Push 消息时,就可以用此别名来指定。
②每个用户只能指定一个别名。
③同一个应用程序内,对不同的用户,建议取不同的别名。这样,尽可能根据别名来唯一确定用户。
④系统不限定一个别名只能指定一个用户。如果一个别名被指定到了多个用户,当给指定这个别名发消息时,服务器端API会同时给这多个用户发送消息。

举例:在一个用户要登录的游戏中,可能设置别名为 userid。游戏运营时,发现该用户 3 天没有玩游戏了,则根据 userid 调用服务器端API发通知到客户端提醒用户。

别名设置:

需要和自己的服务端协商好别名的规则,例如下面的代码中,是将用户ID通过MD5加密,作为别名,设置保存到JPush服务器。当然你也可以使用其他的拼接规则,具体根据每个公司的项目需要设置即可,只要满足别名的命名限制就行: 命名长度限制为 40 字节。(判断长度需采用UTF-8编码)

深入理解各种推送方式可以参考:推送人群的选择 – 推送方式-技术篇

下面是一个JPush设置别名和标签的辅助类。

public class JPushHelper { private String TAG = "JPushHelper"; /** * 设置别名与标签 * * @param UUID */ private void setAlias(String UUID) { if (null != UUID) { //恢复接收推送 JPushInterface.resumePush(MyApplication.getInstance()); JPushInterface.setAliasAndTags(MyApplication.getInstance(), UUID, null, mAliasCallback); } } public void setAlias() { if (MyApplication.isLogin) { //必须登录 UserInfo userBean = ACT_Login.getLoginUser(); if (null != userBean) { //根据具体业务需求,可以再拼接上其他相关字段 //String alias = ""; //StringBuilder stringBuffer = new StringBuilder(); // stringBuffer.append(StringUtil.getString(userBean.user_id)); Log.e(TAG, stringBuffer.toString()); alias = MD5Util.string2MD5(userBean.user_id); //限制:alias 命名长度限制为 40 字节。(判断长度需采用UTF-8编码) Log.e(TAG, alias); //setAlias(""); setAlias(alias); } } } /** * 停止接收推送 */ public void removeAlias() { JPushInterface.clearAllNotifications(MyApplication.getInstance()); //setAlias("");//该句打开的话,如果退出登录后,用户就收不到离线的消息了。 JPushInterface.stopPush(MyApplication.getInstance()); } private final TagAliasCallback mAliasCallback = new TagAliasCallback() { @Override public void gotResult(int code, String alias, Set<String> tags) { String logs; switch (code) { case 0: // 建议这里往 SharePreference 里写一个成功设置的状态。成功设置一次后,以后不必再次设置了。 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 秒来调用 Handler 设置别名 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_ALIAS, alias), 1000 * 6); Log.e(TAG, logs + AppDateUtil.getTimeStamp(System.currentTimeMillis(), AppDateUtil.MM_DD_HH_MM_SS)); break; default: logs = "Failed with errorCode = " + code; Log.e(TAG, logs); } } }; private static final int MSG_SET_ALIAS = 1001; private final Handler mHandler = new Handler() { @Override public void handleMessage(android.os.Message msg) { super.handleMessage(msg); switch (msg.what) { case MSG_SET_ALIAS: Log.e(TAG, "Set alias in handler." + ((String) msg.obj)); // 调用 JPush 接口来设置别名。 JPushInterface.setAliasAndTags(MyApplication.getInstance(), (String) msg.obj, null, mAliasCallback); break; default: Log.e(TAG, "Unhandled msg - " + msg.what); } } }; // 校验Tag Alias 只能是数字,英文字母和中文 public static boolean isValidTagAndAlias(String s) { Pattern p = Pattern.compile("^[\u4E00-\u9FA50-9a-zA-Z_!@#$&*+=.|]+$"); Matcher m = p.matcher(s); return m.matches(); } } 
3.关于后端服务器设置别名还是前端设置别名:

说实在的,以前没有遇到过这个选择题,因为之前做的极光推送都是在我们客户端设置,不过最近调试JPush的时候后台说他设置别名,我突然就有点蒙了?不是一般都是前端设置吗???
于是有个疑问:如果是服务端设置别名,那服务端也没有经过客户端,那极光服务器咋通过别名来匹配用户进行点对点推送呀???我一下子有点想不通了。然后查了一下文档和相关博客,确实极光服务器也给后端服务提供设置别名的API,又问了一下后端开发,他是不是只是单单设置了别名,还是在设备ID上也有做了处理,因为没有映射关系,极光服务器也不可能找到对应的用户进行推送呀。果不其然,他是在RegistrationID上设置的别名,这下我就明白了。
不过大部分的情况,应该还是前端设置别名吧,毕竟那些登入登出的操作在前端控制会比较方便。

小提醒:

实际应用场景,客户端一般都需要在登录到App成功后,设置别名,恢复接收推送。退出登录后,停止接收推送。
恢复接收推送:

 JPushInterface.resumePush(MyApplication.getInstance()); 

停止接收推送:

JPushInterface.stopPush(MyApplication.getInstance()); 
4.App杀死后还想要收到推送

有的公司由于业务需求,可能会要求你,App杀死后还想要收到推送。如果知道JPush Android SDK 是作为 Android Service 长期运行在后台的,从而创建并保持长连接,保持永远在线的能力...的童鞋们,那么必须要清楚一点的是:如果APP真的被杀死了,是不可能收到推送的,如果杀死了还能收到,那说明可能是以下几种情况:
①应用自启动了
②要么是其他方式将App拉起来了。
③你压根就没有杀死App,Service还在运行着。(有的手机清理App,并没有完全杀死进程的)

关于这个问题可以看看这位小姐姐的总结:Android 关于App被杀死后,如何接收极光推送
阅读参考:
官网集成
极光推送Android端API
详解极光推送的 4 种消息形式 —— Android 篇
常见问题 - JPush 合集(持续更新)

原文链接:https://yq.aliyun.com/articles/638344
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章