2019-05-10 简单集成华为推送(老版本Push SDK)

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证书指纹

二,简介

  1. 端内推送
    App<->推送服务器长连接,服务器下发推送消息给App进程,App进程通过NotificationManager在通知栏进行显示,比如IM,运动跟踪之类的应用,优点是速度快,能够自行保证送达率,延时小,缺点是App进程在被系统或用户手动kill掉后,无法收到推送,一般大厂在做好端外推送的同时也会自己做端内推送,小公司就不要考虑那么多了。
  2. 端外推送
    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成功率翻倍要诀

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容