App 布局优化的原理以及各种实际方法案例

作为性能优化的第二篇文章,就来讲讲 App 的布局优化。



零、前言

绝大多数开发者,在实际开发中,都是拿到设计图后,就开始在 xml 文件中写布局。布局一旦写完,符合设计要求,就再也没修改过了,如果你这做了,那么本篇文章对你可能有点帮助。



一、Android 绘制原理


1.1 CPU 和 GPU

cpu 大家应该不陌生,“中央处理器”,除了要负责逻辑计算外,还要做其它一系列操作。随着各种复杂 app 的出现,实际运算性能会大打折扣。

为了提高图形实现效率以及复杂的图形,诞生了 GPU,主要功能就是帮助 CPU 分担图形显示,负责栅格化操作。

为什么要提及这两个概念?

因为渲染操作通常就依赖这两个核心组件:CPU 和 GPU。下面给一张图来具体说明下:

CPU、GPU

  1. 蓝色的 Control 为控制器,用于协调控制整个 CPU 的运行,包括取出指令、控制其他模块的运行等。

  2. 绿色的 ALU ( Arithmetic Logic Unit )是算术逻辑单元,用于进行数学、逻辑运算。

  3. 橙色的 Cache 和 DRAM 分别为缓存和 RAM ,用于存储信息。

从结构图可以看出, CPU 的控制器较为复杂,而 ALU 数量较少。因此 CPU 擅长各种复杂的逻辑运算,但不擅长数学尤其是浮点运算。 我们页面是一个个像素点,以16进制展示,假如某个背景要从白色到红色,明显就是进制之间的转化,那么 ALU 显然擅长这个,所以在 GPU 中 ALU 数量较多的原因。


1.2 xml 布局显示到屏幕流程

xml 布局写过不知道多少,那么是如何显示到屏幕上的呢?

1. <Button width="">   例如一个按钮
2. LayoutInflater 加载进内存
3. CPU 计算,处理成位图
4. CPU 将图形交给 GPU
5. GPU 对图形栅格化操作
6. 显示器显示

1.3 帧率

谈及帧率,我们就需要知道一个知识点:Android 系统每隔 16ms 发出 VSYNC 信号(1000ms/60=16.66ms) ,触发对 UI 进行渲染。

我们常说的 60HZ 代表了屏幕在一秒内刷新屏幕的次数,取决于硬件的固定参数。
fps 代表了 GPU 在一秒内绘制操作的帧数。

如果每次渲染都成功这样就能够达到流畅的画面所需要的 60 fps ,为了能够实现 60 fps ,这意味着计算渲染的大多数操作都必须在 16ms 内完成。与手机交互过程中,如触摸和反馈 60 帧以下人是能感觉出来的。 60 帧以上不能察觉变化,当帧率低于 60 fps 时感觉的画面的卡顿和迟滞现象。


1.4 卡顿和过度绘制

在我们的实际开发中,会存在画面卡顿以及过度绘制两种现象。

画面卡顿也就是我们的帧率,即每秒看到的画面小于60,那么就会出现卡顿,出现丢帧现象。

如果每隔16ms,CPU 传递过来的图形有重复的位置,会造成用户只能看到顶层画面,而底层画面被覆盖,底层部分的绘制虽然用户无法看到,但同样也占据了计算资源,造成了不必要浪费,这就叫做过度绘制。



二、优化方向

通过上面的原理可知,我们布局的优化主要有两个方面:CPU 减少 xml 转换成对象的时间 以及 GPU 减少重复绘制的时间。



三、优化各种方案


3.1 测量页面绘制时间

在讲优化方案之前,我们先来知道我们自己所写的页面,帧率是否处于60以上,直接上代码:

public class FpsUtil {

    private long startTime;

    private int count = 0;

    private static final long INTERVAL = 160 * 1000 * 1000;// 160ms

    public void getFps() {
        Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {

            @Override
            public void doFrame(long frameTimeNanos) {
                if (startTime == 0) {
                    startTime = frameTimeNanos;
                }
                long interval = frameTimeNanos - startTime;
                if (interval > INTERVAL) {
                    double fps = (((double) (count * 1000 * 1000)) / interval) * 1000L;
                    LogUtil.d("FPS", "fps=" + fps);
                    startTime = 0;
                    count = 0;
                } else {
                    count++;
                }
                Choreographer.getInstance().postFrameCallback(this);
            }
        });
    }
}

这是一个测量页面帧率的帮助类,在任意页面中使用该帮助类,可以知道此页面的实时帧率


3.2 背景优化
  • 移除 Window 默认的 backgroud 背景,谷歌自带的 theme 中每个页面是有默认背景的,如果我们在页面中加背景,那么就有两层绘制了,所以可以去除默认的背景,具体做法是:
去掉所有activity主题设置中的属性,直接在styles.xml中设置
<item name="android:windowBackground">@null</item>
  • 移除 XML 布局文件中的非必须的 backgroud(这一点还是很细节,编程习惯好的会注意)
  • 按需显示站位图片(例如从后台获取的图片有就显示,没有可以给个透明背景站位,或者站位图)

3.3 布局选择优化
  • 减少嵌套层级,在完成业务需求的前提下,尽量减少层级,如布局较为简单,优先使用线性布局
  • 使用 ConstraintLayout 约束布局。现在创建的 xml 文件默认都是使用 ConstraintLayout 作为根布局的,这种布局在处理复杂页面上很有效果,能较少不少层级嵌套,当然有个前提,如果页面较为简单,还是优先线性布局。理由是简单页面,线性布局加载时间是最短的。

3.4 自定义View的过度绘制优化

如果我们项目中用到了自定义View,在你绘制时,有些区域时重叠的,那么可以使用 canvas.clipRect 来帮助系统识别哪些是可见区域。

也就是我们在进行自定义View 的时候,需要考虑下有些绘制区域是否需要进行相应的裁剪,来减少过度绘制。


3.5 优化标签使用

对于复杂的页面,我们还可以使用一些优化标签,例如 include、merge、ViewStub 等,这些就不举例了,项目中应该都使用过。


3.6 xml 加载优化

前面提到过优化的两个方向,其中一个就是减少 xml 转换成对象的时间,在这之前,我们要知道 xml 布局加载原理:

1. setContentView
2. LayoutInflater(inflate)
3. XmlREsourceParser(getLayout)
4. inflate
5. createViewFromTag
6. tryCreateView
7. createView
8. View

从上面的流程中可以看出 读取 xml 文件采用了 IO 流,创建 View 的过程使用了反射操作,这都是耗时操作,所以也有相应的优化方法。

有一个插件是 X2C ,原理是 APT 编译器翻译 XML 为 Java 代码,感兴趣的可以使用下。

在我的项目中是没有使用了,因为我认为如果项目中能做到上面的这些优化细节,那么布局优化也就到位了。



写在文末

纸上得来终觉浅,绝知此事要躬行。 《冬夜读书示子聿》-- 陆游

好了,关于 App 布局优化的各种方案 就说完了,各位小伙伴可以在项目中借鉴文章中给出的思路优化实战一把。


码字不易,如果本篇文章对您哪怕有一点点帮助,请不要吝啬您的点赞,我将持续带来更多优质文章。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值