2849 字
14 分钟
App绘制优化

App绘制优化#

1、你在做布局优化的过程中用到了哪些工具?#

我在做布局优化的过程中,用到了很多的工具,但是每一个工具都有它不同的使用场景,不同的场景应该使用不同的工具。下面我从线上和线下两个角度来进行分析。

比如说,我要统计线上的FPS,我使用的就是Choreographer这个类,它具有以下特性:

  • 1、能够获取整体的帧率
  • 2、能够带到线上使用。
  • 3、它获取的帧率几乎是实时的,能够满足我们的需求。

同时,在线下,如果要去优化布局加载带来的时间消耗,那就需要检测每一个布局的耗时,对此我使用的是AOP的方式,它没有侵入性,同时也不需要别的开发同学进行接入,就可以方便地获取每一个布局加载的耗时。如果还要更细粒度地去检测每一个控件的加载耗时,那么就需要使用LayoutInflaterCompat.setFactory2这个方法去进行Hook。

此外,我还使用了LayoutInspector和Systrace这两个工具,Systrace可以很方便地看到每帧的具体耗时以及这一帧在布局当中它真正做了什么。而LayoutInspector可以很方便地看到每一个界面的布局层级,帮助我们对层级进行优化。

2、布局为什么会导致卡顿,你又是如何优化的?#

分析完布局的加载流程之后,我们发现有如下四点可能会导致布局卡顿:

  • 1、首先,系统会将我们的Xml文件通过IO的方式映射的方式加载到我们的内存当中,而IO的过程可能会导致卡顿。
  • 2、其次,布局加载的过程是一个反射的过程,而反射的过程也会可能会导致卡顿。
  • 3、同时,这个布局的层级如果比较深,那么进行布局遍历的过程就会比较耗时。
  • 4、最后,不合理的嵌套RelativeLayout布局也会导致重绘的次数过多。

对此,我们的优化方式有如下几种:

  • 1、针对布局加载Xml文件的优化,我们使用了异步Inflate的方式,即AsyncLayoutInflater。它的核心原理是在子线程中对我们的Layout进行加载,而加载完成之后会将View通过Handler发送到主线程来使用。所以不会阻塞我们的主线程,加载的时间全部是在异步线程中进行消耗的。而这仅仅是一个从侧面缓解的思路。
  • 2、后面,我们发现了一个从根源解决上述痛点的方式,即使用X2C框架。它的一个核心原理就是在开发过程我们还是使用的XML进行编写布局,但是在编译的时候它会使用APT的方式将XML布局转换为Java的方式进行布局,通过这样的方式去写布局,它有以下优点:1、它省去了使用IO的方式去加载XML布局的耗时过程。2、它是采用Java代码直接new的方式去创建控件对象,所以它也没有反射带来的性能损耗。这样就从根本上解决了布局加载过程中带来的问题。
  • 3、然后,我们可以使用ConstraintLayout去减少我们界面布局的嵌套层级,如果原始布局层级越深,它能减少的层级就越多。而使用它也能避免嵌套RelativeLayout布局导致的重绘次数过多。
  • 4、最后,我们可以使用AspectJ框架(即AOP)和LayoutInflaterCompat.setFactory2的方式分别去建立线下全局的布局加载速度和控件加载速度的监控体系。

3、做完布局优化有哪些成果产出?#

  • 1、首先,我们建立了一个体系化的监控手段,这里的体系还指的是线上加线下的一个综合方案,针对线下,我们使用AOP或者ARTHook,可以很方便地获取到每一个布局的加载耗时以及每一个控件的加载耗时。针对线上,我们通过Choreographer.getInstance().postFrameCallback的方式收集到了FPS,这样我们可以知道用户在哪些界面出现了丢帧的情况。
  • 2、然后,对于布局监控方面,我们设立了FPS、布局加载时间、布局层级等一系列指标。
  • 3、最后,在每一个版本上线之前,我们都会对我们的核心路径进行一次Review,确保我们的FPS、布局加载时间、布局层级等达到一个合理的状态。

4、你是怎么做卡顿优化的?#

从项目的初期到壮大期,最后再到成熟期,每一个阶段都针对卡顿优化做了不同的处理。各个阶段所做的事情如下所示:

  • 1、系统工具定位、解决
  • 2、自动化卡顿方案及优化
  • 3、线上监控及线下监测工具的建设

我做卡顿优化也是经历了一些阶段,最初我们的项目当中的一些模块出现了卡顿之后,我是通过系统工具进行了定位,我使用了Systrace,然后看了卡顿周期内的CPU状况,同时结合代码,对这个模块进行了重构,将部分代码进行了异步和延迟,在项目初期就是这样解决了问题。但是呢,随着我们项目的扩大,线下卡顿的问题也越来越多,同时,在线上,也有卡顿的反馈,但是线上的反馈卡顿,我们在线下难以复现,于是我们开始寻找自动化的卡顿监测方案,其思路是来自于Android的消息处理机制,主线程执行任何代码都会回到Looper.loop方法当中,而这个方法中有一个mLogging对象,它会在每个message的执行前后都会被调用,我们就是利用这个前后处理的时机来做到的自动化监测方案的。同时,在这个阶段,我们也完善了线上ANR的上报,我们采取的方式就是监控ANR的信息,同时结合了ANR-WatchDog,作为高版本没有文件权限的一个补充方案。在做完这个卡顿检测方案之后呢,我们还做了线上监控及线下检测工具的建设,最终实现了一整套完善,多维度的解决方案。

5、你是怎么样自动化的获取卡顿信息?#

我们的思路是来自于Android的消息处理机制,主线程执行任何代码它都会走到Looper.loop方法当中,而这个函数当中有一个mLogging对象,它会在每个message处理前后都会被调用,而主线程发生了卡顿,那就一定会在dispatchMessage方法中执行了耗时的代码,那我们在这个message执行之前呢,我们可以在子线程当中去postDelayed一个任务,这个Delayed的时间就是我们设定的阈值,如果主线程的messaege在这个阈值之内完成了,那就取消掉这个子线程当中的任务,如果主线程的message在阈值之内没有被完成,那子线程当中的任务就会被执行,它会获取到当前主线程执行的一个堆栈,那我们就可以知道哪里发生了卡顿。

经过实践,我们发现这种方案获取的堆栈信息它不一定是准确的,因为获取到的堆栈信息它很可能是主线程最终执行的一个位置,而真正耗时的地方其实已经执行完成了,于是呢,我们就对这个方案做了一些优化,我们采取了高频采集的方案,也就是在一个周期内我们会多次采集主线程的堆栈信息,如果发生了卡顿,那我们就将这些卡顿信息压缩之后上报给APM后台,然后找出重复的堆栈信息,这些重复发生的堆栈大概率就是卡顿发生的一个位置,这样就提高了获取卡顿信息的一个准确性。

6、卡顿的一整套解决方案是怎么做的?#

首先,针对卡顿,我们采用了线上、线下工具相结合的方式,线下工具我们册中医药尽可能早地去暴露问题,而针对于线上工具呢,我们侧重于监控的全面性、自动化以及异常感知的灵敏度。

同时呢,卡顿问题还有很多的难题。比如说有的代码呢,它不到你卡顿的一个阈值,但是执行过多,或者它错误地执行了很多次,它也会导致用户感官上的一个卡顿,所以我们在线下通过AOP的方式对常见的耗时代码进行了Hook,然后对一段时间内获取到的数据进行分析,我们就可以知道这些耗时的代码发生的时机和次数以及耗时情况。然后,看它是不是满足我们的一个预期,不满足预期的话,我们就可以直接到线下进行修改。同时,卡顿监控它还有很多容易被忽略的一个盲区,比如说生命周期的一个间隔,那对于这种特定的问题呢,我们就采用了编译时注解的方式修改了项目当中所有Handler的父类,对于其中的两个方法进行了监控,我们就可以知道主线程message的执行时间以及它们的调用堆栈。

对于线上卡顿,我们除了计算App的卡顿率、ANR率等常规指标之外呢,我们还计算了页面的秒开率、生命周期的执行时间等等。而且,在卡顿发生的时刻,我们也尽可能多地保存下来了当前的一个场景信息,这为我们之后解决或者复现这个卡顿留下了依据。

7、卡顿的主要场景?#

卡顿的主要场景有很多,按场景可以分成4类:UI绘制、应用启动、页面跳转、事件响应,其中又可细分为如下:

1、UI#
  • 绘制
  • 刷新
2、启动#
  • 安装启动
  • 冷启动
  • 热启动
3、跳转#
  • 页面间跳转
  • 前后台切换
4、响应#
  • 按键
  • 系统事件
  • 滑动

而造成其产生的根本原因可以分为两大类:

1、界面绘制#
  • 绘制层级深
  • 页面复杂
  • 刷新不合理
2、数据处理#
  • 数据处理在UI线程
  • 占用CPU高,导致主线程拿不到时间片
  • 内存增加导致GC频繁,从而引起卡顿

需要更全面更深入的理解请查看 Android性能优化之绘制优化

深入探索Android布局优化(上)

深入探索Android布局优化(中)

深入探索Android布局优化(下)

深入探索Android卡顿优化(上)

深入探索Android卡顿优化(下)

App绘制优化
作者
强人自传
发布于
2024-02-29
许可协议
CC BY-NC-SA 4.0