Android绘制优化布局优化

犀渠玉剑良家子,白马金羁侠少年。这篇文章主要讲述Android绘制优化布局优化相关的知识,希望能为你提供帮助。
相关文章
Android绘制优化( 一) 绘制性能分析
前言
我们知道一个界面的测量和绘制是通过递归来完成的, 减少布局的层数就会减少测量和绘制的时间, 从而性能就会得到提升。当然这只是布局优化的一方面, 那么如何来进行布局的分析和优化呢? 本篇文章会给你一个满意的答案。
1.布局优化工具
在讲到如何去布局优化前, 我们先来学习两种布局优化的工具。
Hierarchy Viewer Hierarchy Viewer是android SDK自带的可视化的调试工具, 用来检查布局嵌套和绘制的时间。需要注意的是在在Android的官方文档中提到: 出于安全考虑, Hierarchy Viewer只能连接Android开发版手机或是模拟器。
首先我们在Android Studio中选择Tools-> Android-> Android Device Monitor, 在Android Device Monitor中选择Hierarchy Viewer , 如下图所示:

Android绘制优化布局优化

文章图片

选择Hierarchy Viewer后会进出Hierarchy Viewer窗口, 如下图所示。
Android绘制优化布局优化

文章图片

Hierarchy Viewer中有4个四个子窗口, 它们的的作用为:
  • Windows: 当前设备所有界面列表。
  • Tree View: 将当前Activity的所有View的层次按照高层到低层从左到右显示出来。
  • Tree Overview: 全局概览, 以缩略的形式显示。
  • Layout View: 整体布局图, 以手机屏幕上真实的位置呈现出来。单击某一个控件, 会在Tree Overview窗口中显示出对应的控件。
根据上面讲到的Hierarchy Viewer的4个四个子窗口, 我们可以很容易的查看我们布局控件的层级关系。当然Hierarchy Viewer还可以查看某一个View的耗时, 我们可以选择某一个View, 然后单击下图红色箭头标识的按钮, 这里我们把他简称为Layout Time按钮。
Android绘制优化布局优化

文章图片

从图中可以看出被选中的RelativeLayout自身的Measure、Layout和Draw的耗时数据都为n/a。单击Layout Time按钮后, 就可以查看View的耗时情况了, 如下图所示。
【Android绘制优化布局优化】
Android绘制优化布局优化

文章图片

从图中可以看出, 被选中的LinearLayout给出了自身Measure、Layout和Draw的耗时, 并且它所包含的View中都有了三个指示灯, 分别代表当前View在Measure、Layout和Draw的耗时, 绿色代表比其他50%View的同阶段( 比如Measure阶段) 速度要快, 黄色则代表比其他50%View同阶段速度要慢, 红色则代表比其他View同阶段都要慢, 则需要注意了。如果想要看View的具体耗时, 则点击该View就可以了。
Android Lint Android lint是在ADT 16提供的新工具, 它是一个代码扫描工具, 通过代码静态检查来发现代码出现的潜在问题, 并给出优化建议。检查的范围主要有以下几点:
  • Correctness 正确性
  • Security 安全性
  • Performance 性能
  • Usability 可用性
  • Accessibility 可达性
  • Internationalization 国际化
Android Lint功能十分强大, 这里我们只关注XML布局检查, 我们可以通过Android Studio的Analyze-> Inspect Code来配置检查的范围, 如下图所示。
Android绘制优化布局优化

文章图片

点击上图的OK按钮后, 就会进行代码检查, 检查的结果如下图所示。
Android绘制优化布局优化

文章图片

图中列出了项目中出现的问题种类, 以及每个问题种类的个数, 问题种类包括我们前面提到的Correctness 、Internationalization 、Performance等。我们点击展开最后的XML一项, 点击一个问题, 就会出现如下图的提示。
Android绘制优化布局优化

文章图片

可以看出给出了Namespace declaration is never used的提示, 并指出了问题所在的文件和行数, 我们点击数字3, 直接跳入到问题的代码, 发现如下代码:
< ?xml version= " 1.0" encoding= " utf-8" ?> < RelativeLayout xmlns:android= " http://schemas.android.com/apk/res/android" xmlns:tools= " http://schemas.android.com/tools" android:id= " @ + id/activity_main" android:layout_width= " match_parent" android:layout_height= " match_parent" > ...

第三行的Namespace 确实没有被用到。如果想要自定义Android Lint的检查提示, 可以通过File-> Settings-> Editor-> Inspections中来配置Android Lint, 如下图所示。
Android绘制优化布局优化

文章图片

从图中可以发现, 我们可以配置Android Lint检查的范围以及问题的严重等级。
2.布局优化方法
布局的优化方法很多, 主要包括合理运用布局、Include、Merge、ViewStub, 下面我们来一一对这些内容进行讲解。
合理运用布局
我们常用的布局主要有LinearLayout、RelativeLayout和FrameLayout等, 合理的使用它们可以使得Android绘制工作量变少, 性能得到提高。我们来举个简单的例子。
< ?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= " horizontal" > < TextView android:layout_width= " wrap_content" android:layout_height= " wrap_content" android:text= " 布局优化" /> < LinearLayout android:layout_width= " wrap_content" android:layout_height= " wrap_content" android:layout_marginLeft= " 10dp" android:orientation= " vertical" > < TextView android:layout_width= " wrap_content" android:layout_height= " wrap_content" android:text= " Merge" /> < TextView android:layout_width= " wrap_content" android:layout_height= " wrap_content" android:text= " ViewStub" /> < /LinearLayout> < /LinearLayout>

上面的代码用了两个LinearLayout来进行布局, 运行效果如下图所示。
Android绘制优化布局优化

文章图片

我们用Hierarchy Viewer来查看层级情况, 如下图所示。
Android绘制优化布局优化

文章图片

可以看到我们的布局共有3层, 一共含有5个View。如果我们用RelativeLayout来进行改写呢? 代码如下所示。
< ?xml version= " 1.0" encoding= " utf-8" ?> < RelativeLayout xmlns:android= " http://schemas.android.com/apk/res/android" android:layout_width= " match_parent" android:layout_height= " match_parent" > < TextView android:id= " @ + id/tv_text1" android:layout_width= " wrap_content" android:layout_height= " wrap_content" android:text= " 布局优化" /> < TextView android:id= " @ + id/tv_text2" android:layout_width= " wrap_content" android:layout_height= " wrap_content" android:layout_marginLeft= " 10dp" android:layout_toRightOf= " @ id/tv_text1" android:text= " Merge" /> < TextView android:id= " @ + id/tv_text3" android:layout_width= " wrap_content" android:layout_height= " wrap_content" android:layout_below= " @ id/tv_text2" android:layout_marginLeft= " 10dp" android:layout_toRightOf= " @ + id/tv_text1" android:text= " ViewStub" /> < /RelativeLayout>

我们只用了一个RelativeLayout来进行布局。用Hierarchy Viewer来查看层级情况, 如下图所示。
Android绘制优化布局优化

文章图片

布局共有两层, 一共含有4个View。从这里我们就可以看出我们用RelativeLayout减少了一层的布局, 当然这只是一个简单例子, 如果布局复杂, 那么合理的用RelativeLayout来替代LinearLayout会减少很多层布局。
一般情况下, RelativeLayout的性能是比LinearLayout低, 因为RelativeLayout中的View的排列方式是基于彼此依赖的。但是如果布局层数较多时, 如果能用RelativeLayout来实现, 还是推荐用RelativeLayout。
使用Include标签来进行布局复用 一个很常见的场景就是, 多个布局需要复用一个相同的布局, 比如一个TitleBar。如果这些界面都要加上这个相同布局TitleBar, 维护起来就就很麻烦, 我们需要复制TitleBar的布局到每个需要添加的界面, 这样容易发生遗漏。如果需要修改TitleBar则需要去每个引用TitleBar的布局进行修改。为了解决这些问题, 我们可以用Include标签来解决。
首先我们先来写一个简单的TitleBar布局: titlebar.xml 如下所示。
< ?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= " 40dp" android:background= " @ android:color/darker_gray" > < ImageView android:layout_width= " 30dp" android:layout_height= " 30dp" android:src= " @ drawable/ico_left" android:padding= " 3dp" android:layout_gravity= " center" /> < TextView android:layout_width= " match_parent" android:layout_height= " wrap_content" android:layout_gravity= " center" android:gravity= " center" android:text= " 绘制优化" /> < /LinearLayout>

这个TitleBar由ImageView和TextView组成, 下面我们将TitleBar引入到我们此前用过的布局中, 如下所示。
< ?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" > < include layout= " @ layout/titlebar" /> < RelativeLayout android:layout_width= " match_parent" android:layout_height= " match_parent" android:orientation= " horizontal" > < TextView android:id= " @ + id/tv_text1" android:layout_width= " wrap_content" android:layout_height= " wrap_content" android:text= " 布局优化" /> ... < /RelativeLayout> < /LinearLayout>

可以看到我们用include标签引入了titlebar布局, 运行效果如下图所示。
Android绘制优化布局优化

文章图片

用Merge标签去除多余层级 Merge意味着合并, 在合适的场景使用Merge标签可以减少多余的层级。Merge标签一般和Include标签搭配使用, 上面的例子, 我们用Hierarchy Viewer来查看布局层级, 如下图所示。
Android绘制优化布局优化

文章图片

可以看到我们用Include标签引用的布局的根布局是一个LinearLayout。如果我们使用Merge标签来替换LinearLayout呢? titlebar.xml 的代码如下所示。
< ?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= " 40dp" android:background= " @ android:color/darker_gray > < ImageView android:layout_width= " 30dp" android:layout_height= " 30dp" android:src= " @ drawable/ico_left" android:padding= " 3dp" android:layout_gravity= " center" /> < TextView android:layout_width= " match_parent" android:layout_height= " wrap_content" android:layout_gravity= " center" android:gravity= " center" android:text= " 绘制优化" /> < /merge>

这时我们再用Hierarchy Viewer来查看布局层级, 如下图所示。
Android绘制优化布局优化

文章图片

可以看到此前的根布局LinearLayout没有了, 但是我们用merge标签来替代LinearLayout导致LinearLayout失效, 因此布局就错乱了, 因此可以得知merge标签最好是来替代FrameLayout, 或者是布局一致的LinearLayout, 比如当前布局的LinearLayout是垂直方向的, 被包含的布局的LinearLayout也是垂直方向的则可以用merge标签, 本场景包含的LinearLayout是水平的, 显然并不符合这一要求。但是如果执意想要在本场景使用merge标签也是可以的, 就是用继承自LinearLayout的自定义View, 代码如下所示。
< ?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" > < com.example.liuwangshu.moonlayout.TitleBar android:layout_width= " wrap_content" android:layout_height= " wrap_content" android:orientation= " horizontal" android:background= " @ android:color/darker_gray" > < /com.example.liuwangshu.moonlayout.TitleBar> < RelativeLayout android:layout_width= " match_parent" android:layout_height= " match_parent" android:orientation= " horizontal" > < TextView android:id= " @ + id/tv_text1" android:layout_width= " wrap_content" android:layout_height= " wrap_content" android:text= " 布局优化" /> ... < /RelativeLayout> < /LinearLayout>

上面布局中TitleBar就是一个自定义View, 它继承LinearLayout。我们在TitleBar标签中添加此前的LinearLayout的属性: android:orientation和android:background。
使用ViewStub来提高加载速度 一个很常见的开发场景就是我们想要一个布局时, 并不是所有的控件都需要显示出来, 而是显示出一部分, 对于这种情况, 我们一般采用的方法就是使用View的GONE和INVISIBLE, 但是这种方法效率不高, 虽然是达到了隐藏的目的, 但是仍在布局当中, 系统仍然会解析它们, 我们可以用ViewStub来解决这一问题。
ViewStub是轻量级的View, 不可见并且不占布局位置。当ViewStub调用inflate方法或者设置可见时, 系统会加载ViewStub指定的布局, 然后将这个布局添加到ViewStub中, 因此, 在对ViewStub调用inflate方法或者设置可见时, 它是不占布局空间和系统资源的, 它主要的目的就是为目标视图占用一个位置。因此, 使用ViewStub可以提高界面初始化的性能, 从而提高界面的加载速度。
我们首先在布局中加入ViewStub标签, 布局代码如下所示。
< ?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" > < ViewStub android:id= " @ + id/viewsub" android:layout_width= " match_parent" android:layout_height= " 40dp" android:layout= " @ layout/titlebar" /> ... < /LinearLayout>

ViewStub标签中用android:layout引用了此前写好的布局titlebar.xml。这时我们运行程序, ViewStub标签所引用的布局是显示不出来的, 因为引该局还没有加载到ViewStub中, 接下来在代码中使用ViewStub:
public class MainActivity extends AppCompatActivity { private ViewStub viewsub; @ Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); viewsub= (ViewStub) findViewById(R.id.viewsub); //viewsub.inflate(); //1 viewsub.setVisibility(View.VISIBLE); //2 } }

可以使用注释1和注释2处的代码来将ViewStub引用的布局加载到ViewStub中, 这样引用的布局就显示了出来。
在使用ViewStub时需要主要以下问题:
- ViewStub只能加载一次, 加载后ViewStub对象会被置为空, 这样当ViewStub引用的布局被加载后, 就不能用ViewStub来控制引用的布局了。因此, 如果一个控件需要不断的显示和隐藏, 还是要使用View的Visibility属性。
- ViewStub不能嵌套Merge标签。
- ViewStub操作的是布局文件, 如果只是想操作具体的View, 还是要使用View的Visibility属性。
3.避免GPU过度绘制
什么是过度绘制呢? 我们来打个比方, 假设你要粉刷房子的墙壁, 一开始刷了绿色, 接着又刷了黄色, 这样黄色就将绿色盖住, 也就说明第一次的大量粉刷工作白做了。同样手机屏幕绘制也是如此, 过度绘制是指在屏幕上某个像素在同一帧的时间内被绘制多次, 从而浪费了GPU和CPU的资源。产生这一原因主要有两个原因:
- 在XML布局中, 控件有重叠且都有设置背景。
- View的OnDraw中同一区域绘制多次。
过度绘制是不可避免的, 但是过多的过度绘制会浪费很多资源, 并且导致性能问题, 因此, 避免过度绘制是十分必要的。我们可以用Android系统中自带的工具来检测过度绘制。首先要保证系统版本在Android 4.1以上, 接着在开发者选项中打开调试GPU过度绘制选项就可以进入GPU过度绘制模式, 如下图所示。
Android绘制优化布局优化

文章图片

这时屏幕会出现出各种颜色, 主要有以下几种, 如下图所示。
Android绘制优化布局优化

文章图片

各个颜色的定义为:
  • 原色: 没有过度绘制 – 每个像素在屏幕上绘制了一次。
  • 蓝色: 一次过度绘制 – 每个像素点在屏幕上绘制了两次。
  • 绿色: 两次过度绘制 – 每个像素点在屏幕上绘制了三次。
  • 粉色: 三次过度绘制 – 每个像素点在屏幕上绘制了四次。
  • 红色: 四次或四次以上过度绘制 – 每个像素点在屏幕上绘制了五次或者五次以上。
最理想的是蓝色, 一个像素只绘制一次, 合格的页面绘制是白色、蓝色为主, 绿色以上区域不能超过整个的三分之一, 颜色越浅越好。
避免过度绘制主要有以下几个方案:
1.移除不需要的background。
2.在自定义View的OnDraw方法中, 用canvas.clipRect来指定绘制的区域, 防止重叠的组件发生过度绘制。
欢迎关注我的微信公众号, 第一时间获得博客更新提醒, 以及更多成体系的Android相关原创技术干货。
扫一扫下方二维码或者长按识别二维码, 即可关注。


Android绘制优化布局优化

文章图片


    推荐阅读