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(); } }