这是我参与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
面向搜索引擎编程的我各种搜索,有的说是没有链接上华为移动服务,有的说是bks证书不对,什么升级手机上的华为服务,各种方法都尝试过都是没有效果。
我意识到问题和大部分的人不同,因为我们项目与华为健康有合作,所以还接入了华为登录,华为健康等华为系SDK,所以自己开始尝试解决。
附上我的SDK版本
友盟其他的SDK都是最新的,其中华为厂商推送版本为
implementation 'com.huawei.hms:push:5.1.1.301'
implementation 'com.umeng.umsdk:huawei-umengaccs:1.3.1'
定位问题
-
单独接入厂商推送 因为我友盟退出厂商推送这个功能的时候,我就接入过,而且过程页比较顺利,这次再接入,我预计1个小时完成。但是却出了问题。于是我新建了一个项目,包名已经其他项目保持与主项目一致,初始化华为厂商推送失败成功
-
在上面的基础上,引入华为登录,华为健康SDK。初始化华为厂商推送失败
猜测:1.厂商推送本身没有问题,但是与华为其他SDK一起接入时,会出现问题。
猜测:2.华为推送SDK与华为其他SDK一起接入时,会出现问题。
于是我去友盟,华为分别进行技术咨询:
- 友盟回复:请看文档XXX,如果还不行再看文档xxx,最后给的回复是:我们没有问题,报错是华为的异常 根据华为的技术要求提供了一些日志,他们看完之后回复
- 华为回复:项目生成的bks证书校对失败,让我尝试单独接入华为系SDK
- 不接入友盟的华为厂商推送,只安卓华为文档结束华为的推送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'的依赖删除。
项目中对于华为厂商推送的结构如下
在进行初始化的时候,使用自己的方法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
}
}