Android整合極光推送踩坑(二)升級篇
轉載請標明出處
前言
前段時間針對整合極光推送寫了篇文章( 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"; @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<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。如果這個activity是null,說明程序已經被殺死,如果不等於null,說明程序是在前臺或者後臺。Activity ac = com.carspass.common.util.ActivityManager.getAppManager().currentActivity();
/** * 應用程式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標記就可以。比如如果棧頂有就直接複用當前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<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); } } };
我通過debug,斷網操作,發現斷網後,極光介面內部會自動判斷是否重新聯網了,聯網成功便把剛才沒發給極光伺服器的設定別名發出去,然後得到成功的0的回撥。假如設定失敗,會走6002,隔60s以後重新發送。mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_ALIAS, aliasAndTagsInfo));
所以,我認為並不會出現卡頓的情況,不管設定別名成功還是失敗,設定別名後面的程式碼仍會繼續執行。而且這種連線第三方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 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 }
四、測試後發現,android端程序被殺死就無法收到Push,產品覺得特別無法接受,因為Ios可以,為啥android就不可以,而且為啥送達率那麼低。
推送成功率問題
android、iOS的送達根本就不適合用於比較。 android目標是進一個月內的線上使用者(即一個月內有建立過jpush長連線的使用者)裡面根據你的audience來匹配使用者,而推送是否能送達和你的應用使用者的線上活躍是成正比的。 如果你想了解一般的第三方android應用收到哪些系統限制而導致你的應用存活低下,請看:http://docs.jpush.io/guideline/faq/#android 的[第三方系統收不到推送的訊息] 而iOS的成功數是指成功推送到apns伺服器的數量,iOS的點選本來就很低,大多人看到推送後可能是直接進入應用檢視並直接清除通知中心裡面的通知,也可能看了一下通知後感覺沒有興趣,直接清通知欄。iOS的點選低不等於推送的到裝置數少。
天線上 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]
小結
到此把這次整合極光推送過程中遇到的問題都總結了一下。對後期測試過程中,提出的問題一併做了記錄。以後專案中再遇到什麼問題和功能實現,繼續升級總結。
相關推薦
Android整合極光推送踩坑(二)升級篇
轉載請標明出處 前言 前段時間針對整合極光推送寫了篇文章( Android整合極光推送和踩過的坑),後來提測以後發現了各種問題。一直沒時間總結一下,趁著週末有點時間,趕緊把這段時間裡針對Push這塊兒遇到的問題梳理一下。並且對上篇文章 《Android整合極光推
Android整合極光推送和踩過的坑(一)
轉載請標明出處 整合步驟以及整合過程遇到的坑: 這部分主要闡述了整合極光推送的sdk的步驟,以及我在整合過程中遇到的一些問題。整合步驟只是摘出了極光SDK中必須的骨子的部分,可以滿足一般專案Push需求,這裡只做了通知訊息,自定義的穿透訊息請詳見極光的SDK整合文件
關於整合極光推送的坑
1、根據文件使用自動整合, 需要在AndroidManifest.xml中加入下列程式碼(加粗部分) <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.androi
Android整合極光推送
概述 推送是現在大部分應用都擁有的一項功能,使用推送的目的就是為了讓客戶端接收到最新的訊息以及提醒等,今天我們就來學習一下目前用的比較廣泛的極光推送。 整合過程 首先進入極光推送官網,註冊並且登入帳號,地址如下 登入成功後,會跳到建立應用介面
極光推送 使用例項 (一)服務端
原文:http://blog.csdn.net/u014733374/article/detail
【轉載】極光推送 使用例項 (一)服務端
最近一直在做後臺開發,但心裡還是總惦記著Android,感覺還是Android有意思。正好專案中要用到極光推送,今天抽空來記錄下這兩天的研究成果。 我們知道IOS有自己的推送服務,但很遺憾Android沒有原生的推送服務,現在有很多第三方的推送服務,比如個推、極光、亞馬遜、百度雲、聚能等。今天
spring boot 初步踩坑(二)—— 打包載入本地jar
打包時載入本地jar,度娘提供了幾種方式,下邊是我遇到的幾種方式: 1、打包jar 有兩種方式 1) org.springframework.boot spring-boot-maven-plug
Android快速整合極光推送,內含自定義通知,通知推送物件到某一個人,或者某一群人
整合極光推送 使用jcenter 自動整合步驟 說明 : 使用 jcenter 自動整合,不需要在專案中新增 jar 和 so,jcenter 會自動完成依賴;在 AndroidManifest.xml 中不需要新增任何 JPush SDK 相關的配置,jcen
Android整合信鴿推送【華為廠商通道之坑】
Android整合信鴿推送(華為通道)之坑 公司App之前用的是騰訊信鴿推送【V3.2.2之前版本】前不久信鴿新增加了華為小米魅族廠商通道【V3.2.2之前版本】,然後就趕緊升級新增華為小米魅族廠商通道,提高推送成功率。 首先按照流程開始準備工作 第
Android 使用極光推送整合、出現問題和後臺保活
今天接入極光推送學習一下,就發現的問題一起探討 主要包含了註冊、接入、sdk版本獲取失敗、後臺保活介紹 接入的時候還是按照官方接入流程,主要就是一下三步 註冊 註冊極光推送賬號 建立應用(記錄自己的appkey) 根據平臺設定推送設定,主要是設定一個包名(這個包名一定要和自
使用android studio整合極光推送
1.下載sdk http://docs.jiguang.cn/resources/ 2.解壓下載的壓縮包 點選開啟libs,如下圖: 我把資料夾中的內容分成了兩部分,以便下面用。接下來建一個名為
Eclipse Android開發整合極光推送
今天上午閒來無事,想起還沒做過推送。去官網準備整合環境。看了下是android studio的,我就沒往下看。百度了幾篇文章,硬是沒弄出來,主要是百度的文章比較舊,和現在的sdk有區別。所幸我已經完美繼承,特此寫一篇繼承文件給剛準備入坑的小夥伴。第一步:進入極光推送官網。註冊
swift3.0 整合極光推送(v2.2)iOS10.0最新寫法
// // AppDelegate.swift // 1120-jiguang // // Created by targetcloud on 2016/11/20. // Copyright © 2016年 targetcloud. All rights rese
Android Studio整合極光推送(Jpush) 報錯 java.lang.UnsatisfiedLinkError: cn.jpush.android.service.PushProtoco
Android studio 整合極光推送(Jpush) (華為手機)報錯, E/JPush: [JPushGlobal] Get sdk version fail![獲取sdk版本失敗!] W/System.err: java.lang.UnsatisfiedLinkError: cn.jpush.a
項目實戰:iOS極光推送集成(30分鐘搞定)
adg append ati 技術分享 tro markdown ocs sym xcode 推送有非常多,如個推、友盟、融雲和極光等等。在這裏就講下怎樣使用極光推送。主要內容是將官方文檔資料詳細匯總並一步一步集成到項目中,您也能夠直接去官方文檔閱
整合極光推送 定時推送
前端,後端,極光推送之間的邏輯關係 (誤區:php與前端app要對接,其實不要直接對接,php直接呼叫極光伺服器就可以了) 一、安裝jpush 極光推送 在composer的配置檔案中加入 "require": { "jpush/jp
ionic 1,2 整合極光推送
專案用到極光推送,在此做個總結,以免忘記! 首先,這肯定要用到cordova的jpush外掛,這個外掛跟cordova整合的百度定位外掛有點衝突,需注意! 去極光官網申請appkey,這是必須的; 然後add此外掛,帶上appkey; 好了,在app.js檔案初始化,定義方法,程式碼
Swift 3.0 整合極光推送
1.前言 推送證書配置什麼的都不多講了,極光推送的開發文件裡都有詳細的介紹極光推送文件,因為官方的文件是OC版本的,我這裡主要是講解一下怎麼用Swift進行整合。 本篇文章也可移步簡書閱覽,效果更好哦! 2.配置 現在一切都已經根據他們的文件配置好了
Android 關於極光推送時測試和正式的問題
極光推送並沒有提供區分測試和正式的欄位,如果想區分測試和正式的環境,可以重新註冊一個,在重新註冊時,會讓你重新填寫包名, release: com.jpush.test debug: com.jpush.test.debug 然後我們配置gradle,
極光推送的坑
極光推送開發環境可以收到,生產環境收不到推送 初次發現這個問題的時候 首先排查的就是證書配置。 綠色的表示沒什麼問題。 開發環境的證書跟生產環境的證書 都已經配置完成, 下面我們看看極光官網的配置 這個也沒有問題。 然後看程式碼 //極光植入 &