前段时间针对集成极光推送写了篇文章( Android集成极光推送和踩过的坑),后来提测以后发现了各种问题。一直没时间总结一下,趁着周末有点时间,赶紧把这段时间里针对Push这块儿遇到的问题梳理一下。并且对上篇文章 《Android集成极光推送和踩过的坑》中一些错误进行更正,因需求变更出现的一些连带的问题的处理方法做一下总结。
上篇文章中我用的以下方法判断的前后台,遍历正在运行的所有的进程,看list里第一个正在运行的进程是否是我们自己的进程,是就return true,不是就return false。进而判断我们的进程是否处于前台。
/** * 判断进程是否在后台 * * @param context * @return */ public static boolean isBackground(Context context) { ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List
appProcesses = activityManager.getRunningAppProcesses(); for (RunningAppProcessInfo appProcess : appProcesses) { if (appProcess.processName.equals(context.getPackageName())) { LogUtil.i("ym", appProcess.processName + "前台"); return false; } else { LogUtil.i("ym", appProcess.processName + "后台"); return true; } } return false; }
提测以后发现在Android7.0的系统上会出现前后台判断误差。Android7.0是多任务处理机制,home键以后,会出现前台进程会有多个的情况,只拿第一个去判断我们的进程是否在前台变的不可靠。后台我们的需求变更了,要求“打开应用”,如果进处于后台,之前是什么页面就是什么页面,而不是每次都打开"首页”。那么问题就来了,我怎么知道按home键的时候的activity是哪个activity。期初以为通过intent标记就可以做到,尝试以后发现不起作用。后来去请教了大神,大神给我提供了一种方法获取当前栈顶的activity,废话不多说,直接上代码。
/** * Created by ym on 2017/5/27. * 自定义极光推送的广播接受者(v1.3.0新增) * 2017.6.30 v2.0.0修改:删v1.3.0前后台判断统一处理,新增议价消息跳转刷新逻辑 ym */ public class MyJPushReceiver extends BroadcastReceiver { private static final String TAG = "JPush"; @Override public void onReceive(Context context, Intent intent) { Bundle bundle = intent.getExtras(); LogUtil.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); LogUtil.e(TAG, "[MyReceiver] 接收Registration Id : " + regId); } else if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) { LogUtil.e(TAG, "[MyReceiver] 接收到推送下来的自定义消息: " + bundle.getString(JPushInterface.EXTRA_MESSAGE)); // processCustomMessage(context, bundle); } else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) { LogUtil.e(TAG, "[MyReceiver] 接收到推送下来的通知"); int notificationId = bundle.getInt(JPushInterface.EXTRA_NOTIFICATION_ID); LogUtil.e(TAG, "[MyReceiver] 接收到推送下来的通知的ID: " + notificationId); } else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) { LogUtil.e(TAG, "[MyReceiver] 用户点击打开了通知"); SharePreferenceUtil share = new SharePreferenceUtil(context); //解析json String string = bundle.getString(JPushInterface.EXTRA_EXTRA);//json串 LogUtil.e(TAG, "=====###########" + string); try { JSONObject jsonObject = new JSONObject(string); String type = jsonObject.getString("type"); LogUtil.e(TAG, "type:" + type); Activity ac = com.carspass.common.util.ActivityManager.getAppManager().currentActivity(); switch (type) { case "1"://打开应用 Intent i = new Intent(); if (ac != null) {//前后/后台---之前的界面 i.setComponent(ac.getComponentName()); } else {//杀死进程--重启 i.setClass(context, ACT_Main.class); } i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP); context.startActivity(i); break; case "2"://打开创建订单页 String sourse_id = jsonObject.getString("sourse_id"); if (!TextUtils.isEmpty(sourse_id)) { Intent intentOrder = new Intent(context, ACT_PlaceOrder.class); intentOrder.putExtra("id", Integer.parseInt(sourse_id)); intentOrder.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (ac != null) {//前台/后台---之前的界面+创建订单页 context.startActivity(intentOrder); } else {//杀死进程--重启-首页+创建订单页 Intent intentMain = new Intent(context, ACT_Main.class); intentMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); Intent[] intents = {intentMain, intentOrder}; context.startActivities(intents); } } break; case "3"://打开品牌 String brand_id = jsonObject.getString("brand_id"); String bra_name = jsonObject.getString("bra_name"); if (!TextUtils.isEmpty(brand_id)) { Intent intentBrand = new Intent(context, ACT_BrandCarList.class); intentBrand.putExtra("id", brand_id); intentBrand.putExtra("title", bra_name); intentBrand.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (ac != null) {//前台/后台---之前的界面+品牌页 context.startActivity(intentBrand); } else {//杀死进程--重启-首页+品牌页 Intent intentMain = new Intent(context, ACT_Main.class); intentMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); Intent[] intents = {intentMain, intentBrand}; context.startActivities(intents); } } break; case "4"://打开指定页面 String http_url = jsonObject.getString("http_url"); if (!TextUtils.isEmpty(http_url)) { Intent intentWeb = new Intent(context, ACT_Web.class); intentWeb.putExtra("title", ""); intentWeb.putExtra("url", http_url); intentWeb.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (ac != null) {//前后/后台---之前的界面+web页 context.startActivity(intentWeb); } else {//杀死进程--重启-首页+web页 Intent intentMain = new Intent(context, ACT_Main.class); intentMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); Intent[] intents = {intentMain, intentWeb}; context.startActivities(intents); } } break; case "5"://打开议价详情 String bargainid = jsonObject.getString("bargainid"); if (!TextUtils.isEmpty(bargainid)) { if (TextUtils.equals(bargainid, "0")) {//取消的议价--打开应用 Intent cancelBargainInetent = new Intent(); if (ac != null) {//前台/后台---之前的界面(议价走生命周期自己刷新) cancelBargainInetent.setComponent(ac.getComponentName()); } else {//杀死进程--重启 cancelBargainInetent.setClass(context, ACT_Main.class); } cancelBargainInetent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP); context.startActivity(cancelBargainInetent); } else {//已反馈的议价 if (ac != null) {//未杀死进程---之前的界面 String simpleName = ac.getClass().getSimpleName(); if (TextUtils.equals(simpleName, "ACT_BargainingDetail")) {//之前页面是议价详情的 share.putString("pushBargainId", bargainid); if (TextUtils.equals(Contants.ACT_BargainingDetailFlag, "foreground")) { //议价详情在前台--直接刷新 ((ACT_BargainingDetail) ac).pushRefresh(); } else { //议价详情在后台--打开之前页面走生命周期刷新 Intent intent1 = new Intent(); intent1.setComponent(ac.getComponentName()); intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP); context.startActivity(intent1); } } else {//之前不是议价详情页的--之前的+新的议价详情页 Intent bargainIntent = new Intent(context, ACT_BargainingDetail.class); bargainIntent.putExtra("id", bargainid); bargainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(bargainIntent); } } else {//杀死进程--重启-首页+议价页 Intent bargainIntent = new Intent(context, ACT_BargainingDetail.class); bargainIntent.putExtra("id", bargainid); bargainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Intent intentMain = new Intent(context, ACT_Main.class); intentMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); Intent[] intents = {intentMain, bargainIntent}; context.startActivities(intents); } } } break; } } catch (JSONException e) { e.printStackTrace(); } } else if (JPushInterface.ACTION_RICHPUSH_CALLBACK.equals(intent.getAction())) { LogUtil.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); LogUtil.e(TAG, "[MyReceiver]" + intent.getAction() + " connected state change to " + connected); } else { LogUtil.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 if (key.equals(JPushInterface.EXTRA_EXTRA)) { if (TextUtils.isEmpty(bundle.getString(JPushInterface.EXTRA_EXTRA))) { LogUtil.e(TAG, "This message has no Extra data"); continue; } try { JSONObject json = new JSONObject(bundle.getString(JPushInterface.EXTRA_EXTRA)); Iterator
it = json.keys(); while (it.hasNext()) { String myKey = it.next().toString(); sb.append("\nkey:" + key + ", value: [" + myKey + " - " + json.optString(myKey) + "]"); } } catch (JSONException e) { LogUtil.e(TAG, "Get message extra JSON error!"); } } else { sb.append("\nkey:" + key + ", value:" + bundle.getString(key)); } } return sb.toString(); } }
我们项目里自己维护了一个actvity的Manager去管理activity。可以通过
获取到当前处于栈顶的activity。如果这个activity是null,说明进程已经被杀死,如果不等于null,说明进程是在前台或者后台。Activity ac = com.carspass.common.util.ActivityManager.getAppManager().currentActivity();
/** * 应用程序Activity管理类:用于Activity管理和应用程序退出 */ public class ActivityManager { private Stack
activityStack; private static ActivityManager instance; public SharePreferenceUtil share; private ActivityManager() { } /** * 单一实例 */ public static ActivityManager getAppManager() { if (instance == null) { instance = new ActivityManager(); } return instance; } /** * 添加Activity到堆栈 */ public void addActivity(Activity activity) { if (activityStack == null) { activityStack = new Stack (); } activityStack.add(activity); } /** * 获取当前Activity(堆栈中最后一个压入的) */ public Activity currentActivity() { //2017.6.16 通知打开应用--之前的界面,空指针容错处理 ym start // Activity activity = activityStack.lastElement(); // return activity; if (activityStack != null) { return activityStack.lastElement(); } return null; //2017.6.16 通知打开应用--之前的界面,空指针容错处理 ym end } /** * 结束当前Activity(堆栈中最后一个压入的) */ public void finishActivity() { Activity activity = activityStack.lastElement(); finishActivity(activity); } /** * 结束指定的Activity */ public void finishActivity(Activity activity) { if (activity != null) { activityStack.remove(activity); activity.finish(); activity = null; } } /** * 结束指定类名的Activity */ public void finishActivity(Class> cls) { for (Activity activity : activityStack) { if (activity.getClass().equals(cls)) { finishActivity(activity); } } } /** * 除了指定类名的Activity,其他的都结束 * 这个方法会报异常,不能再增强for循环中remove元素 * @param cls */ /* public void finishActivityButThis(Class> cls) { for (Activity activity : activityStack) { if (!activity.getClass().equals(cls)) { finishActivity(activity); } } }*/ /** * 除了第一个启动的Activity,其他的都结束 */ public void finishActivityButMain() { for (int i = activityStack.size() - 1; i > 0; i--) { activityStack.get(i).finish(); activityStack.remove(i); } } /** * 结束所有Activity */ public void finishAllActivity() { for (int i = 0, size = activityStack.size(); i < size; i++) { if (null != activityStack.get(i)) { Activity activity = activityStack.get(i); if (!activity.isFinishing()) { activity.finish(); } } } activityStack.removeAllElements(); activityStack.clear(); } /** * 退出应用程序 */ public void AppExit(Context context) { try { finishAllActivity(); /* * Intent intent = new Intent(context, ACT_Main.class); * PendingIntent restartIntent = PendingIntent.getActivity( context, * 0, intent, Intent.FLAG_ACTIVITY_NEW_TASK); //退出程序 AlarmManager * mgr = * (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); * mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, * restartIntent); // 1秒钟后重启应用 */ // 杀死该应用进程 android.os.Process.killProcess(android.os.Process.myPid()); System.exit(0); } catch (Exception e) { } } }
根据业务场景,我并不需要知道应用是否在前台还是后台,只要我合理地利用Intent的flag标记就可以。比如如果栈顶有就直接复用当前activty,没有在去new一个新栈去放新的activity。这样话,我就无需去判断应用是否在前后台,在前台,如果要打开一个新的页面,就开一个新的task去存放新的actvity,如果只是纯打开应用,本身就是开着呢,就没啥反应。在后台,就把之前的页面打开,或是+新的页面,或是纯打开之前的页面。i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP);
所以我只需要通过获取栈顶的activity是否是null来判断进程是否被杀死就好。无需知道是否在前后台。
在这个项目业务场景中,我用的前后台的地方是第5种情况,产品需求要求议价详情页不添加新层,直接本页刷新。业务场景是这样的:用户在议价详情页(id是A),这时来了个id是B的议价详情的Push,用户点击Push直接本页刷新展示B的议价详情信息。处理方式:需要判断议价详情页是否在前台。在前台,直接调刷新数据的方法。在后台,走生命周期的onStart()方法中的刷新数据。
判断某个activity在前后台的方法:
在application中提供了avtivity的生命周期的回调方法。
议价详情页刷新的方法:/** * 2017.6.19 * 判断应用在前后台 ym * 2017.6.30 新增判断议价详情页前后台标记 ym */ public void isRunningForeground() { if (Build.VERSION.SDK_INT >= 14) { registerActivityLifecycleCallbacks( new Application.ActivityLifecycleCallbacks() { @Override public void onActivityCreated( Activity activity, Bundle bundle) { } @Override public void onActivityStarted(Activity activity) { } @Override public void onActivityResumed(Activity activity) { // LogUtil.i("ym", "判断前后台界面:" + activity.getClass().getSimpleName() + "前台"); if (TextUtils.equals(activity.getClass().getSimpleName(), "ACT_BargainingDetail")) { Contants.ACT_BargainingDetailFlag = "foreground"; } } @Override public void onActivityPaused(Activity activity) { } @Override public void onActivityStopped(Activity activity) { // LogUtil.i("ym", "判断前后台界面:" + activity.getClass().getSimpleName() + "后台"); if (TextUtils.equals(activity.getClass().getSimpleName(), "ACT_BargainingDetail")) { Contants.ACT_BargainingDetailFlag = "background"; } } @Override public void onActivitySaveInstanceState( Activity activity, Bundle bundle) { } @Override public void onActivityDestroyed(Activity activity) { } }); } }
@Override protected void onStart() { super.onStart(); //2017.6.30 议价详情页刷新 v2.0.0新增 ym start if (getIntent() != null && getIntent().getExtras() != null) { // 获取议价id id = getIntent().getExtras().getString("id"); String pushBargainId = share.getString("pushBargainId"); if (!TextUtils.isEmpty(pushBargainId)) { id = pushBargainId; //清空 share.putString("pushBargainId", ""); } // 2016.6.6 未读议价消息进入议价详情标识 ym start messageRead = getIntent().getExtras().getString("messageRead"); // 2016.6.6 未读议价消息进入议价详情标识 ym end getBargainingDetail(); } //2017.6.30 议价详情页刷新 v2.0.0新增 ym end }
议价详情页不走生命周期刷新的方法:/** * 议价推送刷新 v2.0.0 新增 ym */ public void pushRefresh() { String pushBargainId = share.getString("pushBargainId"); if (!TextUtils.isEmpty(pushBargainId)) { id = pushBargainId; //清空 share.putString("pushBargainId", ""); } getBargainingDetail(); } @Overrid
我把push中议价详情的id存在sp中,走生命周期的时候onStart中通过判断sp中这个push的id是否为空,来决定要不要把之前A的id换成Push的B的id。正常的情况,我们只是A刷新A的,只有是点击Push的才去换成B的。为啥这儿要知道议价详情页是否在前台,因为在前台的话,并不走onStart(),我们怎么刷新,通过activity调刷新数据的方法去直接刷新。
我认为通过下面这句给设置极光别名,不管成功不成功,开个头。这句是同步的。/** * 设置AliasAndTag,设置多组tag,如果不需要设置tag的化,直接将此参数设为null;(这个方法设置别名,tag传null没有问题) * 一般在程序登录成功,注册成功等地方调用。别名一般是用户的唯一标识,如userId等 * * @param alias * @param tags */ public void setAliasAndTags(final String alias, Set
tags) { if (TextUtils.isEmpty(alias)) { //Toast.makeText(context, "别名为空", Toast.LENGTH_SHORT).show(); return; } // 调用 Handler 来异步设置别名 AliasAndTagsInfo aliasAndTagsInfo = new AliasAndTagsInfo(); aliasAndTagsInfo.setAlias(alias); aliasAndTagsInfo.setTag(tags); mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_ALIAS, aliasAndTagsInfo)); } private final TagAliasCallback mAliasCallback = new TagAliasCallback() { @Override public void gotResult(int code, String alias, Set tags) { String logs; switch (code) { case 0: logs = "Set tag and alias success"; Log.d(TAG, logs); // 建议这里往 SharePreference 里写一个成功设置的状态。成功设置一次后,以后不必再次设置了。 saveAlias(alias); break; case 6002: logs = "Failed to set alias and tags due to timeout. Try again after 60s."; Log.d(TAG, logs); // 延迟 60 秒来调用 Handler 设置别名 mHandler.sendMessageDelayed( mHandler.obtainMessage(MSG_SET_ALIAS, alias), 1000 * 60); break; default: logs = "Failed with errorCode = " + code; Log.d(TAG, logs); } } };
我通过debug,断网操作,发现断网后,极光接口内部会自动判断是否重新联网了,联网成功便把刚才没发给极光服务器的设置别名发出去,然后得到成功的0的回调。假如设置失败,会走6002,隔60s以后重新发送。mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_ALIAS, aliasAndTagsInfo));
所以,我认为并不会出现卡顿的情况,不管设置别名成功还是失败,设置别名后面的代码仍会继续执行。而且这种连接第三方sdk服务器的接口都是异步执行的吧。还是不能理解大神的意思。
我们有极光的版本是V1.3.0,上线以后发现,V1.2.0及以下版本如果使用应用宝等增量更新包的话,之前的登录的token本地不会被清除。导致V1.2.0及以下版本如果用户不主动退出重新登录,就不能给极光设置上别名。因为我设置别名的操作时在登录成功的回调里设置的。我们业务逻辑做了免登录处理,直接进首页,并不会走登录界面。这种情况只出现在增量更新版本的话,如果是把之前的版本卸载,重新安装新的版本,这样本地的token会被清掉,那么用户进入app后必须重新登录,所以就不会有设置不上别名的情况了。那么来说说我怎么兼容低版本的吧。
还记得我们在设置了别名成功后做了个什么操作吗?
我们把别名保存到了本地。case 0: logs = "Set tag and alias success"; Log.d(TAG, logs); // 建议这里往 SharePreference 里写一个成功设置的状态。成功设置一次后,以后不必再次设置了。 saveAlias(alias); break;
那么就好办了,只有1.3.0以上的版本的才会有这个保存的别名,V1.2.0以下的版本没有集成极光,sp中没有这个别名。
免登录以后用户会直接进首页,首页在首页的onCreate()方法中,我去判断这个别名存不存在,不存在就给它再设置一遍别名。1.3.0以上的版本在登录的时候设置上了就不会重复设置。
//2017.6.28 兼容1.2.0版本,给极光设置别名。防止重复设置别名,v1.2.0的sp中没有存极光别名 ym start if (TextUtils.isEmpty(share.getString("JpushConfig"))) { setPushAlias(); } //2017.6.28 兼容1.2.0版本,给极光设置别名。防止重复设置别名,v1.2.0的sp中没有存极光别名 ym end
这样就成功地解决了兼容低版本因增量更新包而不走登录设置不上别名的问题。/** * 兼容v1.2.0,给极光设置别名 */ private void setPushAlias() { //2017.6.28 给极光设置别名 兼容低版本用户 ym start String access_token = share.getString("access_token"); String alias = MD5Util.md5(access_token + Contants.Alias); LogUtil.e("ym", "极光别名:" + alias); JPushManager jPushManager = new JPushManager(act); jPushManager.setAliasAndTags(alias, null); //2017.6.28 给极光设置别名 兼容低版本用户 ym end }
极光社区官方说明
推送成功率问题
「如何理解送达成功率」15
1.「极光目标数和成功数的比例为什么很低?」
android、iOS的送达根本就不适合用于比较。 android目标是进一个月内的在线用户(即一个月内有建立过jpush长连接的用户)里面根据你的audience来匹配用户,而推送是否能送达和你的应用用户的在线活跃是成正比的。 如果你想了解一般的第三方android应用收到哪些系统限制而导致你的应用存活低下,请看:http://docs.jpush.io/guideline/faq/#android 的[第三方系统收不到推送的消息] 而iOS的成功数是指成功推送到apns服务器的数量,iOS的点击本来就很低,大多人看到推送后可能是直接进入应用查看并直接清除通知中心里面的通知,也可能看了一下通知后感觉没有兴趣,直接清通知栏。iOS的点击低不等于推送的到设备数少。
2.「关于极光推送安卓版本送达率问题?」
天在线 100,当前在线 20,看应用,可能是正常的。 如果持续天在线 100,一条广播推送(群发)一天的时间内收到推送应该也大概 100 。(假设这条推送默认的推送时长为 1 天,你未做改变) 未收到的原因,各种都有可能,包括被卸载。极光要做的事情是:至少你上线了(推送时长范围内),就及时地把消息推送下去。
3. 「极光推送,iOS的送达率几乎是100%,安卓只达到五分之一。」iOS的成功指的是成功送达到iOS的apns服务器; Android的送达是成功送达到用户设备数(包含 在线送达 + 离线送达) Android客户端是长连接机制,和极光服务器建立上连接的时候才能及时收到推送;所以判断送达,请根据在线数判断 如果和极光服务器的连接断开,那称之为客户端离线,客户端离线的可能:断网、进程不在、关机、主动调用了stopPush服务等; 对于这些离线的客户,极光有离线消息机制去保证,免费用户,默认为每个客户端保留最近5条,默认一天,最长10天;vip可以根据需要调节保留条数和天数; 只要用户在离线保存时间范围内上线,那就能收到之前的推送。
五、离线消息保存时间
产品问我,要是不开通vip,能不能把离线消息保存时间保存到最长的10天,我看来Android端的文档,并没有可以设置的地方,话说这明明是后台做的,端上怎么保存,明明是收不到嘛才让后台保存离线消息的。然后就查阅了后台的Push API文档,后台可以通过time_to_live这个字段修改离线消息保留时间。
Options
// options(array $opts = array()) // 数组 $opts 的键支持 'sendno', 'time_to_live', 'override_msg_id', 'apns_production', 'big_push_duration' 中的一个或多个参数说明:
可选项 说明 sendno 表示推送序号,纯粹用来作为 API 调用标识,API 返回时被原样返回,以方便 API 调用方匹配请求与返回 time_to_live 表示离线消息保留时长(秒),推送当前用户不在线时,为该用户保留多长时间的离线消息,以便其上线时再次推送。默认 86400 (1 天),最长 10 天。设置为 0 表示不保留离线消息,只有推送当前在线的用户可以收到 override_msg_id 表示要覆盖的消息ID,如果当前的推送要覆盖之前的一条推送,这里填写前一条推送的 msg_id 就会产生覆盖效果 apns_production 表示 APNs 是否生产环境,True 表示推送生产环境,False 表示要推送开发环境;如果不指定则默认为推送生产环境 apns_collapse_id APNs 新通知如果匹配到当前通知中心有相同 apns-collapse-id 字段的通知,则会用新通知内容来更新它,并使其置于通知中心首位;collapse id 长度不可超过 64 bytes big_push_duration 表示定速推送时长(分钟),又名缓慢推送,把原本尽可能快的推送速度,降低下来,给定的 n 分钟内,均匀地向这次推送的目标用户推送。最大值为1400.未设置则不是定速推送
极光社区官方说明
使用内网的相关说明
- 如果内网服务器要调用JPush REST API,那么要开通端口80,443。
API 是有很多服务器的,所以每次调用的 IP 地址不同,所有 API 都只支持 https 访问,最好使用域名71。
- 我们有几个 IP 基本固定,可以考虑用这几个:
113.31.17.107
113.31.136.60
183.232.57.12
注:IP 会尽可能保持不变,但,IP不保证不变,IP也不保证一定固定;如果使用IP方式,IP如果变更或者增加,非极光VIP合作客户,我们不会另行知会,请知悉。客户端连内网,怎么与极光的服务器保持长连接?
开通VIP服务:企业sis方案。
联系商务,电话:QQ:800024881,电话:400-612-5955,邮箱:[email protected]内网使用极光推送需要服务器开放下列端口限制,用于JPush的登录注册及保持推送长链接:
19000
3000-3020
7000-7020
8000-8020备注:
sdk使用的几个域名:
s.jpush.cn
im.jpush.cn
stats.jpush.cn完全使用内网
其实我想说,完全内网隔离很难完全使用,尤其ios系统消息推送依赖苹果的接口。没有外网是无法推送到ios系统的,如果数据要求隐私性较高。可以咨询商务考虑私有云,具体信息可以咨询商务后在确认,简单说就是在你们的内网环境部署一套小型push系统,对于你们都是内网环境比较适合,至于ios系统针对这种情况可能要到时候在咨询相关技术支持才可以获得最终的结论。
关于收费问题,联系商务哦:商务QQ:800024881开发者商务邮箱:[email protected]
到此把这次集成极光推送过程中遇到的问题都总结了一下。对后期测试过程中,提出的问题一并做了记录。以后项目中再遇到什么问题和功能实现,继续升级总结。