友盟厂商推送华为获取Token报错:ApiException: 907135003,bks证书检验失败

1,962 阅读8分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

之前的学习记录都是在自己的本地,借着掘金的这次8月活动的机会,也挑战一下自己。各位看官点点赞,小弟先谢过。

问题出现:

项目需要使用接入友盟厂商推送,在其他厂商都调试成功了,在测试华为厂商推送的时候,初始化一直报错HuaWeiRegister: getToken failed.ApiException: 907135003: client api invalid

I/NAccs.HuaWeiRegister: register begin isChannel:false
I/NAccs.HuaWeiRegister: onToken appId:103885137
E/HuaWeiRegister: getToken failed.
com.huawei.hms.common.ApiException: 907135003: client api invalid

1.png 面向搜索引擎编程的我各种搜索,有的说是没有链接上华为移动服务,有的说是bks证书不对,什么升级手机上的华为服务,各种方法都尝试过都是没有效果。

我意识到问题和大部分的人不同,因为我们项目与华为健康有合作,所以还接入了华为登录,华为健康等华为系SDK,所以自己开始尝试解决。

附上我的SDK版本

友盟其他的SDK都是最新的,其中华为厂商推送版本为

 implementation 'com.huawei.hms:push:5.1.1.301'
 implementation 'com.umeng.umsdk:huawei-umengaccs:1.3.1'

定位问题

  1. 单独接入厂商推送 因为我友盟退出厂商推送这个功能的时候,我就接入过,而且过程页比较顺利,这次再接入,我预计1个小时完成。但是却出了问题。于是我新建了一个项目,包名已经其他项目保持与主项目一致,初始化华为厂商推送失败成功

  2. 在上面的基础上,引入华为登录,华为健康SDK。初始化华为厂商推送失败

猜测:1.厂商推送本身没有问题,但是与华为其他SDK一起接入时,会出现问题。
猜测:2.华为推送SDK与华为其他SDK一起接入时,会出现问题。

于是我去友盟,华为分别进行技术咨询:

  • 友盟回复:请看文档XXX,如果还不行再看文档xxx,最后给的回复是:我们没有问题,报错是华为的异常 根据华为的技术要求提供了一些日志,他们看完之后回复
  • 华为回复:项目生成的bks证书校对失败,让我尝试单独接入华为系SDK
  1. 不接入友盟的华为厂商推送,只安卓华为文档结束华为的推送SDK。在初始化的时候,成功的获取到了华为推送token,接入成功

于是排除华为系SDK内部导致bks证书可能,那么就只有一个情况:

友盟厂商推送的华为渠道SDK,在项目中 如果同时还接入了华为的其他SDK,则会导致项目的bks证书出现问题,以至于连接不上华为服务,进而获取不到华为推送token

于是再次建立友盟工单,与技术沟通,再次得到简单的回复:

友盟再次回复:我们的华为渠道SDK没有问题!

。。。。我心里都真是XXOO,单独接入华为没问题,接入友盟的华为厂商推送bks证书就不对,还没问题么?除了会提供几个不是文档地址让我对着看。。就是我们没问题。。而且更奇怪的是,友盟的官网文档是很久很久以前的,按着官网文档接入现在会出问题,但是你一把问题给客服一说,他们立马发另一个文档地址给你。。那既然你们知道官网文档有问题也有新的解决文档。。你们就不可以把官网的文档更新一下么。哎,还是自己来吧

分析问题

算了,深呼吸,还是我自己再尝试解决吧。现在的情况是,我按照华为的推送文档,可以拿到推送token,其实已经可以离线推送成功了。但是我们想的肯定是后端只需要在友盟后台触发推送就好,而不是友盟发一次,华为再发一次。

思考:

友盟集成厂商推送,我们还是需要在各大厂商注册账号,获取参数,所以可以肯定,友盟也只是封装了一层,让使用者只需要在他们那边填好参数,触发消息推送,然后友盟内部再触发各自的厂商推送。那么问题来了,友盟是如何封装的这一层呢? 我们可以看到友盟的华为推送要求依赖2个库,一个是和华为自己推送的SDK一模一样,

'com.huawei.hms:push:5.1.1.301'

另一个是友盟自己SDK

'com.umeng.umsdk:huawei-umengaccs:1.3.1'

包括其他几个渠道的依赖方式也是类似的。所以我在想友盟会不会是先拿着各自渠道的token,然后注册到自己的推送中,用户触发友盟推送,友盟在再触发各自的厂商推送。

为了验证这个猜测,我去观察了5个厂商的推送初始化日志,他们有一个共同点:

先是厂商自己的推送注册成功的日志,然后再是友盟的注册成功日志。

那解决问题的方案就来了:我现在是能获取到华为的token,那我不使用友盟的华为厂商SDK,我自己将华为的token,注册到友盟推送里不就好了。

解决问题

友盟华为渠道的初始化就一行代码

HuaWeiRegister.register(application)

好在内部没有混淆,可以很清楚的看到逻辑

 org.android.agoo.huawei.HuaWeiRegister
         private static void getToken(final Context context) {
        ThreadPoolExecutorFactory.execute(new Runnable() {
            public void run() {
                try {
                    ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 128);
                    String value = appInfo.metaData.getString("com.huawei.hms.client.appid");
                    String appId = "";
                    if (!TextUtils.isEmpty(value)) {
                        appId = value.replace("appid=", "");
                    }

                    ALog.i("HuaWeiRegister", "onToken", new Object[]{"appId", appId});
                    String token;
                    关键代码获取华为推送token
                    if (TextUtils.isEmpty(appId)) {
                        token = HmsInstanceId.getInstance(context).getToken();
                    } else {
                        token = HmsInstanceId.getInstance(context).getToken(appId, "HCM");
                    }

                    if (!TextUtils.isEmpty(token)) {
                        ALog.i("HuaWeiRegister", "onToken", new Object[]{"token", token});
                        NotifManager notifyManager = new NotifManager();
                        notifyManager.init(context);
                        上报第三方token到友盟内部
                        notifyManager.reportThirdPushToken(token, "HW_TOKEN");
                    }
                } catch (Exception var6) {
                    Log.e("HuaWeiRegister", "getToken failed.", var6);
                }

            }
        });
    }

其他代码就不贴了,只看关键方法getToken

因为刚刚单独接入过华为推送SDK,所以我看到token = HmsInstanceId.getInstance(context).getToken(appId, "HCM");这行代码时我很兴奋,因为这个代码就是华为的token的获取方式,在友盟的SDK里再次出现也证明了我的猜测,所以只需要找到友盟是怎么拿着这么token注册到自己内部就好。 下面有一行代码已经很明显了reportThirdPushToken上报第三方推送token。

所以:我们只需要拿着我们已有的华为token,然后自己调用友盟的上报方法即可。

于是我项目里,不引用'com.umeng.umsdk:huawei-umengaccs:1.3.1',自己照着友盟的HuaWeiRegister类重新封装一个类,负责把华为的推送token注册到友盟中。同时我们还缺少一个HuaweiPushMessageService这个类我们只需要从友盟的华为推送SDK里复制出来就好。 然后把项目里对'com.umeng.umsdk:huawei-umengaccs:1.3.1'的依赖删除。 项目中对于华为厂商推送的结构如下

1629011180(1).png

在进行初始化的时候,使用自己的方法HuaWeiPushManager.getInstance.register(application)去替代友盟的初始化代码HuaWeiRegister.register(application)。代码如下

    /**
     * 厂商推送
     * 根据官方文档推荐,在app收个activity的onCreate中注册
     * 接OPPO产商推送,买一送二(onePlus realme)
     */
    private fun registerDeviceChannel(application: Application) {
        //小米推送
        MiPushRegistar.register(
            application,
            UMENG_XIAOMI_ID,
            UMENG_XIAOMI_KEY
        )
        //华为推送
        //HuaWeiRegister.register(application)
        HuaWeiPushManager.getInstance.register(application)
        //OPPO推送
        OppoRegister.register(
            application,
            UMENG_OPPO_KEY,
            UMENG_OPPO_SECRET
        )
        //VIVO推送
        VivoRegister.register(application)
    }

然后重新运行项目,获取华为token成功,友盟厂商推送注册成功,友盟后台触发离线推送,华为手机在关闭APP后,也能正常收到推送。问题得以解决

后语

总算解决了这个问题,心里松了一口气,同时把这个情况分别告诉了华为的技术和友盟的技术。如果以后还有搬砖的反馈这种问题,他们也能有点印象。当然还是希望友盟能尽快解决这个问题。 分别和华为和友盟技术沟通后,心里感慨:华为牛逼,友盟。。呵呵

然后也看了友盟封装的华为推送SDK的代码,确实比较简单,怪不得他们一直很自信的说自己没有问题。毕竟没几行代码。我也想不明白问题出在哪里,但是可以肯定'com.umeng.umsdk:huawei-umengaccs:1.3.1' 这个SDK有问题。

封装后的代码

最后附我封装后的类

import android.app.Application
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.text.TextUtils
import com.huawei.agconnect.config.AGConnectServicesConfig
import com.huawei.hms.aaid.HmsInstanceId
import com.huawei.hms.common.ApiException
import org.android.agoo.control.NotifManager
import timber.log.Timber

/**
 * @author : ZhaoYun
 * @desc   : 自己稍微封装一下友盟的华为初始化(代替'com.umeng.umsdk:huawei-umengaccs:1.3.1')
 */
class HuaWeiPushManager {

    companion object {
        val getInstance: HuaWeiPushManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            HuaWeiPushManager()
        }
    }

    fun register(application: Application) {
        if (checkDevice() && Build.VERSION.SDK_INT >= 17) {
            Timber.i("MobiUMengManager  HuaWei register")
            getHuaWeiToken(application)
        } else {
            Timber.i("MobiUMengManager  HuaWei register: register not in main process, return")
        }
    }

    /**
     * 华为华为推送token,向友盟注册华为推送
     */
    private fun getHuaWeiToken(application: Application) {
        // 创建一个新线程
        object : Thread() {
            override fun run() {
                try {
                    // 从agconnect-service.json文件中读取appId
                    val appId =
                        AGConnectServicesConfig.fromContext(application).getString("client/app_id")
                    // 输入token标识"HCM"
                    val tokenScope = "HCM"
                    val token = HmsInstanceId.getInstance(application).getToken(appId, tokenScope)
                    Timber.i("MobiUMengManager  HuaWei get token:$token")

                    val handler = Handler(Looper.getMainLooper())
                    handler.postDelayed({
                        val notifyManager = NotifManager()
                        notifyManager.init(application)
                        notifyManager.reportThirdPushToken(token, "HW_TOKEN")
                        Timber.i("MobiUMengManager  HuaWei registe token:$token")
                    }, 5000L)
                    // 判断token是否为空
                    if (!TextUtils.isEmpty(token)) {
                        sendRegTokenToServer(token)

                    }
                } catch (e: ApiException) {
                    Timber.i("MobiUMengManager  HuaWei get token failed, $e")
                }
            }
        }.start()
    }

    private fun sendRegTokenToServer(token: String?) {
        Timber.i("MobiUMengManager  HuaWei sending token to server. token:$token")
    }

    /**
     * 检查是是否为华为设备
     */
    private fun checkDevice(): Boolean {
        var result = false
        if (Build.BRAND.equals(
                "huawei",
                ignoreCase = true
            ) || Build.BRAND.equals("honor", ignoreCase = true)
        ) {
            result = true
        }
        return result
    }
}