转载请标明出处

http://blog.csdn.net/mohan6/article/details/74133186

本文作者:【默寒的博客】

前言

前段时间针对集成极光推送写了篇文章( 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<RunningAppProcessInfo> 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";@Overridepublic 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);//解析jsonString 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<String> 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 ac = com.carspass.common.util.ActivityManager.getAppManager().currentActivity();

获取到当前处于栈顶的activity。如果这个activity是null,说明进程已经被杀死,如果不等于null,说明进程是在前台或者后台。

/*** 应用程序Activity管理类:用于Activity管理和应用程序退出*/
public class ActivityManager {private Stack<Activity> 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<Activity>();}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标记就可以。比如

 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP);

如果栈顶有就直接复用当前activty,没有在去new一个新栈去放新的activity。这样话,我就无需去判断应用是否在前后台,在前台,如果要打开一个新的页面,就开一个新的task去存放新的actvity,如果只是纯打开应用,本身就是开着呢,就没啥反应。在后台,就把之前的页面打开,或是+新的页面,或是纯打开之前的页面。

所以我只需要通过获取栈顶的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() {@Overridepublic void onActivityCreated(Activity activity, Bundle bundle) {}@Overridepublic void onActivityStarted(Activity activity) {}@Overridepublic void onActivityResumed(Activity activity) {
//                  LogUtil.i("ym", "判断前后台界面:" + activity.getClass().getSimpleName() + "前台");if (TextUtils.equals(activity.getClass().getSimpleName(), "ACT_BargainingDetail")) {Contants.ACT_BargainingDetailFlag = "foreground";}}@Overridepublic void onActivityPaused(Activity activity) {}@Overridepublic void onActivityStopped(Activity activity) {
//                  LogUtil.i("ym", "判断前后台界面:" + activity.getClass().getSimpleName() + "后台");if (TextUtils.equals(activity.getClass().getSimpleName(), "ACT_BargainingDetail")) {Contants.ACT_BargainingDetailFlag = "background";}}@Overridepublic void onActivitySaveInstanceState(Activity activity, Bundle bundle) {}@Overridepublic void onActivityDestroyed(Activity activity) {}});}}

议价详情页刷新的方法:

@Overrideprotected void onStart() {super.onStart();//2017.6.30 议价详情页刷新 v2.0.0新增  ym startif (getIntent() != null && getIntent().getExtras() != null) {// 获取议价idid = getIntent().getExtras().getString("id");String pushBargainId = share.getString("pushBargainId");if (!TextUtils.isEmpty(pushBargainId)) {id = pushBargainId;//清空share.putString("pushBargainId", "");}// 2016.6.6 未读议价消息进入议价详情标识 ym startmessageRead = getIntent().getExtras().getString("messageRead");// 2016.6.6 未读议价消息进入议价详情标识 ym endgetBargainingDetail();}//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<String> 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<String> 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);  }  }  };  

我认为通过下面这句给设置极光别名,不管成功不成功,开个头。这句是同步的。

 mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_ALIAS,  aliasAndTagsInfo)); 

我通过debug,断网操作,发现断网后,极光接口内部会自动判断是否重新联网了,联网成功便把刚才没发给极光服务器的设置别名发出去,然后得到成功的0的回调。假如设置失败,会走6002,隔60s以后重新发送。

所以,我认为并不会出现卡顿的情况,不管设置别名成功还是失败,设置别名后面的代码仍会继续执行。而且这种连接第三方sdk服务器的接口都是异步执行的吧。还是不能理解大神的意思。

三、兼容项目低版本Bug解决

我们有极光的版本是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 startif (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 startString 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}

这样就成功地解决了兼容低版本因增量更新包而不走登录设置不上别名的问题。

四、测试后发现,android端进程被杀死就无法收到Push,产品觉得特别无法接受,因为Ios可以,为啥android就不可以,而且为啥送达率那么低。

极光社区官方说明

推送成功率问题

「如何理解送达成功率」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.未设置则不是定速推送

六、内外网的问题

极光社区官方说明

使用内网的相关说明


  1. 如果内网服务器要调用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合作客户,我们不会另行知会,请知悉。
  2. 客户端连内网,怎么与极光的服务器保持长连接?

    • 开通VIP服务:企业sis方案。
      联系商务,电话:QQ:800024881,电话:400-612-5955,邮箱:sales@jpush.cn

    • 内网使用极光推送需要服务器开放下列端口限制,用于JPush的登录注册及保持推送长链接:
      19000
      3000-3020
      7000-7020
      8000-8020

      备注:
      sdk使用的几个域名:
      s.jpush.cn
      im.jpush.cn
      stats.jpush.cn

  3. 完全使用内网

其实我想说,完全内网隔离很难完全使用,尤其ios系统消息推送依赖苹果的接口。没有外网是无法推送到ios系统的,如果数据要求隐私性较高。可以咨询商务考虑私有云,具体信息可以咨询商务后在确认,简单说就是在你们的内网环境部署一套小型push系统,对于你们都是内网环境比较适合,至于ios系统针对这种情况可能要到时候在咨询相关技术支持才可以获得最终的结论。

关于收费问题,联系商务哦:商务QQ:800024881开发者商务邮箱:sales@jpush.cn

小结

到此把这次集成极光推送过程中遇到的问题都总结了一下。对后期测试过程中,提出的问题一并做了记录。以后项目中再遇到什么问题和功能实现,继续升级总结。

Android集成极光推送踩坑(二)升级篇相关推荐

  1. Android集成极光推送和踩过的坑(一)

    转载请标明出处 http://blog.csdn.net/mohan6/article/details/72960346 本文作者:[默寒的博客] 集成步骤以及集成过程遇到的坑: 这部分主要阐述了集成 ...

  2. Android 集成极光推送和厂商通道

    JPush 产品简介 Push 是经过考验的大规模 App 推送平台,每天推送消息量级为数百亿条. 开发者集成 SDK 后,可以通过调用 API 推送消息.同时,JPush 提供可视化的 web 端控 ...

  3. Android集成极光推送Flutter

    该文章只包含Andriod的集成方式 一.Flutter文档集成步骤文档地址 极光推送客户端集成插件 二.关于集成方法总结 ①.创建应用 在极光推送官方网站上创建应用获取Appkey ②.安装jpus ...

  4. Android集成极光推送

    话不多说先上图: 集成步骤: 1.新建Android Studio项目,因为之后会用到包名 2.建项目后登录极光推送开发者平台创建自己的应用 https://www.jiguang.cn/dev2/# ...

  5. Android基于极光推送实现单点登录

    上一篇参考了一位博友的文章,给大家分享了如何集成极光推送,没看上一篇的请先看上一篇 http://blog.csdn.net/gao_blog/article/details/79640279 今天主 ...

  6. Android华为推送踩坑,极光推送集成华为遇到的坑?

    一.前言: 首先极光推送对各个厂商通道对接是没有在开发者平台提供文档的,需要申请VIP资格后,极光才会提供对应对接文档. 1.极光普通集成 1.步骤1 图片.png 2.步骤2 图片.png 3.步骤 ...

  7. Android第三方SDK集成 —— 极光推送

    前言: 本文前篇,可以帮助朋友们快速集成极光推送.本文后篇,是我自己项目实践的一些总结和心得,应该对读者们还是很有参考价值的,相信读完这篇文章,你会对极光推送有更加深入的理解,而不仅仅只是会集成而已. ...

  8. React-Native集成极光推送(Android和IOS)

    React-Native集成极光推送的具体流程如下: 本文选取的是极光官方维护的react-native推送插件,github地址:https://github.com/jpush/jpush-rea ...

  9. Java中集成极光推送实现给Android提送消息通知(附代码下载)

    场景 Android中集成极光推送实现推送消息通知与根据别名指定推送附示例代码下载: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details ...

最新文章

  1. 第四季度数据中心网络设备销量达35亿美元
  2. SpringBoot(1.5.6.RELEASE)源码解析(一)
  3. 年轻群体当道,哈弗F7如何赢得芳心?
  4. 中秋干货 | 架构进阶之路上的实时数仓
  5. react前端显示图片_在react里怎么引用图片
  6. Dotnet 6.0,你值得拥有
  7. git.exe 启动 慢_四川成都surface电脑启动到一半黑屏维修服务地址电话
  8. 2021年4月Oracle数据库补丁分析报告
  9. 基于phash和汉明距离找出相似图片
  10. HTMO DOM部分---小练习;列表之间移动、日期选择、好友选中、滑动效果、滚动条效果、飞入飞出效果。...
  11. HDU 4722 Good Numbers 2013年四川省赛题
  12. 11. 给 apache ,nginx 设置变量
  13. Linux下搭建Haproxy负载均衡
  14. 如果计算机正执行屏幕保护程序 当用户,计算机一级考试参考试题(含答案)篇篇一.doc...
  15. bochs镜像java模拟器_bochs镜像下载
  16. 和小白一起学习V4L2采集视频
  17. 火线、地线、零线区别
  18. iPhone线控耳机如何使用教程
  19. 大神爆料:红米K30S至尊纪念版和红米10XPro哪个好-哪个更值得入手-参数对比
  20. 使用idea打包web项目为war

热门文章

  1. 《曾国藩传》第一卷京官时代-读后感
  2. Linux CPU,内存查查清楚
  3. java.exe应用程序出错_EXPLORER.EXE应用程序错误的原因和解决办法
  4. 使用Scrapy爬取笑话并存储到文件和MySQL
  5. 使用c++代码打败红蜘蛛
  6. hdu4899 dp套dp
  7. c++服务器protobuf使用
  8. php引用中国联通M2M接口
  9. That引导的宾语从句
  10. Autodesk Inventor Professional 2022.0.1 Update Only x64