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开发环境,这里不再赘述





    




    
        
        
        
        
        
        
        
        
    


    
        
        
    


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 = HuaweiPush.HuaweiPushApi.getToken(client);
        tokenResult.setResultCallback(new ResultCallback() {
            @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成功率翻倍要诀

你可能感兴趣的:(2019-05-10 简单集成华为推送(老版本Push SDK))