Android卡顿优化思路
卡顿优化思路
- 卡顿原理分析
- 卡顿流程flow
- 卡顿概貌分析
- 卡顿实际数据收集
- 卡顿优化细节
卡顿原因
屏幕刷新频率高于帧率,帧率低于30
每帧执行流程
Choreographer中维护着四个队列callbacks
- 输入事件队列
- 动画队列
- 绘制队列
- app添加的frameCallback队列
vysnc信号由SurfaceFlinger中创建HWC触发,通过bitTube技术发送到目标进程,目标进程vsync信号到来时,执行Choreographer中的onVsync回调,最终触发doFrame顺序执行这四条队列中的消息。
bitTube
在linux/unix中,bitTube技术成为socketPair,它通过dup技术复制socket的句柄,传递到目标进程,开启socket的全双工通信。
句柄
在内核中,每一个进程都有一个私有的“打开文件表”,这个表是一个指针数组,每一个元素都指向一个内核的打开文件对象。而fd,就是这个表的下标。当用户打开一个文件时,内核会在内部生成一个打开文件对象,并在这个表里找到一个空项,让这一项指向生成的打开文件对象,并返回这一项的下标作为fd.
ui优化
- 多余Bg移除
- ui重叠区域优化 cancas.clipRect
- 减少ui层级
- 耗时方法分析与优化
- 多样式布局采用单一rv处理
webview优化
webview的加载流程
webiew初始化
- 目的是初始化并启动浏览器内核。
- 提前初始化webview并隐藏 优化126ms
webview 单独进程
- 单独进程 activity配置
- 单独进程的交互 webview.addJavascriptInterface(),webview.evalute()
安全性
- addJavaScriptInterface添加的java对象的方法,需要添加@addJavascriptInterface注解,避免xss攻击
卡顿收集策略
开发卡顿检测StrictMode
private void initStrictMode() {
if (isDebug()) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectCustomSlowCalls() //API等级11,使用StrictMode.noteSlowCode
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyDialog() //弹出违规提示对话框
.penaltyLog() //在Logcat 中打印违规异常信息
.penaltyFlashScreen() //API等级11
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects() //API等级11
.penaltyLog()
.penaltyDeath()
.build());
}
}
线下卡顿检测
- adb shell dumpsys gfxinfo [packagename]
Applications Graphics Acceleration Info:
Uptime: 205237819 Realtime: 436545102
** Graphics info for pid 5842 [xxxx] **
Stats since: 198741999784549ns
Total frames rendered: 653
Janky frames: 157 (24.04%)
50th percentile: 9ms
90th percentile: 34ms
95th percentile: 53ms
99th percentile: 200ms
Number Missed Vsync: 46
Number High input latency: 268
Number Slow UI thread: 76
Number Slow bitmap uploads: 3
Number Slow issue draw commands: 8
Number Frame deadline missed: 92
- 通过gpu绘制条形柱分析
条形柱共分为8种颜色,绿色和蓝色部分是异步应用能够优化的部分。包括其他处理 - 输入 - 动画 - travel
BlockCanary检测卡顿
在ActivityThread.main中的Looper大循环中,Looper.looponce会不断从消息队列中取出消息派发出去,并在前后通过logging打印了两个日志,我们通过设置自定义的logger,在两部分日志的时间差与30ms做对比,如果超过30ms,认为是卡顿。
logging.println(">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what);
msg.target.dispatchMessage(msg);
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
卡顿分析信息收集
- Debug.startMethodTracing 收集具体的卡顿方法
- 查看trace文件 根据bottomup分析具体的耗时方法
- 火焰图,横轴是调用方法耗时,纵轴是调用深度
- 调用图,调用链以及方法耗时
线上卡顿分析与收集
在ActivityThread.main中的Looper大循环中,Looper.looponce会不断从消息队列中取出消息派发出去,并在前后通过logging打印了两个日志,我们通过设置自定义的logger,在两部分日志的时间差与30ms做对比,如果超过30ms,认为是卡顿。将主线程堆栈信息写入到缓存文件并异步发送到日志后台。
常见的卡顿问题
sharepreference
- 首次读取写入会loadxml到内存
- sp文件修改是全量读写的
- commit异步写入,通过CountdownLatch阻塞等待结果
- apply延迟100ms写入,无返回结果
- 主线程ANR,sp的修改会先体现在内存中,然后往QueueWorker中加入磁盘异步写数据的任务,但是会在Activity.onResume以及Service.onstartCommand等方法中增加waitToFinish等待磁盘写入完成的代码。
- 解决方案使用MMKV
- 尽量拆分小的xml
主线程操作文件
主线程网络操作
作者:八道
链接:https://juejin.cn/post/7065128832616890398
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。