May 14, 2019 添加更新,对于Android O(API 26)及以上的手机,如果target API ≥ 26(Android 8.0),则必须自己生成NotificationChannel,否则推送无法显示消息,8.0之前的不会受影响,系统默认生成一个NotificationChannel,使用NotificationManager.createNotificationChannel()可以生成NotificationChannel。
一,环境
华为荣耀V8 EMUI 8.0.0 Android 8.0.0
Android Studio 3.3.2
华为推送版本 2.5.2.300
SDK集成方式:
参考华为开发者联盟>HMS>资源中心>消息推送服务>集成SDK
华为推送分为新老两个版本,注意区别,新版本SDK为HMS Push,老版本切换到新版本需要更新SHA256证书指纹
二,简介
- 端内推送
App<->推送服务器
长连接,服务器下发推送消息给App进程,App进程通过NotificationManager
在通知栏进行显示,比如IM,运动跟踪之类的应用,优点是速度快,能够自行保证送达率,延时小,缺点是App进程在被系统或用户手动kill掉后,无法收到推送,一般大厂在做好端外推送的同时也会自己做端内推送,小公司就不要考虑那么多了。 - 端外推送
当App<->推送服务器
长连接断开后,推送走的就是端外推送了,因为不依赖App客户端,靠的只能是第三方推送平台提供的服务,主要分成三大类,手机厂商,专业的第三方推送,BAT的推送,比如(排名不分先后):小米,华为,个推,极光,百度云,阿里云移动,腾讯信鸽等。
作为默默无闻的小公司,端内推送开发维护成本太高,直接忽略不考虑;
专业的第三方推送反而比较适合,因为提供了方便齐全的功能,比如个推,但是个推也会因为各种原因有推送不达的情况,所以在个推基础上可以补充一点厂商的推送,至于具体选择哪个可以参考文章连接:Android 端外推送到底有多烦?(https://juejin.im/post/57a19c012e958a0066715d0c)选择合适自己方案
我选择的是个推,华为和小米,选择华为和小米的原因也很无奈,这两者市场最大,用户使用这两种手机较多,使用个推又一直有推送不达的情况,公司主营的业务又对推送的送达率有较高要求,只能集成华为和小米推送来获得更好的送达率
这里只讨论华为推送集成,但是在App初始化的时候会选择适合自己手机的推送方式,即华为手机上选择华为推送,小米手机选择小米推送,其他手机选择个推
参考文章:
[Android] 代码获取手机系统类型(小米MIUI、华为EMUI、魅族FLYME)
Android常见ROM类型识别MD
获取系统手机类型
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及以后会有Permission deny的情况,需要使用反射机制来获取
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;
}
}
根据手机系统类型不同,把对应系统的token、推送平台以及其他推送相关的配置信息上传给应用服务器,比如使用的是华为手机,就需要把华为推送的token上传;如果是小米手机,就需要把小米推送的regid上传,服务器根据不同的推送配置信息通过指定的推送平台发送消息,推送平台再利用自己的渠道下发到手机终端。
三,集成过程
1. AndroidManifest.xml文件的配置
SDK的添加过程可以参考文章链接:华为开发者联盟>HMS>资源中心>消息推送服务>AndroidStudio开发环境,这里不再赘述
<!--*********************华为推送***Start*********************-->
<!--替换成自己申请到的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消息(注册、Push消息、Push连接状态等)-->
<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" />
<!-- 可选,用于点击通知栏或通知栏上的按钮后触发onEvent回调 -->
<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>
<!-- 接收通道发来的通知栏消息,兼容老版本PUSH -->
<action android:name="com.huawei.intent.action.PUSH" />
</intent-filter>
</receiver>
<!--*********************华为推送***End*********************-->
2. 自定义广播HuaweiPushReceiver
自定义的广播HuaweiPushReceiver必须声明在AndroidManifest.xml文件中,HuaweiPushReceiver必须重写4个回调方法:onToken,onPushMsg,onEvent,onPushState,分别对应接收token,接收透传消息,通知栏点击事件回调,push连接状态
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";
/**
* 连接上华为服务时回调,可以获取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,需要上传到应用服务器,以便应用服务器根据token发送消息
String content = "华为推送get token and belongId successful, token = " + token + ",belongId = " + belongId;
AXLog.e(TAG, content);
}
/**
* 透传消息的回调方法
* @param context
* @param msg 推送消息内容
* @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,"收到推送消息 是否后台运行:"+ bisRuning);
//处理透传的消息,可以静默也可以通过NotificationManager来展示推送消息
NotificactionUtil.jsonPareChnnel(content,bisRuning);//自定义
AXLog.e(TAG,"alarmMessage:"+content);
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 自定义的消息的回调方法
* @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);
}
//可以对extras.getString返回的键值对数据进行处理
String content = "华为推送--------receive extented notification message: " + extras.getString(BOUND_KEY.pushMsgKey);
AXLog.e(TAG, content);
}
super.onEvent(context, event, extras);
}
/**
* 连接状态的回调方法
* @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. 华为推送客户端实例化
//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();
//创建华为移动服务client实例用以使用华为push服务
//需要指定api为HuaweiPush.PUSH_API
//连接回调以及连接失败监听
client = new HuaweiApiClient.Builder(this)
.addApi(HuaweiPush.PUSH_API)
.addConnectionCallbacks(new HuaweiApiClient.ConnectionCallbacks() {
@Override public void onConnected() {
//华为移动服务client连接成功,在这边处理业务自己的事件
AXLog.i(TAG, "HuaweiApiClient 连接成功");
getTokenAsyn();
}
@Override public void onConnectionSuspended(int i) {
//HuaweiApiClient断开连接的时候,业务可以处理自己的事件
AXLog.i(TAG, "HuaweiApiClient 连接断开");
// client.connect();
}
})
.addOnConnectionFailedListener(new HuaweiApiClient.OnConnectionFailedListener() {
@Override
public void onConnectionFailed(ConnectionResult arg0) {
AXLog.i(TAG, "HuaweiApiClient连接失败,错误码:" + arg0.getErrorCode());
}
})
.build();
client.connect();
//API 26及以上需要设置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,"异步回调接口result:"+result.getTokenRes().getToken());
}
});
}
}
4. 测试推送
测试推送的方式有两种,一是应用服务器通过API向华为Push服务器发消息,这种需要服务器同步开发;二是通过华为开发者联盟提供的PUSH服务来发送消息。
推荐选择第二种,因为服务器开发需要时间,另外可能有bug,如果出现推送问题,没办法搞清楚究竟是哪部分的问题,先通过华为开发者联盟可以把App程序调通,而后再协助服务器开发调试比较合适。
使用华为开发者联盟推送测试消息需要先获取到手机的token,可以通过打印日志的方式把token打印出来,这个token在应用安装后就是固定的,卸载重装会重置。
发送推送的方式参考链接:华为开发者联盟>HMS>资源中心>消息推送服务>发送消息
四,总结
体验了一下,透传消息在kill应用进程的情况下,一次都没有收到,前后台存活的情况下,100%送达,速度也很快,大概延迟在2~3秒左右;如果使用通知栏消息,则应用存活与否都能成功送达,但是速度比较慢,实测的话需要5~10秒甚至更长。
对比iOS的APNs,Android上的推送还是非常麻烦的,开发起来也很让人烦躁,大概是因为集成那么多推送,结果还是不能保证送达吧!国外使用的是谷歌推送,基本没有这些问题,类似苹果的APNs,国内因为各种原因包括厂商定制化的原因,导致Android开发者很难受,不过现在国内有在推进统一推送联盟,希望以后推送不要这么难搞了,最新的消息看到是已经出了一个《统一推送技术要求和测试方法》(2019-2),有兴趣的朋友可以持续跟进!
参考链接:
[华为官方]推送服务客户端开发指南
[华为论坛-花粉俱乐部]【获奖名单】华为推送负责人揭晓Push成功率翻倍要诀