其他
船新版本之学习屏幕刷新机制引发的画面卡顿监控与优化的思考
https://juejin.cn/user/263492889751640/posts
第一章 屏幕的刷新机制 第二章 System Trace和Java/Kotlin Method Trace的使用体验 第三章 基于性能分析的简单优化理解 第四章 未来的展望与惋惜
既然是系统发送vsync信号,我更新UI的时候怎么知道vsync什么时候来? 画面不变的时候vsync来了我需要做什么吗,告诉它快点走,我不更新UI了?
it.invalidate()
}
// 简单解释一下,点击textview调用了自身的invalidate方法
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
}
// mTraversalScheduled标识防止一帧内多次调用scheduleTraversals,上屏是个整体操作
// postSyncBarrier()同步屏障,优先执行异步消息
// mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);发送CALLBACK_TRAVERSAL类型的消息进入队列并且对底层进行监听,待底层返回vsync刷新信号
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
performTraversals();
}
}
// vsync信号上来了,mTraversalScheduled标识重新置为false,为下一轮屏幕刷新服务
// removeSyncBarrier()移除同步屏障,执行同步消息
// performTraversals()屏幕刷新测量、布局、绘制三大流程
private void performTraversals() {
......
performMeasure() 测量
......
perfromLayout() 布局
......
performDraw() 绘制
......
}
// 根据一些状态标识位判断是否需要执行测量、布局、绘制三大流程
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
// checkThread()涉及到了一些onCreate函数时机更新UI,和子线程使用wm更新UI的骚操作
// mLayoutRequested更新布局标签
// scheduleTraversals()因此最后还是走到了上面分析的流程
if (view == null) {
return
}
// 循环播放放大缩小动画
val scaleUpX = ObjectAnimator.ofFloat(view, "scaleX", 1.0f, 1.5f)
val scaleUpY = ObjectAnimator.ofFloat(view, "scaleY", 1.0f, 1.5f)
val scaleDownX = ObjectAnimator.ofFloat(view, "scaleX", 1.5f, 1.0f)
val scaleDownY = ObjectAnimator.ofFloat(view, "scaleY", 1.5f, 1.0f)
val scaleUp = AnimatorSet()
scaleUp.playTogether(scaleUpX, scaleUpY)
scaleUp.setDuration(500)
val scaleDown = AnimatorSet()
scaleDown.playTogether(scaleDownX, scaleDownY)
scaleDown.setDuration(500)
val animatorSet = AnimatorSet()
animatorSet.playSequentially(scaleUp, scaleDown)
animatorSet.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {}
override fun onAnimationEnd(animation: Animator) {
animation.start()
}
override fun onAnimationCancel(animation: Animator) {}
override fun onAnimationRepeat(animation: Animator) {}
})
animatorSet.start()
}
private fun test() {
anima(binding?.tvTest1)
binding?.tvTest1?.setOnClickListener {
test1()
}
}
private fun test1() {
Thread.sleep(1000)
}
// anima()循环播放动画
// 点击view就睡眠1s,也可以模拟成主线程的耗时操作
选择需要分析的应用程序,点击CPU准备Record, All Frames代表所有帧,JankyFrames代表丢掉的帧, 可以很明显看出来,点击后出现了一个卡顿帧,页面沉睡了1000ms,正好卡帧了1s。
可以看到橙色是系统API,绿色是自己写的函数test1,也就是后面我们能优化的函数,蓝色是三方API Flame Chart?帧记录表?Flame Chart提供了调用栈的反向调用图,Flame Chart中的水平条表示出现在相同的调用序列中同一方法的执行时间,从图中我们很容易发现哪个方法消耗的时间最多 可以看到onClick这条是比较长的,几乎占了一半,鼠标移动到上面能够发现也是1s
手机很差(好像只要足够好,画面就不可能卡,卡就加机器!) 代码很差(代码不可能有问题,是Java的问题)
Thread {
Thread.sleep(1000)
}.start()
}
// 将耗时操作交给线程去处理,拿到操作的结果后再交给主线程使用
// 恭喜,这个方法至少堵住了主线程10ms+10ms的时间,如果一个页面100个请求呢?
// 我们还在疑惑为什么每次跳转这个页面都会卡一下,我明明用了rxjava啊,都是异步请求了,难道是手机不行?
// 嘻嘻嘻嘻嘻嘻
val userPhone = 协程版UserUtils.getUserPhone()
MainHttp.getData(userId, userPhone).subscribe() // 我都协程了怎么还是rxjava请求网络,不会这个人不会写吧(bushi
// 顶多1ms+1ms
for (index in 0..1000) {
val tempTextView = TextView(this)
tempTextView.text = "我在放大缩小哦"
tempTextView.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
binding?.llBg?.addView(tempTextView)
anima(tempTextView)
}
// 1000个你说肉眼不卡?好好好?那这样呢
// tempTextView.postDelayed({ test2() }, 100)
}
开启方法Trace后,函数运行效率大大降低,能作为参考和排查问题的思路,得不到比较真实的优化数据。该问题的解决方法应该是采用插桩之类的方案,对方法进行计时,发起技术埋点,进行上报,进行标准的制定,根据标准控制卡顿代码。 debug环境下的优化始终算事前,并做不到线上的监控分析,该问题的解决方法应该是采用类似于watchdog方案监听线上的函数执行情况。