文章目录
Android的渲染机制优化是重中之重,大家都知道,但是问题也往往还是会出现,打造一个高性能扁平化的APP,优化必不可少。
概述与案例
为什么会发现卡顿性能,屏幕过热等问题呢,参考Google 发布 Android 性能优化典范,本文主要用从具体调优技巧出发,实现Android overdraw问题的排查和调优
Overdraw即是绘制重叠,很好理解,App界面都有布局,我们可以认为他就是画上去的,如果我们一遍遍的去涂画背景,底层的不可见涂层便是不必要的,浪费资源的同时也会造成画板的厚重层次。
排查技巧一:查看是否过度绘制
-
通过在手机开发者选项中打开GPU过度绘制调式即可
-
打开GPU过度绘制开关后,以下UI界面为例,其中不同颜色对应的屏幕的绘制次数,颜色的深度代表层级,颜色越深就表示UI的层级越重
- 蓝色 1x
- 绿色 2x
- 淡红 3x
- 深红 4x+ (尽量避免这种情况的出现)
排查技巧二:通过Hierachy View或者Layout Inspecot查看布局层级
Android 的UI加载布局,都是成树状结构,越往分支延伸,层级结构越复杂,搭载一个简单轻薄的界面,需要做到简化布局的目的。
-
Hierachy View在AndroidStudio 3.0后都是通过monitor.bat工具打开调式,在SDK目录下tools里
参考官方使用工具介绍Profile your layout with Hierarchy Viewer -
Layout Inspecot通过AndroidStudio即可查看View Tree
Overdraw优化策略-扁平化
过度绘制的优化我个人的原则就是:所见即所画
这句话什么意思呢,就是尽可能的只绘制我们所需要看到的界面,如我概述中说的,如果布局是刷漆,那么刷一层就是我们最理想化的结果。当然,理想化是不可达的,但我们可以无限去接近它。
围绕这个原则做优化,我做了2点概要总结,当然,在具体开发中这4个方向又包含了其他许多优化操作的可能。
一、至尊超薄
顾名思义,就是打造扁平化UI,超薄绘制图层,关键要理解Android App Window层级的联系,如下图,
优化层级,我的常用方法就是从2点出发
- 背景图层的优化打薄:去除不必要的背景图层,如包括Drawable或者Color的绘制,可以从这几点考虑
- 减少布局层级:布局层级优化即减少window的控件层级关系,选择最优的控件布局来达到我们的UI效果。
技巧一:去除window背景绘制
我们在定义Activity Layout时,会给Activity指定主题,通常的theme中会默认给DecorView中指定一个背景色,当时但我们Layout中需要自定义背景布局颜色或者图片时,我们可以去除这个不必要的window背景色
通过在theme中指定
android:windowbackground="null"
或者 在Activity中 setContentView() 后指定
getWindow().setBackgroundDrawable(null);
技巧二:上下级同色图层间保持最少图层绘制原则
- 如布局中的子View背景色和父控件背景色一致时,可以去除子View的背景绘制或者去除布局背景绘制而选择分别给子View绘制背景,这样保证了最少图层绘制
- 如ImageView或者Button的backgroundColor和布局颜色一致时,可以将颜色设置为透明,或者不设置。当然,在selector drawble中也可以使用。
技巧三:学会选择布局控件
Android有很多基本布局,从FrameLayout,LinearLayout,RelativeLayout再到新出的ConstraintLayout,不同的基本布局,应用场景也不一样。
如何选择很重要
以扁平化的UI为目标来看,减少层级是一个目的,同时也要考虑他们绘制时的资源消耗,并不是越新功能越强大的控件就越出色越受用。
理解绘制资源消耗,就得明白布局内部做了些什么,
比如LinearLayout对比RelativeLayout对比,RelativeLayout在measure时耗时更严重,原因是RelativeLayout对子View做了2次measure。但是这样说来,是不是说我就非得多用LinearLayout,不用RelativeLayout呢?不是这样的,理解了LinearLayout内部结构就会发现,当我们使用它的权重属性时,它也会测量2次,而且当多层次的LinearLayout嵌套使用weight属性,状况会更加严重。
因此,合理的选择布局控件重中之重
技巧四:merge标签的使用
merge主要作用是作为一个复用层级的root标签。它能够去除一个包裹嵌套层级的View Group,我们在轻量化布局代码时,尝尝会抽离出许多layout布局,而将这些子布局嵌套到根布局时,这个连接的节点,通过使用Merge能够去除不必要的根标签,降低层级。
-
应用场景和要求:merge主要和include配合,当父布局和include加载子布局标签一样时使用。通常建议在FrameLayout和LinearLayout中使用,复杂布局可能会造成维护时更加复杂
-
代码:
------------------根布局------------------ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Merge标签Head TextView" /> <include layout="@layout/layout_merge"></include> </LinearLayout> ------------------layout_merge------------------ <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Merge 1标签Content" /> <include layout="@layout/layout_merge_inner"></include> </merge> ------------------layout_merge_inner 再次嵌套merge------------------ <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Merge 1标签Foot" /> </merge>
-
使用注意点:
- merge只是作为一个root标签使用,他是个虚拟的View,因此不能通过findviewbyid实例化
- merge可以重复嵌套
- merge再次声明的父布局约束参数不生效
二、不见不画,待见再画
不见不画——就是在不可见的界面,我们如果不用去绘制,那么可以想象一下,展现在我们眼睛里的,看起来复杂重叠的UI就是一层假象的薄膜。有点类似网上看的,新闻主播看似西装革履的播报新闻,其实桌子下面凉飕飕的大裤衩子,哈哈哈。
待见再画——我把它归纳为一直延迟加载的技巧,我们的App是动态的,UI也是,不同时刻不同操作都会造成UI的不断变化。一次性加载所有布局那是傻瓜,循序渐进才是王道。要做到延迟加载,我们需要理解《布局被Inflate时做了哪些操作?》,由我这篇文章可以知道,View在xml布局时不管声明的GONE还是VISIBLE都会被加载并且占用资源。因此,想要延迟加载一个子View,得有别的办法,如下:
技巧五:动态代码加载
通过动态代码加载,也可以实现布局的延迟加载
//使用LayoutInflater来加载activity_main.xml视图
FrameLayout rootView = (FrameLayout) LayoutInflater.from(this).inflate(R.layout.activity_main, null);
setContentView(rootView);
View childView = LayoutInflater.from(this).inflate(R.layout.layout_set_foucs_cx, rootView);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) childView.getLayoutParams();
layoutParams.gravity = Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL ;
childView.setLayoutParams(layoutParams);
技巧六:ViewStub标签的使用(延迟加载)
ViewStub可以理解为占位替代标签,它是个超级轻量级View,加载的过程对资源的占用非常小,是实现布局延迟加载利器
-
使用:
在布局中声明: <ViewStub android:id="@+id/vstb_view" android:layout_width="@dimen/setcamera_cs_h_land" android:layout_height="@dimen/setcamera_cs_h_land" android:layout_gravity="bottom|center_horizontal" android:layout_marginBottom="@dimen/pr_bottom_height" android:layout="@layout/layout_lazay" android:orientation="vertical" /> 需要被加载ViewStub包裹的布局时: 通过: ViewStub stub = (ViewStub) findViewById(R.id.vstb_awb_cs); stub.inflate(); 或者 stub.setVisibility(View.VISIBLE);
-
注意点:ViewStub只是作为占位框使用,当我们延迟加载完布局时,ViewStub就被摒弃了。
技巧七:clipRect & quickReject
这个技巧主要用于我们自定义View中如何去避免overdraw
在Android 4.4以后,系统有自动检测overdraw的功能,会通过避免绘制那些完全不可见的组件来尽量减少 Overdraw,那些Nav Drawer里面不可见的View就不会被执行浪费资源。但是但我们在自定义View或者布局中重写了onDraw方法后,系统无法监控并自动优化
了。因此,我们需要自己去指定给系统知道,哪些部分需要绘制,哪些部分是overdraw的区域。
- canvas.clipRect():可以理解为指定画布的区域,区域以外的绘制效果都不会生效
- canvas.quickReject():判断是否和某个矩形相交
总结
如何避免overdraw,有一些感想,实际优化有很多的技巧,大部分只有熟记心中,时刻避免和检测即可。但要想实现快速开发扁平化的优秀App,基础功底要扎实,需要我们深入理解内部布局的源码实现,从而自然而然的去做出选择,比如基本布局的绘制原理,只有理解了才会得出最优解!