Android布局优化(五)绘制优化—避免过度绘制_第1张图片
  • Android布局优化(一)LayoutInflate — 从布局加载原理说起
  • Android布局优化(二)优雅获取界面布局耗时
  • Android布局优化(三)使用AsyncLayoutInflater异步加载布局
  • Android布局优化(四)X2C — 提升布局加载速度200%
  • Android布局优化(五)绘制优化—避免过度绘制

目录

Android布局优化(五)绘制优化—避免过度绘制_第2张图片

前言

本系列的前面几篇文章我们介绍了布局加载的原理及优化,布局加载完成后(生成VIew对象)就要进行视图绘制,我们知道,android要求每帧的绘制时间不超过16ms,不然就会导致丢帧及应用卡顿。所以本文将会介绍一些布局绘制优化技巧

如何监控应用渲染速度

点击设置—>开发人员选项—>监控—>GPU呈现模式分析,然后选择 在屏幕上显示为条形图 即可以看到一个图表,如下图所示

Android布局优化(五)绘制优化—避免过度绘制_第3张图片

1.沿水平轴的每个竖条都代表一个帧,每个竖条的高度表示渲染该帧所花的时间(单位:毫秒)
2.水平绿线表示 16 毫秒。 要实现每秒 60 帧,代表每个帧的竖条需要保持在此线以下。 当竖条超出此线时,可能会使动画出现暂停

再来看下每个竖条的颜色代表什么意思:

分析从哪些方向进行绘制优化

从GPU呈现模式分析可以看出来,我们能够进行优化的点主要就是测量、布局、绘制、动画和输入处理

  1. 测量、布局、绘制过程都会存在自顶而下遍历过程,所以如果布局的层级过多,这会占用额外的CPU资源
  2. 当屏幕上的某个像素在同一帧的时间内被绘制了多次(Overdraw),这会浪费大量的CPU以及GPU资源
  3. 在绘制过程,也就是onDraw()方法内,我们应该尽量避免局部对象的创建,因为onDraw()方法在绘制过程中会多次调用,大量的局部变量可能会造成内存抖动
  4. 合理使用动画,这个本章不做讨论,有兴趣的可以自己了解动画的相关知识
  5. 不应该在Event响应的回调中做耗时操作

总结下来视图绘制优化主要要解决的问题就是:

减少view树层级,要宽而浅,避免窄而深

如何检测过度绘制

点击设置—>开发人员选项—>硬件—>调试GPU过度绘制,然后选择 显示过度绘制区域 即可以看到一个图表,如下图所示

Android布局优化(五)绘制优化—避免过度绘制_第4张图片

再来看下每种颜色代表什么意思:

Android布局优化(五)绘制优化—避免过度绘制_第5张图片

有些过度绘制是无法避免的。但是在优化界面时,应该尽量让大部分的界面显示为原色(即无过度绘制)或者为蓝色(仅有 1 次过度绘制)。如果出现粉色或者红色,应该查看代码看看能否尽量避免

如何避免过度绘制

移除window的背景

一般情况下我们的AppTheme都默认带会有windowBackground

但是这个windowBackground大部分清洁下都是没有什么意义的,因为我们往往都会在布局文件中设置我们当前view的背景颜色。如果我们同时设置了windowBackground和布局文件中的background,那就会出现两次绘制,这显然是没有什么意义的,因为最终用户看到的颜色还是以background为准

我们可以通过下面两个方法来解决这个问题

  1. 在xml中设置
 @null

通过代码设置

 getWindow().setBackgroundDrawable(null);

移除控件中不需要的背景

例子:

  1. 列表页(RecyclerView) 与 其内子控件(Item)的背景相同,故可移除子控件(Item)布局中的背景
  2. 对于1个ViewPager+多个 Fragment 组成的首页界面,若每个Fragment 都设有背景色,即 ViewPager 则无必要设置,可移除

所以对于控件背景颜色的设置基本可以归纳为以下两个原则:

  1. 对于子控件,如果其背景颜色跟父布局一致,那么就不用再给子控件添加背景了
  2. 如果子控件背景五颜六色,且能够完全覆盖父布局,那么父布局就可以不用添加背景了

减少透明度的使用

对于不透明的view,只需要渲染一次即可把它显示出来。但是如果这个view设置了alpha值,则至少需要渲染两次。这是因为使用了alphaview需要先知道混合view的下一层元素是什么,然后再结合上层的view进行Blend混色处理。透明动画、淡入淡出和阴影等效果都涉及到某种透明度,这就会造成了过度绘制。可以通过减少渲染这些透明对象来改善过度绘制。比如:在TextView上设置带透明度alpha值的黑色文本可以实现灰色的效果。但是,直接通过设置灰色的话能够获得更好的性能

使用ConstraintLayout减少布局层级

ConstraintLayout,可以翻译为约束布局,在2016年Google I/O 大会上发布。ConstraintLayout相比RelativeLayout,其性能更好,也更容易使用。连官方的hello world都用ConstraintLayout来写了。所以极力推荐使用ConstraintLayout来编写布局

关于ConstraintLayout如何使用,推荐一篇文章讲的非常详细:https://www.jianshu.com/p/17ec9bd6ca8a,所以这里就不过多介绍了。当你熟练使用它之后,相信我,你再也不想用其他布局了!

使用merge标签减少布局层级

我们通过两个例子来认识merge标签

  1. 自定义view时使用merge标签

比如我们现在要写一个自定义viewGroup继承自ConstraintLayout

public class MyViewGroup extends RelativeLayout {    public MyView(Context context) {        this(context, null, 0);    }    public MyView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        initView();    }    public void initView() {        LayoutInflater.from(getContext()).inflate(R.layout.layout_my_view, this, true);    }}

我们通过LayoutInflater将XML加载出view并添加到这个自定义view的根布局中,这时候我们的XML文件就可以这么写。我们在根布局中使用了merge标签,就代表这个xml文件的根布局就是其parent,也就是我们上面的MyViewGroup,这样相比在根布局中使用RelativeLayout就减少了一个布局层级

这里有一个细节需要注意:当我们使用merge标签时,如果我们希望在Design窗口中实时预览布局效果,我们需要使用 tools:parentTag="android.widget.RelativeLayout"来告诉AndroidStudio你的父布局是什么

<?xml version="1.0" encoding="utf-8"?>    
  1. 有时候我们会通 过include标签来提高布局的复用性,如果layout_include_xx.xml的布局和其父布局使用的是同一个布局类型,如线性布局等。这时候就可以在layout_include_xx.xml中使用merge标签来减少布局层级

使用ViewStub标签延迟加载

ViewStub是一个不可见的View类,用于在运行时按需懒加载资源,只有在代码中调用了viewStub.inflate()或者viewStub.setVisible(View.visible)方法时才内容才变得可见。这里需要注意的一点是,当ViewStubinflate到parent时,ViewStub就被remove掉了,即当前view hierarchy中不再存在ViewStub,而是使用对应的layout视图代替

通常用于不常使用的控件,如

  • 网络请求失败的提示
  • 列表为空的提示
  • 新内容、新功能的引导,因为引导基本上只显示一次
  • 又或者我们写了一个通用的自定义 View,但其中部分子 View 只在部分情况下才显示

ViewStub标签使用注意点:

  1. ViewStub标签不支持merge标签。因此这有可能导致加载出来的布局存在着多余的嵌套结构,具体如何去取舍就要根据各自的实际情况来决定了

  2. ViewStubinflate只能被调用一次,第二次调用会抛出异常

  3. 虽然ViewStub是不占用任何空间的,但是每个布局都必须要指定layout_widthlayout_height属性,否则运行就会报错

减少自定义View的过度绘制,使用clipRect()

下面我们自定义一个View用来显示多张重叠的图片,效果图如下:

Android布局优化(五)绘制优化—避免过度绘制_第6张图片

onDraw()方法也很简单,就是遍历所有图片,然后绘制出来:

    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        for (int i = 0; i < imgs.length; i++) {            canvas.drawBitmap(imgs[i], i * 100, 0, mPaint);        }    }

显示过度绘制区域:

Android布局优化(五)绘制优化—避免过度绘制_第7张图片

过度绘制比较严重,那么如何解决?

我们先来分析一下为什么会出现过度绘制:以第一张图为例,上面的代码会把整张图都绘制出来了,第二张在第一张上面继续绘制,这就造成了过度绘制

那么,解决办法也很简单,对于前面的n-1张图,我们只需要绘制一部分即可,对于最后一张才绘制完整的。

Canvas中的clipRect()方法能够设置一个裁剪矩形,只在这个矩形区域内的内容才能够绘制出来

优化后的代码如下:

protected void onDraw(Canvas canvas) {    super.onDraw(canvas);    for (int i = 0; i < imgs.size(); i++) {        canvas.save();        if (i < imgs.size() - 1) {            //前面的n-1张图,只裁剪一部分            canvas.clipRect(i * 100, 0, (i + 1) * 100, imgs.get(i).getHeight());        } else if (i == imgs.size() - 1) {            //最后一张,完整的            canvas.clipRect(i * 100, 0, i * 100 + imgs.get(i).getWidth(), imgs.get(i).getHeight());        }        canvas.drawBitmap(imgs.get(i), i * 100, 0, mPaint);        canvas.restore();    }}

优化后的效果图如下:

Android布局优化(五)绘制优化—避免过度绘制_第8张图片

所有区域都是蓝色的,即只有1次过度绘制。

Canvas除了clipRect()方法外,还有clipPath()等方法,优化时选择合理的方法去裁剪即可

总结

布局加载优化主要从IO反射为突破口,也可以通过异步加载从侧面环境这个问题。而布局绘制优化致力于解决过度绘制问题。本系列文章(布局优化)到此就结束了,希望对你有所帮助

更多相关文章

  1. Android 线性布局详解
  2. Android的相对布局属性的解释
  3. 相对布局RelativeLayout的妙用:提示标签在输入框内
  4. Android开发布局系列: LinearLayout布局实现垂直水平居中
  5. 【Android-002】【常见布局】
  6. android布局的一些知识

随机推荐

  1. communicate with service on android
  2. Chromium Webview for Android
  3. Android编译出错:app:checkDebugDuplicate
  4. Starting an Activity(开始活动)
  5. Fragment嵌套时注意点
  6. video conference s/w
  7. AsyncTask executeOnExecutor
  8. android按钮样式
  9. 百度SDK定位
  10. Timer的使用