Android SDK教程

Android SDK 網絡問題解析

Android 客戶端網絡不穩定,會致使App 有時候沒法及時收到 Push 消息。android

不少開發者認爲這是由於 JPush 推送不穩定、延遲,甚至有時候認爲 JPush 後臺推送系統出問題了。json

本文目的是從各個方面來分析 Android 網絡致使的 JPush 不能正常工做的問題。api

JPush 正常工做的必要條件

首先,咱們須要知道,JPush SDK 並非集成到App 後就必然一直工做的。安全

其正常工做的必要條件是:JPush SDK 與 JPush Server 的網絡保持着鏈接。請參考這篇文章來作進一步的理解:極光推送技術原理:移動無線網絡長鏈接。服務器

而 Android 設備的網絡的複雜性、不穩定性,是 Android 設備開發最複雜的地方之一。微信

另外,每款手機的網絡能力也是千差萬別的。國內不少雜牌手機在網絡方面甚至會有嚴重的問題。大品牌廠商的手機則要好不少。網絡

只要 JPush 的網絡鏈接是正常的,則:app

  • JPush 收到消息必定是及時的。其延遲是秒級的,通常在 1 秒以內。若是超過 10 秒,則必定是客戶端網絡出了問題。
  • 手機休眠時,也可以及時地收到推送消息。

部分系統的特殊處理致使問題

MIUI V5 系統
  • 自啓動管理:默認狀況下,手機開機後,只有系統默認的服務能夠啓動起來。除非在自啓動管理界面,設置容許第三方程序自啓動。異步

  • 網絡助手:能夠手動禁止已安裝的第三方程序訪問2G/3G和WIFI的網絡和設置之後新安裝程序是否容許訪問2G/3G和WIFI的網絡。ide

4.0以上的android系統
  • 在設置->應用,強行中止 應用程序後該程序沒法再自啓動,就算從新開機也同樣,必定要手動開啓才能運行起來。

讓咱們從目前獲得的反饋來整理調試的思路

手機休眠時收不到 JPush 消息,解鎖或屏幕燈亮則能夠成功接收

這個現象代表,手機休眠時,JPush SDK 「被迫」與服務器端的網絡失去了鏈接。

JPush SDK 的工做原理是要確保在手機休眠時也能正常的工做,即休眠時也能夠及時地收到Push消息。實際上JPush在大部分上手機上都能達到此效果。

這個「被迫」,是由 Android 設備的環境所致使的。涉及的緣由有以下幾個方面:

  • 手機自己的網絡設置。標準版本的 Android ROM 是沒有這個設置的,但某些特殊的 ROM 可能會有這方面的設置。
  • 手機上的安全、省電工具軟件額外作的事情

上述的特殊機制會關閉網絡。網絡一旦鏈接上,JPush也會鏈接上服務器,從而Push消息就會收到。

有時候收到 JPush 消息很及時,有時候則要等幾分鐘

JPush 會監聽網絡切換廣播。當網絡關閉時,把原來JPush鏈接關閉。當有新的網絡時,建立JPush鏈接。

另外,RTC會定時發送心跳。若是以前的網絡已經斷了,則會從新鏈接。

應該說,當前的網絡鏈接策略仍是相對簡單的,這樣作的目的是:省電、省流量。

不 好之處就是:網絡沒有切換時,由於當時網絡過差,JPush鏈接會被中斷。這種狀況下,就只能等 RTC 心跳去觸發鏈接。這也是有時候JPush 沒法及時接收Push消息的緣由。根據網絡條件的不一樣,出現這個狀況的機率也會不一樣。但據咱們本身的測試,90% 的時候是能夠及時地收到Push消息的。

JPush 目前在網絡策略方面沒有像微信這種聊天工具作得積極。若是這樣作到,電量和流量的消耗必然會成倍地增長。

徹底收不到 JPush 消息

若是集成以後就徹底收不到Push消息,則頗有多是某個地方配置錯誤。請根據文檔仔細檢查:Android SDK 集成指南,iOS SDK 集成指南,或者根據參考教程:Android SDK 調試指南,iOS SDK 調試指南。

Android SDK 調試指南

SDK啓動過程

  1. 檢查AndroidManifest.xml中是否有配置AppKey,若是沒有,則啓動失敗
  2. 檢查 Androidmanifest.xml文件配置的正確性,必需要保證「Android SDK 集成指南」中全部標註「
  3. Required」的部分都正確配置,不然啓動失敗
  4. 檢查 JPush SDK庫文件的有效性,若是庫文件無效,則啓動失敗
  5. 檢查網絡是否可用,若是網絡可用則鏈接服務器登陸,不然啓動失敗
  6. 登錄成功後能夠從log中看到以下log

測試確認

  • 確認 Androidmanifest.xml 中所需的全部 「Required」 項都已經添加。若是有 "Required" 項未添加,日誌會提示錯誤。
  • 確認 AppKey (在Portal上生成的) 已經正確的寫入 Androidmanifest.xml 中,沒寫會有日誌提示錯誤。
  • 確認在程序啓動時候調用了init(context) 接口
  • 確認測試手機(或者模擬器)的網絡可用,若是網絡正常可用,客戶端調用 init 後不久,應有登陸成功(Login succeed)的日誌信息,如 SDK 啓動過程所示
  • 啓動應用程序,登錄 Portal 系統,並嚮應用程序發送自定義消息或者通知欄提示。在幾秒內,客戶端應可收到下發的通知或者正定義消息.

別名與標籤使用教程

爲何須要別名與標籤

推送消息時,要指定推送的對象:所有,某一我的,或者某一羣人。

所有很好辦,針對某應用「羣發」就行了。Portal與API都支持向指定的 appKey 羣發消息。

要指定向某一個特定的人,或者某一羣特定的人,則相對複雜。由於對於 JPush 來講,某一我的就是一個註冊ID,這個註冊ID與開發者App沒有任何關係,或者說對開發者App是沒有意義的。

若是要對開發者App有意義的某個特定的用戶推送消息,則須要:把 JPush 註冊用戶與開發者App 用戶綁定起來。

這個綁定有兩個基本思路:

  • 把綁定關係保存到 JPush 服務器端
  • 把綁定關係保存到開發者應用服務器中

前者,就是這裏要說到的:別名與標籤的功能。這個機制簡單易用,適用於大多數開發者。

後者,則是 JPush 提供的另一套 RegistrationID 機制。這套機制開發者須要有應用服務器來維護綁定關係,不適用於普通開發者。Android SDK r1.6.0 版本開始支持。

使用方式

別名與標籤的機制,其工做方式是:

  1. 客戶端開發者App調用 setAliasAndTags API 來設置關係
  2. JPush SDK 把該關係設置保存到 JPush Server 上
  3. 在服務器端推送消息時,指定向以前設置過的別名或者標籤推送

SDK 支持的 setAliasAndTags 請參考相應的文檔:別名與標籤 API

使用過程當中有幾個點作特別說明:

  1. App 調用 SDK setAliasAndTags API 時,r1.5.0 版本提供了 Callback 來返回設置狀態。若是返回 6002 (超時)則建議重試

    • 老版本沒有提供 Callback 無設置狀態返回,從而沒有機制肯定必定成功。建議升級到新版本
  2. Portal 上推送或者 API 調用向別名或者標籤推送時,可能會報錯:不存在推送目標用戶。該報錯代表,JPush Server 上尚未針對你所推送的別名或者標籤的用戶綁定關係,因此沒有推送目標。這時請開發者檢查確認,開發者App是否正確地調用了 setAliasAndTags API,以及調用時是否網絡很差,JPush SDK 暫時未能保存成功。

使用別名

用於給某特定用戶推送消息。

所謂別名,能夠近似地被認爲,是用戶賬號裏的暱稱。

使用標籤

用於給某一羣人推送消息。

標籤相似於博客裏爲文章打上 tag ,即爲某資源分類。

動態標籤

JPush 提供的設置標籤的 API 是在客戶端的。開發者如何作到在本身的服務器端動態去設置分組呢? 好比一個企業OA系統,常常須要去變動部門人員分組。如下是大概的思路:

  • 設計一種自定義消息格式(業務協議),App解析後能夠調用 JPush SDK setAliasAndTags API 來從新設置標籤(分組)
    • 例:{"action":"resetTags", "newTags":["dep_level_1":"A公司", "dep_level_2":"技術部", "dep_level_3":"Android開發組", "address":"深圳", "lang":"zh"]}
  • 要動態設置分組時,推送這條自定義消息給指定的用戶
    • 使用別名的機制,推送到指定的用戶。
  • 客戶端App 調用 JPush SDK API 來設置新的標籤

別名與標籤設置異常處理

因爲網絡鏈接不穩定的緣由,有必定的機率 JPush SDK 設置別名與標籤會失敗。

App 開發者合理地處理設置失敗,則偶爾失敗對應用的正常使用 JPush 影響是有限的。

如下以 Android SDK 做爲示例。

基本思路:

  • 設置成功時,往 SharePreference 裏寫狀態,之後沒必要再設置
  • 遇到 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 來看所支持的功能。

自定義通知欄樣式設計
  • 有個 PushNotificationBuilder 概念,開發者使用 setPushNotificationBuilder 方法爲某種類型的 PushNotificationBuilder 指定編號。
  • setPushNotificationBuilder 能夠在 JPushInterface.init() 以後任何地方調用,能夠是開發者應用的邏輯來觸發調用,或者初始化時調用。
  • 只須要設置一次,JPush SDK 會記住這個設置。在下次收到推送通知時,就根據通知裏指定的編號來找到 PushNotificationBuilder 來展示、執行。
API - setDefaultPushNotificationBuilder 設置默認

此 API 改變默認的編號爲 0 的通知欄樣式。

API - setPushNotificationBuilder 指定編號

此 API 爲開發者指定的編號,設置一個自定義的 PushNotificationBuilder(通知樣式構建器)。

Example - 基礎的 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);
Example - 高級自定義的 PushNotificationBuilder

基於基礎的 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. 自定義消息。

通知 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();     } }
相關文章
相關標籤/搜索