Android 客户端网络不稳定,会致使App 有时候没法及时收到 Push 消息。android
不少开发者认为这是由于 JPush 推送不稳定、延迟,甚至有时候认为 JPush 后台推送系统出问题了。json
本文目的是从各个方面来分析 Android 网络致使的 JPush 不能正常工做的问题。api
首先,咱们须要知道,JPush SDK 并非集成到App 后就必然一直工做的。安全
其正常工做的必要条件是:JPush SDK 与 JPush Server 的网络保持着链接。请参考这篇文章来作进一步的理解:极光推送技术原理:移动无线网络长链接。服务器
而 Android 设备的网络的复杂性、不稳定性,是 Android 设备开发最复杂的地方之一。微信
另外,每款手机的网络能力也是千差万别的。国内不少杂牌手机在网络方面甚至会有严重的问题。大品牌厂商的手机则要好不少。网络
只要 JPush 的网络链接是正常的,则:app
自启动管理:默认状况下,手机开机后,只有系统默认的服务能够启动起来。除非在自启动管理界面,设置容许第三方程序自启动。异步
网络助手:能够手动禁止已安装的第三方程序访问2G/3G和WIFI的网络和设置之后新安装程序是否容许访问2G/3G和WIFI的网络。ide
这个现象代表,手机休眠时,JPush SDK “被迫”与服务器端的网络失去了链接。
JPush SDK 的工做原理是要确保在手机休眠时也能正常的工做,即休眠时也能够及时地收到Push消息。实际上JPush在大部分上手机上都能达到此效果。
这个“被迫”,是由 Android 设备的环境所致使的。涉及的缘由有以下几个方面:
上述的特殊机制会关闭网络。网络一旦链接上,JPush也会链接上服务器,从而Push消息就会收到。
JPush 会监听网络切换广播。当网络关闭时,把原来JPush链接关闭。当有新的网络时,建立JPush链接。
另外,RTC会定时发送心跳。若是以前的网络已经断了,则会从新链接。
应该说,当前的网络链接策略仍是相对简单的,这样作的目的是:省电、省流量。
不 好之处就是:网络没有切换时,由于当时网络过差,JPush链接会被中断。这种状况下,就只能等 RTC 心跳去触发链接。这也是有时候JPush 没法及时接收Push消息的缘由。根据网络条件的不一样,出现这个状况的几率也会不一样。但据咱们本身的测试,90% 的时候是能够及时地收到Push消息的。
JPush 目前在网络策略方面没有像微信这种聊天工具作得积极。若是这样作到,电量和流量的消耗必然会成倍地增长。
若是集成以后就彻底收不到Push消息,则颇有多是某个地方配置错误。请根据文档仔细检查:Android SDK 集成指南,iOS SDK 集成指南,或者根据参考教程:Android SDK 调试指南,iOS SDK 调试指南。
推送消息时,要指定推送的对象:所有,某一我的,或者某一群人。
所有很好办,针对某应用“群发”就行了。Portal与API都支持向指定的 appKey 群发消息。
要指定向某一个特定的人,或者某一群特定的人,则相对复杂。由于对于 JPush 来讲,某一我的就是一个注册ID,这个注册ID与开发者App没有任何关系,或者说对开发者App是没有意义的。
若是要对开发者App有意义的某个特定的用户推送消息,则须要:把 JPush 注册用户与开发者App 用户绑定起来。
这个绑定有两个基本思路:
前者,就是这里要说到的:别名与标签的功能。这个机制简单易用,适用于大多数开发者。
后者,则是 JPush 提供的另一套 RegistrationID 机制。这套机制开发者须要有应用服务器来维护绑定关系,不适用于普通开发者。Android SDK r1.6.0 版本开始支持。
别名与标签的机制,其工做方式是:
SDK 支持的 setAliasAndTags 请参考相应的文档:别名与标签 API
使用过程当中有几个点作特别说明:
App 调用 SDK setAliasAndTags API 时,r1.5.0 版本提供了 Callback 来返回设置状态。若是返回 6002 (超时)则建议重试
Portal 上推送或者 API 调用向别名或者标签推送时,可能会报错:不存在推送目标用户。该报错代表,JPush Server 上尚未针对你所推送的别名或者标签的用户绑定关系,因此没有推送目标。这时请开发者检查确认,开发者App是否正确地调用了 setAliasAndTags API,以及调用时是否网络很差,JPush SDK 暂时未能保存成功。
用于给某特定用户推送消息。
所谓别名,能够近似地被认为,是用户账号里的昵称。
用于给某一群人推送消息。
标签相似于博客里为文章打上 tag ,即为某资源分类。
JPush 提供的设置标签的 API 是在客户端的。开发者如何作到在本身的服务器端动态去设置分组呢? 好比一个企业OA系统,常常须要去变动部门人员分组。如下是大概的思路:
因为网络链接不稳定的缘由,有必定的几率 JPush SDK 设置别名与标签会失败。
App 开发者合理地处理设置失败,则偶尔失败对应用的正常使用 JPush 影响是有限的。
如下以 Android SDK 做为示例。
基本思路:
遇到 6002 超时,则稍延迟重试。
// 这是来自 JPush Example 的设置别名的 Activity 里的代码。通常 App 的设置的调用入口,在任何方便的地方调用均可以。 private void setAlias() { EditText aliasEdit = (EditText) findViewById(R.id.et_alias); String alias = aliasEdit.getText().toString().trim(); if (TextUtils.isEmpty(alias)) { Toast.makeText(PushSetActivity.this,R.string.error_alias_empty, Toast.LENGTH_SHORT).show(); return; } if (!ExampleUtil.isValidTagAndAlias(alias)) { Toast.makeText(PushSetActivity.this,R.string.error_tag_gs_empty, Toast.LENGTH_SHORT).show(); return; } // 调用 Handler 来异步设置别名 mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_ALIAS, alias)); } 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); // 建议这里往 SharePreference 里写一个成功设置的状态。成功设置一次后,之后没必要再次设置了。 break; case 6002: logs = "Failed to set alias and tags due to timeout. Try again after 60s."; Log.i(TAG, logs); // 延迟 60 秒来调用 Handler 设置别名 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_ALIAS, alias), 1000 * 60); break; default: logs = "Failed with errorCode = " + code; Log.e(TAG, logs); } ExampleUtil.showToast(logs, getApplicationContext()); } }; 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.d(TAG, "Set alias in handler."); // 调用 JPush 接口来设置别名。 JPushInterface.setAliasAndTags(getApplicationContext(), (String) msg.obj, null, mAliasCallback); break; default: Log.i(TAG, "Unhandled msg - " + msg.what); } } };
JPush 通知推送到客户端时,默认使用手机的默认设置来显示通知栏,包括铃声、震动等效果。
若是开发者想要达到以下的效果,则须要使用“自定义通知栏样式”功能:
通知栏样式在服务器端向下推送时,只体现为一个编号(数字)。
推送通知的样式编号,应该是在客户端作了自定义通知栏样式设置的。
若是通知上的样式编号,在客户端检查不存在,则使用默认的通知栏样式。
开发者不自定义通知栏样式时,则此编号默认为 0。
开发者自定义的通知栏样式编号应大于 0,小于 1000。
在 Portal 上发送通知时,最下边的“可选”部分展开,开发者可指定当前要推送的通知的样式编号。以下图所示:
自定义的通知栏样式,是在客户端进行的。请参考 通知栏样式定制API 来看所支持的功能。
此 API 改变默认的编号为 0 的通知栏样式。
此 API 为开发者指定的编号,设置一个自定义的 PushNotificationBuilder(通知样式构建器)。
定制声音、震动、闪灯等 Notification 样式。
BasicPushNotificationBuilder builder = new BasicPushNotificationBuilder(MainActivity.this); builder.statusBarDrawable = R.drawable.jpush_notification_icon; builder.notificationFlags = Notification.FLAG_AUTO_CANCEL; //设置为自动消失 builder.notificationDefaults = Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS; // 设置为铃声与震动都要 JPushInterface.setPushNotificationBuilder(1, builder);
基于基础的 PushNotificationBuilder,可进一步地定制 Notification 的 Layout。
这里做为 example 的 customer_notitfication_layout 在咱们的 example 项目的 /res/layout/ 下能够找到。你彻底能够用本身的 layout。
CustomPushNotificationBuilder builder = new CustomPushNotificationBuilder(MainActivity.this, R.layout.customer_notitfication_layout, R.id.icon, R.id.title, R.id.text); // 指定定制的 Notification Layout builder.statusBarDrawable = R.drawable.your_notification_icon; // 指定最顶层状态栏小图标 builder.layoutIconDrawable = R.drawable.your_2_notification_icon; // 指定下拉状态栏时显示的通知图标 JPushInterface.setPushNotificationBuilder(2, builder);
以上提供的自定义通知栏样式的功能是有限的。好比:Android SDK 4.0 之后的 Notification 支持指定 Style ,而这种复杂的通知样式定义 JPush SDK 还未有支持。
或者你想要自定义的复杂的通知样式,但不肯意使用上述高级的自定义通知栏定制功能。
建议不要使用 JPush 提供的通知功能,而使用自定义消息功能。
即:推送自定义消息到客户端后,App取到自定义消息所有内容,而后App本身来写代码作通知的展现。请参考文档:通知 vs. 自定义消息。
极光推送包含有通知与自定义消息两种类型的推送。本文描述他们的区别,以及建议的应用场景。
或者说 Push Notification,即指在手机的通知栏(状态栏)上会显示的一条通知信息。这是 Android / iOS 的基本功能。
一条通知,简单的填写纯文本的通知内容便可。
通知主要用于提示用户的目的。应用加上通知功能,有利于提升应用的活跃度。
是极光推送本身的概念。
自定义消息不是通知,因此不会被SDK展现到通知栏上。其内容彻底由开发者本身定义。
自定义消息主要用于应用的内部业务逻辑。一条自定义消息推送过来,有可能没有任何界面显示。
本质上:
自定义消息是原始的消息,JPush SDK 不作处理。而通知,则 JPush SDK 会作通知展现处理,其目的是为了减轻开发人员的工做量。
因此,若是通知功能不太符合您的需求,你均可以使用自定义消息来实现(客户端展示App本身来作)。
简单场景下的通知,用户能够不写一行代码,而彻底由 SDK 来负责默认的效果展现,以及默认用户点击时打开应用的主界面。
JPush Android SDK 提供了 API 让开发者来定制通知栏的效果,请参考:自定义通知栏样式教程;也提供了 接收推送消息Receiver 让你来定制在收到通知时与用户点击通知时的不一样行为。
SDK 不会把自定义消息展现到通知栏。
因此调试时,须要到日志里才能够看到服务器端推送的自定义消息。
自定义消息必定要由开发者写 接收推送消息Receiver 来处理收到的消息。
注意:
当自定义消息内容msg_content为空时,SDK不会对消息进行广播,使得app没法接收到推送的消息,所以建议在使用自定义消息推送时添
加内容
请参考如下示例代码。
public class MyReceiver extends BroadcastReceiver { private static final String TAG = "MyReceiver"; private NotificationManager nm; @Override public void onReceive(Context context, Intent intent) { if (null == nm) { nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } Bundle bundle = intent.getExtras(); Logger.d(TAG, "onReceive - " + intent.getAction() + ", extras: " + AndroidUtil.printBundle(bundle)); if (JPushInterface.ACTION_REGISTRATION_ID.equals(intent.getAction())) { Logger.d(TAG, "JPush用户注册成功"); } else if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) { Logger.d(TAG, "接受到推送下来的自定义消息"); } else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) { Logger.d(TAG, "接受到推送下来的通知"); receivingNotification(context,bundle); } else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) { Logger.d(TAG, "用户点击打开了通知"); openNotification(context,bundle); } else { Logger.d(TAG, "Unhandled intent - " + intent.getAction()); } } private void receivingNotification(Context context, Bundle bundle){ String title = bundle.getString(JPushInterface.EXTRA_NOTIFICATION_TITLE); Logger.d(TAG, " title : " + title); String message = bundle.getString(JPushInterface.EXTRA_ALERT); Logger.d(TAG, "message : " + message); String extras = bundle.getString(JPushInterface.EXTRA_EXTRA); Logger.d(TAG, "extras : " + extras); } private void openNotification(Context context, Bundle bundle){ String extras = bundle.getString(JPushInterface.EXTRA_EXTRA); String myValue = ""; try { JSONObject extrasJson = new JSONObject(extras); myValue = extrasJson.optString("myKey"); } catch (Exception e) { Logger.w(TAG, "Unexpected: extras is not a valid json", e); return; } if (TYPE_THIS.equals(myValue)) { Intent mIntent = new Intent(context, ThisActivity.class); mIntent.putExtras(bundle); mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(mIntent); } else if (TYPE_ANOTHER.equals(myValue)){ Intent mIntent = new Intent(context, AnotherActivity.class); mIntent.putExtras(bundle); mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(mIntent); } } }
使用自定义消息,在客户端App里必定要写代码,去接受 JPush SDK 的广播,从而取得推送下来的消息内容。具体请参考文档:接收推送消息Receiver。
如下代码来自于推聊。
public class TalkReceiver extends BroadcastReceiver { private static final String TAG = "TalkReceiver"; private NotificationManager nm; @Override public void onReceive(Context context, Intent intent) { if (null == nm) { nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } Bundle bundle = intent.getExtras(); Logger.d(TAG, "onReceive - " + intent.getAction() + ", extras: " + AndroidUtil.printBundle(bundle)); if (JPushInterface.ACTION_REGISTRATION_ID.equals(intent.getAction())) { Logger.d(TAG, "JPush用户注册成功"); } else if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) { Logger.d(TAG, "接受到推送下来的自定义消息"); // Push Talk messages are push down by custom message format processCustomMessage(context, bundle); } else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) { Logger.d(TAG, "接受到推送下来的通知"); receivingNotification(context,bundle); } else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) { Logger.d(TAG, "用户点击打开了通知"); openNotification(context,bundle); } else { Logger.d(TAG, "Unhandled intent - " + intent.getAction()); } } private void processCustomMessage(Context context, Bundle bundle) { String title = bundle.getString(JPushInterface.EXTRA_TITLE); String message = bundle.getString(JPushInterface.EXTRA_MESSAGE); if (StringUtils.isEmpty(title)) { Logger.w(TAG, "Unexpected: empty title (friend). Give up"); return; } boolean needIncreaseUnread = true; if (title.equalsIgnoreCase(Config.myName)) { Logger.d(TAG, "Message from myself. Give up"); needIncreaseUnread = false; if (!Config.IS_TEST_MODE) { return; } } String channel = null; String extras = bundle.getString(JPushInterface.EXTRA_EXTRA); try { JSONObject extrasJson = new JSONObject(extras); channel = extrasJson.optString(Constants.KEY_CHANNEL); } catch (Exception e) { Logger.w(TAG, "Unexpected: extras is not a valid json", e); } // Send message to UI (Webview) only when UI is up if (!Config.isBackground) { Intent msgIntent = new Intent(MainActivity.MESSAGE_RECEIVED_ACTION); msgIntent.putExtra(Constants.KEY_MESSAGE, message); msgIntent.putExtra(Constants.KEY_TITLE, title); if (null != channel) { msgIntent.putExtra(Constants.KEY_CHANNEL, channel); } JSONObject all = new JSONObject(); try { all.put(Constants.KEY_TITLE, title); all.put(Constants.KEY_MESSAGE, message); all.put(Constants.KEY_EXTRAS, new JSONObject(extras)); } catch (JSONException e) { } msgIntent.putExtra("all", all.toString()); context.sendBroadcast(msgIntent); } String chatting = title; if (!StringUtils.isEmpty(channel)) { chatting = channel; } String currentChatting = MyPreferenceManager.getString(Constants.PREF_CURRENT_CHATTING, null); if (chatting.equalsIgnoreCase(currentChatting)) { Logger.d(TAG, "Is now chatting with - " + chatting + ". Dont show notificaiton."); needIncreaseUnread = false; if (!Config.IS_TEST_MODE) { return; } } if (needIncreaseUnread) { unreadMessage(title, channel); } NotificationHelper.showMessageNotification(context, nm, title, message, channel); } // When received message, increase unread number for Recent Chat private void unreadMessage(final String friend, final String channel) { new Thread() { public void run() { String chattingFriend = null; if (StringUtils.isEmpty(channel)) { chattingFriend = friend; } Map<String, String> params = new HashMap<String, String>(); params.put("udid", Config.udid); params.put("friend", chattingFriend); params.put("channel_name", channel); try { HttpHelper.post(Constants.PATH_UNREAD, params); } catch (Exception e) { Logger.e(TAG, "Call pushtalk api to report unread error", e); } } }.start(); } }