一、问题背景:
问题视频如下,系统导航栏出现时会卡一下
1、MIUI12.5版本,片多多在播放视频时点击屏幕弹出海报信息时,会明显感觉卡顿一下。
2、MIUI12.0版本,同样版本片多多app却无此问题。其他如huawei机型也无此问题
二、需要梳理的问题:
1、为什么同一手机rom版本不同,但app相同的情况下会出现卡一下
2、为什么只在点击屏幕弹出影片信息的时候才会卡一下
3、卡顿根因是什么
三、先说结论
1、为什么同一手机rom版本不同,但app相同的情况下会出现卡一下
→MIUI12.0是60hz刷新率,MIUI12.5是90hz刷新率,帧率的提升让原本掉帧不明显的问题暴露了出来。三星S20+ 120hz,测试的手机OPPO Reno5 90hz也存在同样问题
2、为什么只在点击屏幕弹出影片信息的时候才会卡一下
-->根据版本排查发现,UIUtils类调用显示导航栏的时候,会调用view.setSystemUiVisibility,导致卡顿。这里会引出一个问题,为什么这个方法的调用会导致卡顿,也就是卡顿根因
3、卡顿根因是什么
-->调用显示导航栏view.setSystemUiVisibility会给ViewRootImpl的mWindowAttributes设置SystemUIVisibility,再v-sync信号来之后执行的ViewRootImpl performTraversals进一步调用到
controlInsetsForCompatibility,基于此方法后续最终调用到notifyInsetsChanged,这是此问题出现调用的关键方法:
if (View.sForceLayoutWhenInsetsChanged && mView != null) { forceLayout(mView); }
代码片段会强制所有View做forceLayout。换句话说,显示或隐藏导航栏系统会强制整个布局树重新布局,即使他不需要。这段强制布局最终会导致performTraversals的layout耗时过长,具体见后续详细分析。
但这个代码片段只在Android11上有,之前的版本没有此代码,所以Android11之前不会有此问题
同时,命中条件如下:
View.sForceLayoutWhenInsetsChanged = targetSdkVersion < Build.VERSION_CODES.R;
由此Android11之后的版本也不会有此问题,问题仅仅在Android11的系统发生。查了当前出现问题的机型,都是Android11
四、详细分析过程:
由于升级后,没有MIUI12.0版本,使用huawei手机代替对比测试。
添加了自定义trace,抓取huawei DUB-AL00同样操作, trace进行分析正常trace:
应用UI线程做binder和layout的时候耗时
对应CPU也吃满了一个核
huawei trace看这个操作掉了4帧,未产生明显的卡顿现象。
帧率60hz。
正常版本其实也发生了掉帧,但掉帧不多,所以现象上并没有明显卡顿。
同时,抓取MIUI12.5卡顿trace:
主线程layout耗时严重,进一步看cpu
cpu也吃满,也没有因为上下文切换导致耗时,进一步看掉帧情况
MIUI12.5帧率提到90hz的刷新率。掉帧迅速上升到10+,发生明显卡顿感。
经过导师支持,通过逆向排查到版本1.6.5开始出现,具体代码是UIUtils.java
public static void showNavigation(View view) { ... view.setSystemUiVisibility(uiOptions);//调用此方法导致,去掉不在卡顿 }
通过跟踪源码并没有直接定位到此方法调用与卡顿的直接联系,于是添加大量的自定义trace进行分析,如下:
主/副标题以及播放时间的文本度量在这一次layout中做 了800+多次,这也是布局嵌套过深产生的负面影响。
基于此,需要找到这些文本被调用如此多的原因。在主标题中加入调试代码isLayoutRequested与requestLayout,发现requestLayout并未调用。
可见,并不是由控件主动发起的重布局请求,追溯源码发现还有可能是调用了forceLayout导致重布局请求,于是打印主标题的调用栈:
发现是ViewRootImpl发起的强制重布局,再进一步分析源码,发现此次强制重布局是因为SystemUI可见性变化
view.setSystemUiVisibility -> ViewRootImpl.recomputeViewAttributes -> mAttachInfo.mRecomputeGlobalAttributes = true ViewRootImpl.performTraversals → collectViewAttributes -> controlInsetsForCompatibility → .. → notifyInsetsChanged → forceLayout
从上述调用栈便解释了为什么只在点击屏幕弹出影片信息的时候才会卡一下
五、解决方案
1)复写TextView的onMeasure,不调用父类的onMeasure,从trace看此方法耗时量级在毫秒,加上调用次数极多,可直接
调用setMeasuredDimension设置固定宽高,而不在走父类度量方法。
缺点:该自定义布局必须固定宽高,且不在调用父类度量方法 2)复写主副标题以及当前时间的forceLayout,不调用父类方法,避免被系统强制布局。查询源码发现,调用此方法的大部分是
TableLayout/ListView/AbsListView,而咱们的主副标题以及当前时间都不在这些容器里面。可以直接复写使用。
当前采用方案2进行解决。卡顿那一帧从103ms掉10帧 缩短到 40ms 只掉 3帧,且无卡顿感觉
在发现的问题机上都通过测试。
六、演进
高刷新率(90hz/120hz)下,对布局层次的要求会越来越高,一次相对布局的度量会对子view进行两次onMeasure,后续可以考虑进一步优化布局以获得更好地刷新体验
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。