而在Android 9.0以上版本,所有Activity生命周期事务变化被合并到一个消息 EXECUTE_TRANSACTION 中
那么高版本如何判断一个消息是为了 PauseActivity呢?通过源码分析,可以发现这个Message的obj属性是一个ClientTransaction类型的对象,而该对象的mLifecycleStateRequest的getTargetState()函数返回值 标识了期望的生命周期状态
以pauseActivity为例,其实际的对象类型为 PauseActivityItem, 它的getTargetState 函数返回值为 ON_PAUSE =4。
因此,我们可以先通过判断Message what值为 EXECUTE_TRANSACTION (159), 再通过反射最终获取到 mLifecycleStateRequest 对象getTargetState函数的返回值,来判断消息是pauseActivity,还是 resumeActivity。
以下为整个流程具体的实现代码: 首先在startActivity 后,主动标记后续需要优化 启动页面的消息
class SplashActivity : AppCompatActivity() {
//...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
Log.d("MainLooperBoost", "SplashActivity onCreate")
Handler().postDelayed({
//发送3秒的耗时消息到队列中
//这里为了方便模拟,直接在主线程发送耗时任务,模拟耗时消息在 启动Activity消息之前的场景
handler.post({
Thread.sleep(3000)
Log.e("MainLooperBoost", "任务处理3000ms")
})
val intent = Intent(this, MainActivity::class.java)
Log.e("MainLooperBoost", "begin start to MainActivity")
startActivity(intent)
//标记接下来需要优化 启动Activity的相关消息
ColdLaunchBoost.getInstance().curWatchingState = WatchingState.STATE_WATCHING_START_MAIN_ACTIVITY
},1000)
}
//...
}
基于Looper消息调度监控,每次消息调度结束时,检查消息队列中的消息,判断是否存在目标消息
其中pauseActivity的Message判断逻辑为, launchActivity消息判断同理。
launchActivity消息判断同理,只是判断targetState的值不同。
修改消息顺序、优化页面跳转
修改普通消息的顺序比较简单。当遍历消息队列找到目标message后,可以修改前一个消息的next值,使其指向下一个消息,这样就从消息队列中移除了消息,之后再复制一份目标消息,重新发送到队列首部。
public boolean upgradeMessagePriority(Handler handler, MessageQueue messageQueue,
TargetMessageChecker targetMessageChecker) {
synchronized (messageQueue) {
try {
Message message = (Message) filed_mMessages.get(messageQueue);
Message preMessage = null;
while (message != null) {
if (targetMessageChecker.isTargetMessage(message)) {
// 拷贝消息
Message copy = Message.obtain(message);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
if (message.isAsynchronous()) {
copy.setAsynchronous(true);
}
}
if (preMessage != null) { //如果已经在队列首部了,则不需要优化
//当前消息的下一个消息
Message next = nextMessage(message);
setMessageNext(preMessage, next);
handler.sendMessageAtFrontOfQueue(copy);
return true;
}
return false;
}
preMessage = message;
message = nextMessage(message);
}
} catch (Exception e) {
//todo report
e.printStackTrace();
}
}
return false;
}
这里需要复制原消息是因为:在消息首次入队时会被标记为已使用,一个 isInUse 的消息无法被重新enqueue到消息队列中。
在提升mH相关消息优先级后,最新的运行日志结果如下:
此时的视频效果如下,看上去从画面上并没发生什么变化(不过生命周期函数提前了):
结合对应的日志可知,MainActivity已经执行到onResume状态,但是由于Choreographer消息被阻塞,导致MainActivity的首帧一直无法得到渲染,从界面上看,还是展示的Splash的页面。
首帧优化
接下来继续分析如何解决上面的问题,进行首帧展示优化。首先需要知道首帧绘制触发的逻辑,在Activity的launch消息处理阶段,会调用addView函数向window添加View,最终会触发requestLayou、scheduleTraversal函数,在scheduleTraversal函数中,会先设置一个消息屏障,并向Choreographer注册traversal Callback,最终在下一次vsync信号发生时,在traversalRunnable函数中进行真正的绘制流程。
在resume Activity对应的消息刚执行结束时,此时的消息队列如下所示,可以发现虽然设置了消息屏障,但是消息屏障并没有发送至队列首部,因为之前的慢消息顺序在消息屏障之前,所以vsync对应的消息依旧得不到优先执行。
因此,我们可以通过遍历消息队列,找到屏障消息 并移动至队首,这样就可以保证后续对应的异步消息优先得到执行。
具体实现代码如下: 首先我们在MainActivity的onResume阶段设置新的监听状态,标记下来需要优化 帧绘制的消息
之后,在每次消息调度结束时,尝试优化屏障消息
通过判断message的target是否为null 来找到第一个 barrier message, 之后直接反射调用 removeSyncBarrier 移除屏障消息(当然也可以通过手动操作前序消息的next指向来实现), 最后复制这个消息屏障,将其发送至队首。
实现代码如下:
/**
* 移动消息屏障至队首
*
* @param messageQueue
* @param handler
* @return
*/
public boolean upgradeBarrierMessagePriority(MessageQueue messageQueue, Handler handler) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
return false;
}
synchronized (messageQueue) {
try {
//反射获取 head Message
Message message = (Message) filed_mMessages.get(messageQueue);
if (message != null && message.getTarget() == null) {
return false;
}
while (message != null) {
if (message.getTarget() == null) { // target 为null 说明该消息为 屏障消息
Message cloneBarrier = Message.obtain(message);
removeSyncBarrier(messageQueue, message.arg1); //message.arg1 是屏障消息的 token, 后续的async消息会根据这个值进行屏障消息的移除
handler.sendMessageAtFrontOfQueue(cloneBarrier);
cloneBarrier.setTarget(null);//屏障消息的target为null,因此这里还原下
return true;
}
message = nextMessage(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
removeSyncBarrier 直接反射调用了相关函数
private boolean removeSyncBarrier(MessageQueue messageQueue, int token) {
try {
Method removeSyncBarrier = class_MessageQueue.getDeclaredMethod("removeSyncBarrier", int.class);
removeSyncBarrier.setAccessible(true);
removeSyncBarrier.invoke(messageQueue, token);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
以下是优化后的日志:
可以发现,帧绘制消息被成功优化到其他消息之前执行。并且该方案可以用于任何一个页面的首帧优化。 以下是优化后的视频效果:
从视频中可以发现,现在MainActivity的画面会在onResume函数执行结束后立即展示。 这里我设置了一个按钮,当点击按钮时,发现没有反应,这是因为首帧消息优化后,进随其后,其他消息开始正常处理,等执行到慢消息时,点击事件对应的消息就得不到响应了。
最后
今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。
最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
【算法合集】
【延伸Android必备知识点】
【Android部分高级架构视频学习资源】
Android精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!