- 绘制
- 刷新
- 冷启动
- 温启动
- 热启动
- 页面间切换
- 前后台切换
- 按键
- 系统事件
- 滑动
- 绘制层级太深
- 页面复杂 控件个数>80
- 刷新不合理
- 在UI主线程处理数据
- 数据处理占用CPU高,导致主线程拿不到时间片
- 内存增加导致GC频繁而引起的卡顿
- 应用层负责绘制
- 系统层负责渲染
- C/S架构
- SurfaceFlinger(C++)
1. Java API
2. C++底层具体实现
1. Measure
深度优先
先遍历子节点,再遍历兄弟节点
广度优先
先遍历兄弟节点,在遍历子节点
2. Layout
深度优先
3. Draw
软件绘制
硬件加速(GPU)
比CPU耗电
兼容问题
内存大
1. 主要工作
- 响应客户端事件,创建Layer与客户端的Surface建立联系
- 接收客户端数据及属性,修改Layer属性,如尺寸,颜色,透明度等
- 将创建的Layer内容刷新到屏幕上
- 维持Layer的序列,并对Layer最终输出做出裁剪计算
2. SharedClient - SharedBufferStack 1 : 31 匿名共享内存
3. SharedBufferStack 包含了N个缓冲区
< 4.1 N = 2
> 4.1 N = 3
4. FPS 60 16ms
5. 刷新机制
双缓冲
VSYNC
Choreographer
Callback_input
优先级最高,与输入事件有关
Callback_animation
第二优先级,与动画有关
Callback_traversal
最低优先级,与UI控件绘制有关
蓝色
测量绘制的时间
红色
执行的时间 Display List
橙色
处理时间
如果这个柱状很高代表GPU太繁忙了
紫色
将资源转移到渲染线程的时间
shell dumpsys gfxinfo com.xxx.xxx
Hierarchy View
Layout Inspactor
定义
布局优化主要就是避免Overdraw
什么是Overdraw?
Overdraw就是过度绘制,是指在一帧的时间内(16.67ms)像素被绘制了多次,理论上一个像素每次只绘制一次是最优的,但是由于重叠的布局导致一些像素会被多次绘制,而每次绘制都会对应到CPU的一组绘图命令和GPU的一些操作,当这个操作耗时超过16.67ms时,就会出现掉帧现象,也就是我们所说的卡顿,所以对重叠不可见元素的重复绘制会产生额外的开销,需要尽量减少Overdraw的发生
查看Overdraw
在开发者选项-调试GPU过度绘制(Show GPU Overdraw)
没有颜色: 意味着没有overdraw。像素只画了一次。
蓝色: 意味着overdraw 1倍。像素绘制了两次。大片的蓝色还是可以接受的(若整个窗口是蓝色的,可以摆脱一层)
绿色: 意味着overdraw 2倍。像素绘制了三次。中等大小的绿色区域是可以接受的但你应该尝试优化、减少它们。
浅红: 意味着overdraw 3倍。像素绘制了四次,小范围可以接受。
暗红: 意味着overdraw 4倍。像素绘制了五次或者更多。这是错误的,要修复它们。
如何优化
合理选择控件容器
LinearLayout
TableLayout
FrameLayout
RelativeLayout
ConstraintLayout
去掉window的默认背景
去掉其他不必要的背景
ClipRect & QuickReject
为了解决Overdraw的问题,Android系统会通过避免绘制那些完全不可见的组件来尽量减少消耗。但是不幸的是,对于那些过于复杂的自定义的View(通常重写了onDraw方法),Android系统无法检测在onDraw里面具体会执行什么操作,系统无法监控并自动优化,也就无法避免Overdraw了。但是我们可以通过canvas.clipRect()来帮助系统识别那些可见的区域。这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视。这个API可以很好的帮助那些有多组重叠组件的自定义View来控制显示的区域。同时clipRect方法还可以帮助节约CPU与GPU资源,在clipRect区域之外的绘制指令都不会被执行,那些部分内容在矩形区域内的组件,仍然会得到绘制。除了clipRect方法之外,我们还可以使用canvas.quickreject()来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作
ViewStub
Merge
善用draw9patch
慎用Alpha
避免“OverDesign”
冷启动
当启动应用时,后台没有该应用的进程,这时系统会首先会创建一个新的进程分配给该应用,这种启动方式就是冷启动
热启动
当启动应用时,后台已有该应用的进程,比如按下home键,这种在已有进程的情况下,这种启动会从已有的进程中来启动应用,这种启动方式叫热启动
温启动
当启动应用时,后台已有该应用的进程,但是启动的入口Activity被干掉了,比如按了back键,应用虽然退出了,但是该应用的进程是依然会保留在后台,这种启动方式叫温启动
如何对比启动时间
adb shell am start -W packagename/MainActivity
adb shell am start -S -R 10 -W packagename/.MainActivity
-S表示每次启动前先强行停止
-R表示重复测试次数
参数
ThisTime
最后一个启动的Activity的启动耗时;
TotalTime
新应用启动的耗时,包括新进程的启动和Activity的启动
WaitTime(5.0之前没有)
ActivityManagerService启动App的Activity时的总时间(包括当前Activity的onPause()和自己Activity的启动)
系统日志统计
过滤displayed输出的启动日志.
WaitTime 是 startActivityAndWait 这个方法的调用耗时
ThisTime 是指调用过程中最后一个 Activity 启动时间到这个 Activity 的 startActivityAndWait 调用结束
TotalTime 是指调用过程中第一个 Activity 的启动时间到最后一个 Activity 的 startActivityAndWait 结束 如果过程中只有一个 Activity ,则 TotalTime 等于 ThisTime
Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量、布局、绘制显示在界面上
任务分优先级
优先级为1 启动加载
优先级为2 首页渲染完成后 开始加载
优先级为3 首页渲染后 延迟加载
traceview
找出单个方法执行时间长的
找出执行次数多的
优化思路总结
UI渲染,去除重复绘制
根据优先级划分初始化工作
SharedPreference优化
网络错误优化,使用ViewStub
Multidex优化
检查BaseActivity
加载启动App
App启动之后立即展示出一个空白的Window
创建App的进程
创建App对象
启动Main Thread
创建启动的Activity对象
加载View
布置屏幕
进行第一次绘制
完成第一次绘制后会把Main Activity替换已经展示的Background Window
- 利用提前展示出来的Window,快速展示出一个画面,给用户快速反馈的体验
设置背景图Theme
程序启动快,界面先显示背景图,然后再刷新其他界面控件。给人刷新不同步感觉
把样式设置为透明
给人程序启动慢感觉,界面一次性刷出来,刷新同步
- 避免在启动时做密集沉重的初始化
四个维度
必要且耗时:启动初始化,考虑用线程来初始化
MultiDex初始化
Tinker初始化
必要不耗时:首页绘制
非必要耗时:数据上报,插件初始化
非必要不耗时:直接去掉,在需要用的时候再加载
其他第三方组件的初始化
思考方向
分步加载
以大化小,优先级高的放前
异步加载
耗时多的异步化
延期加载
非必要的数据延时加载
- 定位问题
避免I/O操作
序列化/反序列化
网络操作
布局嵌套
利用主题快速显示界面
异步初始化组件
通过梳理业务逻辑,延迟初始化组件、操作
正确使用线程
开启线程池比单独开启一个线程耗资源
去掉无用代码、重复逻辑等
开发阶段使用BlockCanary 或者ANRWatchDog等第三方监控sdk
Method Tracing
Systrace
nimbledroid
Lint
部分的数据库,I/O操作发生在MainActivity主线程
Application中创建了线程池
Application中做了大量的初始化操作
MainActivity网络请求密集
是否必要
接口是否可以合并
工作线程使用没设置优先级
AyncTask- thread_priority_background
AsyncQueryHandler - thread_priority_default
线程池
信息没缓存,重复获取同样信息
不合理的业务流程
高级 与低级 区别
废弃的老代码
尽量减少刷新次数
控制刷新频率
进度条,变化没1%,完全没必要刷新
避免没有必要的刷新
数据没有变化,需要刷新的控件在不可见区域
一个View从不可见到可见,一定要刷新一次
尽量避免后台有高CPU的线程运行
缩小刷新区域
invalidate(Rect dirty)
invalidate(int left,int top,int right,int bottom)
RecyclerView
消耗资源最多
Application级别
<application android:hardwareAccelerated="true" .../>
Activity级别
<activity android:haredwareAccelerated="false/true"/>
Window级别
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HAREDWARE_ACCELERATED);
View级别
View.setLayerType(View.LAYER_TYPE_SOFTWARE,null);
LAYER_TYPE_NONE
LAYER_TYPE_HARDWARE
绘制为硬件纹理
LAYER_TYPE_SOFTWARE
此View通过软件渲染为一个Bitmap
将要执行动画的View的LayerType设置为LAYER_TYPE_HARDWARE
计算动画View的属性等信息,更新View的属性
若动画结束,将LayerType设置为NONE
在软件渲染时,可以重用bitmap的方式来节省内存,但开启了硬件加速,这个方案就不起作用
开启硬件加速 需要额外的内存,加速的UI切换到后台时,产生的内存有可能不释放
在UI中的过渡绘制时,硬件加速会比较容易发生问题