系统显示原理
显示过程
安卓应用通过测量,布局,绘制后的surface缓存数据,通过SurfaceFlinger把数据渲染到显示屏幕上,通过安卓刷新机制来刷新数据。
扩展测量、布局、绘制原理
卡顿原因
理想情况下,60FPS(每秒传递的帧数)感觉不到卡,也就是每次绘制时长应该在16ms以内。安卓系统每隔16ms发出VSYNC信号,触发对UI进行渲染。
如果某个操作花费24ms,系统在得到VSYNC信号就无法正常渲染,就会发生丢帧现象。如果在动画或者滑动等场景,就会感觉到卡顿不顺畅。
卡顿原因:
- 绘制任务太重,绘制一帧内容耗时太长
- 主线程太忙,导致VSYNC信号来时还没准备好数据导致丢帧
主线程主要做以下几个方面的工作
- 系统事件处理
- 消息处理
- 界面布局
- 界面绘制
- 界面刷新
性能分析工具
Profile GPU Rendering(GPU呈现模式分析)
功能:卡顿检测
开启方式:开发人员选项-GPU呈现模式分析-在屏幕上显示为条状图
图示:
- 蓝色:绘制时间
- 共色:执行时间
- 橙色:处理时间
超过绿色警戒线,就可能丢帧。所以保存UI流畅关键是让这些垂直柱状条尽可能保存在绿线以下。
Android Profiler分析器
功能:分析cpu、内存 、网络使用情况
开启方式:View > Tool Windows > Android Profiler
使用教程:https://blog.csdn.net/niubitianping/article/details/72617864
Analyze
功能:代码、布局检查
配置:File-Settings-Inspections-Android Lint
检测:Analyze-Inspect Code
布局优化
include
原理:在解析xml布局的时候,如果检测到include标签,那么直接将该布局下的根布局添加到include下的父视图中。
注意:include布局下的根布局id会动态修改,会被设置成include标签中的id值。
merge
子布局和父布局都是同一种类型,如FrameLayout,那么可以使用merge进行布局优化
作用:合并UI布局,使用merge标签能降低UI布局的嵌套层次
注意事项
merge只能用在布局xml文件的根元素
使用merge加载一个布局时,必须制定一个viewGroup作为其父元素,并且要设置加载的attachToRoot参数是true(参照inflate(int,viewGroup,boolean))
不能在ViewStub中使用Merge标签,因为ViewStub的inflate方法中根本就没有attachToRoot的设置
ViewStub
ViewStub是一个不可见和能在运行期间延迟加载目标视图的、宽高都为0的View,在使用inflate()或者设置visible之前,是占用布局空间和系统资源的,它只是一个为目标视图占了个位置而已
原理:当手动调用inflate()或者设置visible(实际上也是调用inflate函数),会将ViewStub从父控件移除,并加装目标控件,然后将目标控件添加到ViewStub父控件中去,这就完成了视图动态替换,也就是延迟加载功能
mStub.inflate();
// mStub.setVisibility(View.VISIBLE);
注意事项
ViewStub只加载一次,之后会被置空,如果要控制某个布局的显示或者隐藏,只能使用View的可见性控制
减少布局层次原则:
布局层次越少、控件越少加载速度越快,属性越少,解析越快。
优化总结:
- 尽量多使用RelativeLayout,避免使用LinearLayout的layout_weight属性,不要使用绝对布局AbsoluteLayout
- 将可复用的组件抽取出来并通过
标签使用 来加载不常用的布局 减少布局层次 - 尽可能少用wrap_content,wrap_content会增加布局measure时的计算成本
- 删除控件中无用属性(属性越少,解析越快)
避免过度绘制
导致过度绘制的主要原因
- xml布局,控件有重叠且都有设置背景
- View自绘,onDraw里面对同个区域绘制多次
检测工具
开发人员选项-调试GPU过度绘制-显示过度绘制区域
- 无色-每个像素绘制1次
- 蓝色-每个像素多绘制1次
- 绿色-每个像素多绘制2次
- 粉色-每个像素多绘制3次,不超过1/4可接受
- 红色-每个像素多绘制4次或者更多,需优化
优化途径
xml上的优化
- 减少背景叠加,移除xml中不必要的背景,或者根据条件设置
- 移除Window默认背景
- 按需显示占位背景图片(???)
例如移除window默认背景:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setBackgroundDrawable(null);
}
自定义View优化
关键在于canvas.clipRect()和canvas.quickreject()的使用
(在自定义View复习阶段补充学习)
启动优化
要分析启动新能,需要熟悉Application和Activity的工作流程和生命周期
Application
启动Application时,系统会创建一个PID,即进程ID,其生命周期是最长的,等于这个应用程序的生命周期,因为它是全局单例。可以避免使用静态变量来存储永久保存值,而是作为Application全局变量保存。
相关抽象接口
- attachBaseContext 得到应用上下文的Context,在应用创建时首次调用
- onCreate 晚于attachBaseContext调用,在应用创建时首次调用
- onTerminate 应用结束时调用
- onConfigurationChanged 系统配置发生变化时调用
- onLowMemory 系统低内存时调用
- onTrimMemory 系统要求应用释放内存时调用
启动流程
启动-Application-attachBaseContext()-onCreate-Activity生命周期
启动分类
- 冷启动:创建一个新的进程,创建和初始化Application类
- 热启动:在已有进程中启动,不会再创建和初始化Application类,Application只初始化一次
优化方案
- 在闪屏页,做数据准备,如底层模块的初始化、数据的预拉取
- UI优化,减少布局层次,避免过度绘制
- 加载逻辑优化
- 必要且耗时:启动初始化,考虑用线程来初始化
- 必要且不耗时:首页绘制
- 非必要且耗时:数据上报,插件初始化(考虑用线程)
- 非必要且不耗时:直接去掉,需要时再加载
合理刷新
减少刷新次数
- 进度条,数据变化没有1%,没必要刷新
- 不可见的控件,没必要刷新
避免后台有高CPU线程运行
如果后台线程开销过大,占用GPU过高,导致系统GC频繁和CPU时间片资源紧张,还是有可能导致页面卡顿。
例如:列表边滑动,边加载图片,可进行优化成:滑动时停止加载图片,滑动结束继续加载图片
缩小刷新区域
- invalidate(Rect dirty)
- 列表item发生变化,调用notifyDataSetChanged()刷新
提升动画性能
性能:属性动画>补件动画>帧动画
硬件加速
硬件加速可渲染提高动画性能,使动画更平滑、流畅
硬件加速控制级别
并不是所有2D绘制的操作都支持硬件加速,所以我们要合理选择硬件加速控制级别
Application级别
Activity级别
Window级别
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
View级别
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
- LAYER_TYPE_NONE 默认行为,正常渲染,不会在off-screen buffer上备份
- LAYER_TYPE_HARDWARE 如果应用开启了硬件加速view渲染在硬件上,否则与LAYER_TYPE_SOFTWARE行为一样
- LAYER_TYPE_SOFTWARE 用软件渲染在一个bitmap上
设计一个动画流程
- LayerType设置成LAYER_TYPE_HARDWARE
- 更新view属性
- 动画结束将LayerType设置成LAYER_TYPE_NONE
硬件加速注意事项
硬件加速属于双缓冲机制,使用显存进行页面渲染(使用较少的物理内存),导致更频繁的显存操作,低版本手机可能引起以下现象:
白屏、花屏、闪屏;
注意事项
- 软件渲染,可以使用重用Bitmap的方法节省内存,硬件加速不适用
- 开启硬件加速的View在前台运行,会耗费额外的内存,切换到内存,产生的额外内存有可能不释放
- 减少过度绘制
不支持硬件加速的相关接口
Canvas不支持硬件加速的二维绘图接口:
- clipPath()
- clipRegion()
- drawPicture()
- drawPosText()
- drawTextOnPath()
- drawVertices()
Paint不支持硬件加速的接口:
- setLinearText()
- setMaskFilter()
- setRasterizer()
卡顿监控方案
利用Looper中的Printer来实现监控,重写Printer方法(判断start和end的时间差值),通过Looper.getMainLooper().setMessageLogging(LogPrinter)设置自定义的Printer
参考:Android应用性能优化最佳实践