最近又对接了一次友盟推送,但是没有之前的那么顺利,因为在华为P30手机上出现了之前没见过的现象。现象就是应用第一次安装到手机上大概率获取不到devicetoken。第二次启动应用后才能获取到token。而且手机在充电情况下是能正常获取的,不充电情况很难获取。也就是调用pushAgent.register()方法,没有任何回调。查看日志和友盟的源码之后发现了端倪。在了解了友盟的注册流程和代码原来之后,我根据其中逻辑建立了新的流程,能够解决刚才说的第一次启动获取不到devicetoken的问题。下面的内容就是探究和解决这个问题的过程。
先说实现方案:
在
pushAgent.register(new UPushRegisterCallback() {
@Override
public void onSuccess(String s) {
String deviceToken = s;
}
@Override
public void onFailure(String s, String s1) {
}
});
后面增加这段代码:
new Timer().schedule(new TimerTask() {
@Override
public void run() {
String deviceToken = Config.f(context);
}
}, 0, 300);
//这段只是示例用循环来反复读取 Config.f(context),具体循环速度、次数、判空、回收资源等细节需自行把握。
-----下面开始探究之路------
顺着pushAgent.register()源码跟踪进去,进入到一个ak.class文件。主要注册逻辑在一个叫做c的私有方法中。下面是该方法中的部分代码:
y.b(new Runnable() {
public void run() {
try {
UPLog.i("", new Object[]{"appkey:" + var2, " secret:" + var3});
TaobaoRegister.register(var1, "default", "umeng:" + var2, var3, "android@umeng", new IRegister() {
public void onSuccess(String var1x) {
UPLog.i("", new Object[]{"register success. deviceToken:" + var1x});
ak.this.a(var1x);
}
public void onFailure(String var1x, String var2x) {
UPLog.e("", new Object[]{"register failed! code:" + var1x + ", desc:" + var2x});
ak.this.a(var1x, var2x);
UMLog.aq(ao.a, 0, "\\|");
}
});
} catch (Throwable var2x) {
UPLog.e("", new Object[]{"register failed:" + var2x.getMessage()});
}
}
});
能看到这段代码调用了TaobaoRegister.register来注册,注册成功后会调用ak文件下的a方法:
private void a(String var1) {
......
UMJobIntentService.enqueueWork(am.b(), UmengMessageCallbackHandlerService.class, var2);
}
a方法又启动执行了一个叫做UmengMessageCallbackHandlerService的JobIntentService,下面是UmengMessageCallbackHandlerService中的相关代码:
final Context var2 = am.b();
if (var2 == null) {
UPLog.i("MsgCallback", new Object[]{"context null!"});
} else {
String var3 = UMGlobalContext.getInstance(var2).getProcessName(var2);
UPLog.i("MsgCallback", new Object[]{"processName:", var3});
if (var1 != null && var1.getAction() != null) {
UPLog.i("MsgCallback", new Object[]{"action:", var1.getAction()});
String var4 = var1.getAction();
byte var5 = -1;
...........
final String var6;
String var9;
boolean var17;
UPushSettingCallback var20;
String var23;
switch(var5) {
case 0:
try {
var6 = var1.getStringExtra("registration_id");
boolean var22 = var1.getBooleanExtra("status", false);
UPLog.i("MsgCallback", new Object[]{"u-push regId:", var6, ", status:" + var22});
final UPushRegisterCallback var24 = PushAgent.getInstance(var2).getRegisterCallback();
if (var22) {
y.b(new Runnable() {
public void run() {
String var1 = "";
try {
MessageSharedPrefs var2x = MessageSharedPrefs.getInstance(var2);
var1 = var2x.getDeviceToken();
if (var6 != null && var1 != null && !var6.equals(var1)) {
var2x.setRegistered(false);
var2x.setDeviceToken(var6);
var2.getContentResolver().delete(aw.e, (String)null, (String[])null);
var2x.resetTags();
}
} catch (Exception var3) {
}
if (var24 != null) {
var24.onSuccess(var6);
}
an.a().a(var1);
PushAgent.getInstance(var2).onAppStart();
}
});
} else if (var24 != null) {
var9 = var1.getStringExtra("s");
String var10 = var1.getStringExtra("s1");
var24.onFailure(var9, var10);
}
} catch (Throwable var16) {
var16.printStackTrace();
}
break;
.......
能看到UmengMessageCallbackHandlerService中的代码,如果注册成功获取到devicetoken之后,会有一系列操作,包括保存token,设置状态,调用成功的回调方法等等。根据日志能观察到,如果注册成功了,日志会输出MsgCallback u-push regId:xxxxxxxx,也就是输出devicetoken。
这一套流程就是,友盟调用TaobaoRegister.register()方法去注册,如果TaobaoRegister.register注册成功了会执行JobIntentService来发送注册成功的广播。对比了日志后发现,在出问题的情况,也就是收不到token 的时候,ak文件的a方法中
UMJobIntentService.enqueueWork(am.b(), UmengMessageCallbackHandlerService.class, var2);
这行代码正常情况下会启动UmengMessageCallbackHandlerService,并执行里面的回调,再去回调前面pushAgent的register的成功回调。但是这个Service不一定会如约启动,经过观察对比,如果手机没充着电,90%以上概率不会启动这个服务。具体原因还需要再研究,但是现象已经观测出来了。
总结流程:
1.pushAgent.register
2.TaobaoRegister.register
3.回调ak.a()
4.启动UmengMessageCallbackHandlerService
5.回调UPushRegisterCallback的onSuccess
6.获取到devicetoken
现在就是在第4步失败,不能启动Service。所以解决方案就在第4步之前去找。
关注第4步之前的TaobaoRegister.register,进到TaobaoRegister.register内部查看源码:
public static synchronized void register(Context var0, String var1, String var2, String var3, String var4, IRegister var5) throws AccsException {
.......
com.taobao.accs.b var9 = ACCSManager.getAccsInstance(var0, var2, var1);
var9.a(var7, var2, var3, var4, new h(var7, var0, var5, var2, var4, var9));
......
}
var5就是注册结果响应的callback,var5被用来创建了一个h对象。进到h类中查看源码,var5传进去变成了h类的c参数,在onBindApp的方法中被调用:
public void onBindApp(int var1, String var2) {
......
if (this.c != null) {
this.c.onSuccess(Config.f(this.a));
}
......
}
看到这里有个Config.f(this.a),也就是这个方法的返回值就是devicetoken,就是把它经过刚才的好几个步骤回调回去才是注册成功。然后查看一下这个类是个public的,方法也是public,所以通过这个方法就可以在第4步之前,实际上第2步就拿到devicetoken。
这时候只需要在pushAgent.register注册之后做一个循环,反复调用Config.f(context)方法,只到返回有值的时候就认为获取到了token。经过多次测试,最多在5秒就可以获取到。要记得,这一系列过程只是解决了应用第一次安装时没有token的问题,并不是每次启动应用都是需要最多5秒来获取token。
其中service为什么只在充电时候启动的原理还没有深入研究。初步检查源码应该是和电源管理,进程调度有关,需要再花时间研究一下。`