公司的 app 一直使用的是極光推送,最近反饋比較多的是推送消息收不到,看來需要找新的推送服務了,在國內目前手機品牌占有率比較多的是華為和小米,且這兩家都有自己的推送服務,同時一個合作的友商說他們使用的是友盟推送,推送率還不錯,那么就測試這三個推送服務了。
按照集成的難易程度排序
友盟推送
接入步驟
官網有提供了視頻和文檔,很詳細,而且很簡單。
小米推送
接入步驟
-
在小米推送運營平台創建應用,地址點這里, 獲取到 AppID , AppKey
-
把從小米下載的 jar 放到 libs 下
-
在 AndroidManifest.xml 中添加權限
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.GET_TASKS" /> <uses-permission android:name="android.permission.VIBRATE"/> <permission android:name="com.xxx.xxx.permission.MIPUSH_RECEIVE" android:protectionLevel="signature" /> <uses-permission android:name="com.xxx.xxx.permission.MIPUSH_RECEIVE" />
-
配置推送服務需要的service和receiver
<service android:enabled="true" android:process=":pushservice" android:name="com.xiaomi.push.service.XMPushService"/> <service android:name="com.xiaomi.push.service.XMJobService" android:enabled="true" android:exported="false" android:permission="android.permission.BIND_JOB_SERVICE" android:process=":pushservice" /> <!--注:此service必須在3.0.1版本以后(包括3.0.1版本)加入--> <service android:enabled="true" android:exported="true" android:name="com.xiaomi.mipush.sdk.PushMessageHandler" /> <service android:enabled="true" android:name="com.xiaomi.mipush.sdk.MessageHandleService" /> <!--注:此service必須在2.2.5版本以后(包括2.2.5版本)加入--> <receiver android:exported="true" android:name="com.xiaomi.push.service.receivers.NetworkStatusReceiver" > <intent-filter> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver> <receiver android:exported="false" android:process=":pushservice" android:name="com.xiaomi.push.service.receivers.PingReceiver" > <intent-filter> <action android:name="com.xiaomi.push.PING_TIMER" /> </intent-filter> </receiver>
-
自定義一個BroadcastReceiver類
public class MiMessageReceiver extends PushMessageReceiver { private static final String TAG = "MiMessageReceiver"; private String mRegId; private String mTopic; private String mAlias; private String mAccount; private String mStartTime; private String mEndTime; @Override public void onNotificationMessageClicked(Context context, MiPushMessage message) { Log.v(MyApplication.TAG, "onNotificationMessageClicked is called. " + message.toString()); if (!TextUtils.isEmpty(message.getTopic())) { mTopic = message.getTopic(); Log.e(TAG, mTopic); } else if (!TextUtils.isEmpty(message.getAlias())) { mAlias = message.getAlias(); Log.e(TAG, mAlias); } } @Override public void onNotificationMessageArrived(Context context, MiPushMessage message) { Log.v(MyApplication.TAG, "onNotificationMessageArrived is called. " + message.toString()); String log = "Arrived a notification message. Content is " + message.getContent(); if (!TextUtils.isEmpty(message.getTopic())) { mTopic = message.getTopic(); } else if (!TextUtils.isEmpty(message.getAlias())) { mAlias = message.getAlias(); } } @Override public void onCommandResult(Context context, MiPushCommandMessage message) { Log.v(TAG, "onCommandResult is called. " + message.toString()); String command = message.getCommand(); List<String> arguments = message.getCommandArguments(); String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null); String cmdArg2 = ((arguments != null && arguments.size() > 1) ? arguments.get(1) : null); String log; if (MiPushClient.COMMAND_REGISTER.equals(command)) { if (message.getResultCode() == ErrorCode.SUCCESS) { mRegId = cmdArg1; Log.e(TAG, "Register push success."); } else { Log.e(TAG, "Register push fail."); } } else { log = message.getReason(); } } @Override public void onReceiveRegisterResult(Context context, MiPushCommandMessage message) { Log.v(TAG, "onReceiveRegisterResult is called. " + message.toString()); String command = message.getCommand(); List<String> arguments = message.getCommandArguments(); String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null); if (MiPushClient.COMMAND_REGISTER.equals(command)) { if (message.getResultCode() == ErrorCode.SUCCESS) { mRegId = cmdArg1; Log.e(TAG, "Register push success."); } else { Log.e(TAG, "Register push fail."); } } } }
-
在 AndroidManifest.xml 中注冊該廣播
<receiver android:name=".MiMessageReceiver" android:exported="true"> <intent-filter> <action android:name="com.xiaomi.mipush.RECEIVE_MESSAGE"/> </intent-filter> <intent-filter> <action android:name="com.xiaomi.mipush.MESSAGE_ARRIVED"/> </intent-filter> <intent-filter> <action android:name="com.xiaomi.mipush.ERROR"/> </intent-filter> </receiver>
-
在 Application 中初始化推送服務
private void initMiPush() { //初始化push推送服務 if (shouldInit()) { MiPushClient.registerPush(this, MI_APP_ID, MI_APP_KEY); } //打開Log LoggerInterface newLogger = new LoggerInterface() { @Override public void setTag(String tag) { // ignore } @Override public void log(String content, Throwable t) { Log.d(TAG, content, t); } @Override public void log(String content) { Log.d(TAG, content); } }; Logger.setLogger(this, newLogger); } private boolean shouldInit() { ActivityManager am = ((ActivityManager) getSystemService(Context.ACTIVITY_SERVICE)); List<ActivityManager.RunningAppProcessInfo> processInfos = am.getRunningAppProcesses(); String mainProcessName = getPackageName(); int myPid = android.os.Process.myPid(); for (ActivityManager.RunningAppProcessInfo info : processInfos) { if (info.pid == myPid && mainProcessName.equals(info.processName)) { return true; } } return false; }
華為推送
接入步驟(HMS-SDK版本:2.4.0.300):
-
把從華為 Push 推送官網下載的 aar ,位於:...\HMS-2.4.0.300\HWHMS-SDK-v2.4.0.300\libs\HMS-SDK-2.4.0.300.aar,放到工程目錄的 aars 下(沒有該目錄的新建一個)
-
在 AndroidManifest.xml 文件的 Application 節點添加: meta-data 和自定義的 Receiver,如下(其中 meta-data 中的 appId 是在網頁上創建應用的 id,我的是一個 8 位的數字):
<meta-data android:name="com.huawei.hms.client.appid" android:value="appId"> </meta-data> <!-- 第三方相關 :接收Push消息(注冊、Push消息、Push連接狀態)廣播 --> <receiver android:name=".HuaweiPushReceiver"> <intent-filter> <!-- 必須,用於接收token --> <action android:name="com.huawei.android.push.intent.REGISTRATION"/> <!-- 必須,用於接收消息 --> <action android:name="com.huawei.android.push.intent.RECEIVE"/> <!-- 可選,用於點擊通知欄或通知欄上的按鈕后觸發onEvent回調 --> <action android:name="com.huawei.android.push.intent.CLICK"/> <!-- 可選,查看push通道是否連接,不查看則不需要 --> <action android:name="com.huawei.intent.action.PUSH_STATE"/> </intent-filter> <meta-data android:name="CS_cloud_ablitity" android:value="@string/hwpush_ability_value"/> </receiver>
-
在 AndroidManifest.xml 文件中添加權限
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
-
在 MainActivity 或者 BaseAcitivty 中初始化華為 Push ,放在 onCreate() 中初始化華為 Push
private void initHuaweiPush(Context context) { HuaweiIdSignInOptions options = new HuaweiIdSignInOptions.Builder(HuaweiIdSignInOptions.DEFAULT_SIGN_IN) .build(); mClient = new HuaweiApiClient.Builder(context) .addApi(HuaweiPush.PUSH_API) .addConnectionCallbacks(new HuaweiApiClient.ConnectionCallbacks() { @Override public void onConnected() { getToken(); runOnUiThread(new Runnable() { @Override public void run() { tv.setText("HUAWEI onConnected, IsConnected: " + mClient.isConnected()); } }); Log.e(TAG, "HUAWEI onConnected, IsConnected: " + mClient.isConnected()); } @Override public void onConnectionSuspended(final int i) { runOnUiThread(new Runnable() { @Override public void run() { tv.setText("HUAWEI onConnectionSuspended, cause: " + i + ", IsConnected:" + " " + mClient.isConnected()); } }); Log.e(TAG, "HUAWEI onConnectionSuspended, cause: " + i + ", IsConnected:" + " " + mClient.isConnected()); } }) .addOnConnectionFailedListener(new HuaweiApiClient.OnConnectionFailedListener() { @Override public void onConnectionFailed(@NonNull final ConnectionResult connectionResult) { runOnUiThread(new Runnable() { @Override public void run() { tv.setText("HUAWEI onConnectionFailed, ErrorCode: " + connectionResult.getErrorCode()); } }); Log.e(TAG, "HUAWEI onConnectionFailed, ErrorCode: " + connectionResult.getErrorCode()); } }) .build(); mClient.connect(); } @Override protected void onStart() { super.onStart(); mClient.connect(); } private void getToken() { if (!isConnected()) { tv.setText("get token failed, HMS is disconnect."); return; } // 同步調用方式,不會返回token,通過廣播的形式返回。 new Thread(new Runnable() { @Override public void run() { PendingResult<TokenResult> token = HuaweiPush.HuaweiPushApi.getToken(mClient); token.await(); } }).start(); } public boolean isConnected() { if (mClient != null && mClient.isConnected()) { return true; } else { return false; } }
-
新建 HuaweiPushReceiver ,繼承 PushReceiver,重寫 onToken() ,onPushMsg(),onEvent(),onPushState(),在 onToken() 中可以獲取到 token。
public class HuaweiPushReceiver extends PushReceiver { private static final String TAG = "Huawei PushReceiver"; @Override public void onToken(Context context, String token, Bundle extras) { String belongId = extras.getString("belongId"); String content = "get token and belongId successful, token = " + token + ",belongId = " + belongId; Log.d(TAG, content); } @Override public boolean onPushMsg(Context context, byte[] msg, Bundle bundle) { try { String content = "-------Receive a Push pass-by message: " + new String(msg, "UTF-8"); Log.d(TAG, content); } catch (Exception e) { e.printStackTrace(); } return false; } public void onEvent(Context context, PushReceiver.Event event, Bundle extras) { if (Event.NOTIFICATION_OPENED.equals(event) || Event.NOTIFICATION_CLICK_BTN.equals(event)) { int notifyId = extras.getInt(BOUND_KEY.pushNotifyId, 0); if (0 != notifyId) { NotificationManager manager = (NotificationManager) context .getSystemService(Context.NOTIFICATION_SERVICE); manager.cancel(notifyId); } String content = "--------receive extented notification message: " + extras.getString (BOUND_KEY.pushMsgKey); Log.d(TAG, content); } super.onEvent(context, event, extras); } @Override public void onPushState(Context context, boolean pushState) { try { String content = "---------The current push status: " + (pushState ? "Connected" : "Disconnected"); Log.d(TAG, content); } catch (Exception e) { e.printStackTrace(); } } }
-
獲取到 token 以后就連接成功了。可以測試推送了。
非華為手機使用華為推送需要安裝-華為移動服務.apk
各個版本EMUI對push的支持情況
華為手機上:
Emui3.0上,Push廣播有很大概率被限制,如: Mate7 3.0版本,榮耀6plus,P7 3.0版本,4X, 4A等。
Emui3.1上,Push廣播基本不被限制,但個別型號機型存在問題,如:榮耀5x等。
Emui4.0及以上,Push廣播有較高概率被限制,不被限制的機型如:榮耀暢玩4C,榮耀暢玩4X,Mate S,P8 MAX等。
Emui4.1 , ROM升級到了最新版本的(80%已升),通知消息不走廣播,不會被限制,透傳消息走廣播,會被限制。
Emui5.0以上 ,通知消息不走廣播,不會被限制,透傳消息走廣播,會被限制。
如廣播被限制,需要將應用設為開機啟動項。所以對於及時性或到達率要求非常高的應用,我們建議應用要考慮替代方案。
非華為手機:
第三方手機(如:小米、OPPO、三星等),由於rom的限制,需要將應用 設為開機啟動項。
測試設備
- Samsung S4(Android 5.0)
- HTC D820u(Android 6.0)
- Huawei P8(Android 6.0)
- Xiaomi Note(Android 7.0)
- Samsung S7(Android 7.0)
- LG Nexus 6(Android 7.0)
- Huawei Mate8(Android 7.0)
- Huawei Mate9(Android 7.0)
測試數據
三家推送比較
- 華為推送在非華為手機上必須安裝華為移動服務,這點比較的坑,用戶可能不會同意安裝的,那就沒得玩了,只適合在華為手機上使用。
- 華為推送還得區別 Emui 的版本,這玩意也不是個小坑,雖然官方QQ群公告說 Emui 5.0 以后都可以收到消息推送,但是不知道靠譜不靠譜
- 友盟推送的話,在測試階段,發現三星S7(Android 7.0),三星S4(Android 5.0),華為P8(Android 6.0)無法獲取到 token,沒有 token ,那就推送不了了。
- 小米推送還可以,但是在三星 S4(Android 5.0) 上無法接收到推送。
- 綜上,app 需要集成華為推送和小米推送比較的靠譜點,針對華為手機使用華為推送,其他手機使用小米推送。
- 使用中的大坑,在華為手機上假如華為移動服務不是最新版本或者被卸載了,推送服務無法使用。必須在 app 中提示用戶更新或安裝,真是一個大坑,關鍵是更新界面丑的要死,還會出現更新失敗的情況,坑人呀。
- 針對小米推送,在非小米設備上會出現重啟以后無法獲得推送,因為是重啟以后,小米的 XMPushService 沒有起來,目前采用的辦法是監聽手機啟動廣播,然后啟動小米的 XMPushService。