May 14, 2019 添加更新,對(duì)于Android O(API 26)及以上的手機(jī)死遭,如果target API ≥ 26(Android 8.0),則必須自己生成NotificationChannel,否則推送無法顯示消息朱巨,8.0之前的不會(huì)受影響,系統(tǒng)默認(rèn)生成一個(gè)NotificationChannel枉长,使用NotificationManager.createNotificationChannel()可以生成NotificationChannel冀续。
一,環(huán)境
華為榮耀V8 EMUI 8.0.0 Android 8.0.0
Android Studio 3.3.2
華為推送版本 2.5.2.300
SDK集成方式:
參考華為開發(fā)者聯(lián)盟>HMS>資源中心>消息推送服務(wù)>集成SDK
華為推送分為新老兩個(gè)版本必峰,注意區(qū)別洪唐,新版本SDK為HMS Push,老版本切換到新版本需要更新SHA256證書指紋
二吼蚁,簡(jiǎn)介
- 端內(nèi)推送
App<->推送服務(wù)器
長(zhǎng)連接凭需,服務(wù)器下發(fā)推送消息給App進(jìn)程,App進(jìn)程通過NotificationManager
在通知欄進(jìn)行顯示肝匆,比如IM粒蜈,運(yùn)動(dòng)跟蹤之類的應(yīng)用,優(yōu)點(diǎn)是速度快旗国,能夠自行保證送達(dá)率枯怖,延時(shí)小,缺點(diǎn)是App進(jìn)程在被系統(tǒng)或用戶手動(dòng)kill掉后能曾,無法收到推送度硝,一般大廠在做好端外推送的同時(shí)也會(huì)自己做端內(nèi)推送设捐,小公司就不要考慮那么多了。 - 端外推送
當(dāng)App<->推送服務(wù)器
長(zhǎng)連接斷開后塘淑,推送走的就是端外推送了萝招,因?yàn)椴灰蕾嘇pp客戶端,靠的只能是第三方推送平臺(tái)提供的服務(wù)存捺,主要分成三大類槐沼,手機(jī)廠商,專業(yè)的第三方推送捌治,BAT的推送岗钩,比如(排名不分先后):小米,華為肖油,個(gè)推兼吓,極光,百度云森枪,阿里云移動(dòng)视搏,騰訊信鴿等。
作為默默無聞的小公司县袱,端內(nèi)推送開發(fā)維護(hù)成本太高浑娜,直接忽略不考慮;
專業(yè)的第三方推送反而比較適合式散,因?yàn)樘峁┝朔奖泯R全的功能筋遭,比如個(gè)推,但是個(gè)推也會(huì)因?yàn)楦鞣N原因有推送不達(dá)的情況暴拄,所以在個(gè)推基礎(chǔ)上可以補(bǔ)充一點(diǎn)廠商的推送漓滔,至于具體選擇哪個(gè)可以參考文章連接:Android 端外推送到底有多煩?(https://juejin.im/post/57a19c012e958a0066715d0c)選擇合適自己方案
我選擇的是個(gè)推乖篷,華為和小米响驴,選擇華為和小米的原因也很無奈,這兩者市場(chǎng)最大那伐,用戶使用這兩種手機(jī)較多踏施,使用個(gè)推又一直有推送不達(dá)的情況石蔗,公司主營(yíng)的業(yè)務(wù)又對(duì)推送的送達(dá)率有較高要求罕邀,只能集成華為和小米推送來獲得更好的送達(dá)率
這里只討論華為推送集成,但是在App初始化的時(shí)候會(huì)選擇適合自己手機(jī)的推送方式养距,即華為手機(jī)上選擇華為推送诉探,小米手機(jī)選擇小米推送,其他手機(jī)選擇個(gè)推
參考文章:
[Android] 代碼獲取手機(jī)系統(tǒng)類型(小米MIUI棍厌、華為EMUI肾胯、魅族FLYME)
Android常見ROM類型識(shí)別MD
獲取系統(tǒng)手機(jī)類型
public static final String SYS_EMUI = "sys_emui";
public static final String SYS_MIUI = "sys_miui";
private static final String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code";
private static final String KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name";
private static final String KEY_MIUI_INTERNAL_STORAGE = "ro.miui.internal.storage";
private static final String KEY_EMUI_API_LEVEL = "ro.build.hw_emui_api_level";
private static final String KEY_EMUI_VERSION = "ro.build.version.emui";
private static final String KEY_EMUI_CONFIG_HW_SYS_VERSION = "ro.confg.hw_systemversion";
public static String getSystem(){
String SYS = "";
//Android API 26及以后會(huì)有Permission deny的情況竖席,需要使用反射機(jī)制來獲取
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
if (!TextUtils.isEmpty(getSystemProperty(KEY_MIUI_VERSION_CODE, ""))
|| !TextUtils.isEmpty(getSystemProperty(KEY_MIUI_VERSION_NAME, ""))
|| !TextUtils.isEmpty(getSystemProperty(KEY_MIUI_INTERNAL_STORAGE, ""))) {
SYS = SYS_MIUI;//小米
}else if (!TextUtils.isEmpty(getSystemProperty(KEY_EMUI_API_LEVEL, ""))
|| !TextUtils.isEmpty(getSystemProperty(KEY_EMUI_VERSION, ""))
|| !TextUtils.isEmpty(getSystemProperty(KEY_EMUI_CONFIG_HW_SYS_VERSION, ""))) {
SYS = SYS_EMUI;//華為
}
return SYS;
} else {
try {
Properties prop= new Properties();
prop.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop")));
if(prop.getProperty(KEY_MIUI_VERSION_CODE, null) != null
|| prop.getProperty(KEY_MIUI_VERSION_NAME, null) != null
|| prop.getProperty(KEY_MIUI_INTERNAL_STORAGE, null) != null){
SYS = SYS_MIUI;//小米
}else if(prop.getProperty(KEY_EMUI_API_LEVEL, null) != null
||prop.getProperty(KEY_EMUI_VERSION, null) != null
||prop.getProperty(KEY_EMUI_CONFIG_HW_SYS_VERSION, null) != null){
SYS = SYS_EMUI;//華為
}
} catch (IOException e){
HHLog.e(TAG, "error, info:" + Log.getStackTraceString(e));
e.printStackTrace();
}
return SYS;
}
}
根據(jù)手機(jī)系統(tǒng)類型不同,把對(duì)應(yīng)系統(tǒng)的token敬肚、推送平臺(tái)以及其他推送相關(guān)的配置信息上傳給應(yīng)用服務(wù)器毕荐,比如使用的是華為手機(jī),就需要把華為推送的token上傳艳馒;如果是小米手機(jī)憎亚,就需要把小米推送的regid上傳,服務(wù)器根據(jù)不同的推送配置信息通過指定的推送平臺(tái)發(fā)送消息弄慰,推送平臺(tái)再利用自己的渠道下發(fā)到手機(jī)終端第美。
三,集成過程
1. AndroidManifest.xml文件的配置
SDK的添加過程可以參考文章鏈接:華為開發(fā)者聯(lián)盟>HMS>資源中心>消息推送服務(wù)>AndroidStudio開發(fā)環(huán)境陆爽,這里不再贅述
<!--*********************華為推送***Start*********************-->
<!--替換成自己申請(qǐng)到的appid-->
<meta-data android:name="com.huawei.hms.client.appid"
android:value="123456789"/>
<activity android:name="com.huawei.hms.activity.BridgeActivity"
android:configChanges="orientation|locale|screenSize|layoutDirection|fontScale"
android:excludeFromRecents="true"
android:exported="false"
android:hardwareAccelerated="true"
android:theme="@android:style/Theme.Translucent">
<meta-data android:name="hwc-theme"
android:value="androidhwext:style/Theme.Emui.Translucent" />
</activity>
<provider android:authorities="com.hhws.camerafamily360.hms.update.provider"
android:name="com.huawei.hms.update.provider.UpdateProvider"
android:exported="false"
android:grantUriPermissions="true"/>
<!--自定義廣播:接收Push消息(注冊(cè)什往、Push消息、Push連接狀態(tài)等)-->
<receiver android:name="com.vihivision.camerafamilyKey.hwpush.HuaweiPushReceiver">
<intent-filter>
<!-- 必須,用于接收TOKEN -->
<action android:name="com.huawei.android.push.intent.REGISTRATION" />
<!-- 必須慌闭,用于接收消息 -->
<action android:name="com.huawei.android.push.intent.RECEIVE" />
<!-- 可選别威,用于點(diǎn)擊通知欄或通知欄上的按鈕后觸發(fā)onEvent回調(diào) -->
<action android:name="com.huawei.android.push.intent.CLICK" />
<!-- 可選,查看PUSH通道是否連接驴剔,不查看則不需要 -->
<action android:name="com.huawei.intent.action.PUSH_STATE" />
</intent-filter>
</receiver>
<receiver android:name="com.huawei.hms.support.api.push.PushEventReceiver" >
<intent-filter>
<!-- 接收通道發(fā)來的通知欄消息兔港,兼容老版本PUSH -->
<action android:name="com.huawei.intent.action.PUSH" />
</intent-filter>
</receiver>
<!--*********************華為推送***End*********************-->
2. 自定義廣播HuaweiPushReceiver
自定義的廣播HuaweiPushReceiver必須聲明在AndroidManifest.xml文件中,HuaweiPushReceiver必須重寫4個(gè)回調(diào)方法:onToken仔拟,onPushMsg,onEvent利花,onPushState科侈,分別對(duì)應(yīng)接收token,接收透?jìng)飨⒊词拢ㄖ獧邳c(diǎn)擊事件回調(diào)臀栈,push連接狀態(tài)
package com.vihivision.camerafamilyKey.hwpush;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Bundle;
import com.huawei.hms.support.api.push.PushReceiver;
// import com.vihivision.camerafamilyKey.base.MyApplication;
import com.vihivision.camerafamilyKey.util.AXLog;
import com.vihivision.camerafamilyKey.util.NotificactionUtil;
public class HuaweiPushReceiver extends PushReceiver {
private static final String TAG = "HuaweiPushReceiver";
/**
* 連接上華為服務(wù)時(shí)回調(diào),可以獲取token值
* @param context
* @param token
* @param extras
* */
@Override
public void onToken(Context context, String token, Bundle extras) {
String belongId = extras.getString("belongId");
//MyApplication.PUST_CLIENTID_HW = token;//保存token,需要上傳到應(yīng)用服務(wù)器挠乳,以便應(yīng)用服務(wù)器根據(jù)token發(fā)送消息
String content = "華為推送get token and belongId successful, token = " + token + ",belongId = " + belongId;
AXLog.e(TAG, content);
}
/**
* 透?jìng)飨⒌幕卣{(diào)方法
* @param context
* @param msg 推送消息內(nèi)容
* @param bundle
* */
@Override
public boolean onPushMsg(Context context, byte[] msg, Bundle bundle) {
try {
String content = new String(msg, "UTF-8");
boolean bisRuning = false;
if (content != null) {
bisRuning = !MyApplication.isRunInBackground;
AXLog.e(TAG,"收到推送消息 是否后臺(tái)運(yùn)行:"+ bisRuning);
//處理透?jìng)鞯南⑷ㄊ恚梢造o默也可以通過NotificationManager來展示推送消息
NotificactionUtil.jsonPareChnnel(content,bisRuning);//自定義
AXLog.e(TAG,"alarmMessage:"+content);
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 自定義的消息的回調(diào)方法
* @param context
* @param event
* @param extras
* */
@Override
public void onEvent(Context context, PushReceiver.Event event, Bundle extras) {
AXLog.e(TAG,"event:"+event.toString()+" extras:"+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);
}
//可以對(duì)extras.getString返回的鍵值對(duì)數(shù)據(jù)進(jìn)行處理
String content = "華為推送--------receive extented notification message: " + extras.getString(BOUND_KEY.pushMsgKey);
AXLog.e(TAG, content);
}
super.onEvent(context, event, extras);
}
/**
* 連接狀態(tài)的回調(diào)方法
* @param context
* @param pushState
* */
@Override
public void onPushState(Context context, boolean pushState) {
try {
String content = "華為推送---------The current push status: " + (pushState ? "Connected" : "Disconnected");
AXLog.e(TAG, content);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3. 華為推送客戶端實(shí)例化
//DCloudApplication
public class MyApplication extends DCloudApplication {
private final static String TAG = "MyApplication";
public static Application MyAPP;
public static Context applicationContext;
//NotificationChannel ID
public final static String NOTIFICATION_CHANNEL_ID = "360";
//NotificationChannel Name
public final static String NOTIFICATION_CHANNEL_NAME = "Anxin360";
HuaweiApiClient client;
@Override
public void onCreate() {
super.onCreate();
MyAPP = this;
applicationContext = getApplicationContext();
//創(chuàng)建華為移動(dòng)服務(wù)client實(shí)例用以使用華為push服務(wù)
//需要指定api為HuaweiPush.PUSH_API
//連接回調(diào)以及連接失敗監(jiān)聽
client = new HuaweiApiClient.Builder(this)
.addApi(HuaweiPush.PUSH_API)
.addConnectionCallbacks(new HuaweiApiClient.ConnectionCallbacks() {
@Override public void onConnected() {
//華為移動(dòng)服務(wù)client連接成功,在這邊處理業(yè)務(wù)自己的事件
AXLog.i(TAG, "HuaweiApiClient 連接成功");
getTokenAsyn();
}
@Override public void onConnectionSuspended(int i) {
//HuaweiApiClient斷開連接的時(shí)候睡扬,業(yè)務(wù)可以處理自己的事件
AXLog.i(TAG, "HuaweiApiClient 連接斷開");
// client.connect();
}
})
.addOnConnectionFailedListener(new HuaweiApiClient.OnConnectionFailedListener() {
@Override
public void onConnectionFailed(ConnectionResult arg0) {
AXLog.i(TAG, "HuaweiApiClient連接失敗盟蚣,錯(cuò)誤碼:" + arg0.getErrorCode());
}
})
.build();
client.connect();
//API 26及以上需要設(shè)置NotificationChannel
NotificationManager manager = (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
notificationChannel.setBypassDnd(true);
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
notificationChannel.setShowBadge(true);
manager.createNotificationChannel(notificationChannel);
}
}
private void getTokenAsyn() {
if (!client.isConnected()) {
AXLog.e(TAG, "獲取TOKEN失敗,原因:HuaweiApiClient未連接");
return;
}
PendingResult<TokenResult> tokenResult = HuaweiPush.HuaweiPushApi.getToken(client);
tokenResult.setResultCallback(new ResultCallback<TokenResult>() {
@Override
public void onResult(TokenResult result) {
AXLog.e(TAG,"異步回調(diào)接口result:"+result.getTokenRes().getToken());
}
});
}
}
4. 測(cè)試推送
測(cè)試推送的方式有兩種卖怜,一是應(yīng)用服務(wù)器通過API向華為Push服務(wù)器發(fā)消息屎开,這種需要服務(wù)器同步開發(fā);二是通過華為開發(fā)者聯(lián)盟提供的PUSH服務(wù)來發(fā)送消息马靠。
推薦選擇第二種奄抽,因?yàn)榉?wù)器開發(fā)需要時(shí)間蔼两,另外可能有bug,如果出現(xiàn)推送問題逞度,沒辦法搞清楚究竟是哪部分的問題额划,先通過華為開發(fā)者聯(lián)盟可以把App程序調(diào)通,而后再協(xié)助服務(wù)器開發(fā)調(diào)試比較合適档泽。
使用華為開發(fā)者聯(lián)盟推送測(cè)試消息需要先獲取到手機(jī)的token锁孟,可以通過打印日志的方式把token打印出來,這個(gè)token在應(yīng)用安裝后就是固定的茁瘦,卸載重裝會(huì)重置品抽。
發(fā)送推送的方式參考鏈接:華為開發(fā)者聯(lián)盟>HMS>資源中心>消息推送服務(wù)>發(fā)送消息
四,總結(jié)
體驗(yàn)了一下甜熔,透?jìng)飨⒃趉ill應(yīng)用進(jìn)程的情況下圆恤,一次都沒有收到,前后臺(tái)存活的情況下腔稀,100%送達(dá)盆昙,速度也很快,大概延遲在2~3秒左右焊虏;如果使用通知欄消息淡喜,則應(yīng)用存活與否都能成功送達(dá),但是速度比較慢诵闭,實(shí)測(cè)的話需要5~10秒甚至更長(zhǎng)炼团。
對(duì)比iOS的APNs,Android上的推送還是非常麻煩的疏尿,開發(fā)起來也很讓人煩躁瘟芝,大概是因?yàn)榧赡敲炊嗤扑停Y(jié)果還是不能保證送達(dá)吧褥琐!國外使用的是谷歌推送锌俱,基本沒有這些問題,類似蘋果的APNs敌呈,國內(nèi)因?yàn)楦鞣N原因包括廠商定制化的原因贸宏,導(dǎo)致Android開發(fā)者很難受,不過現(xiàn)在國內(nèi)有在推進(jìn)統(tǒng)一推送聯(lián)盟磕洪,希望以后推送不要這么難搞了吭练,最新的消息看到是已經(jīng)出了一個(gè)《統(tǒng)一推送技術(shù)要求和測(cè)試方法》(2019-2),有興趣的朋友可以持續(xù)跟進(jìn)褐鸥!
參考鏈接:
[華為官方]推送服務(wù)客戶端開發(fā)指南
[華為論壇-花粉俱樂部]【獲獎(jiǎng)名單】華為推送負(fù)責(zé)人揭曉Push成功率翻倍要訣