对于手机APP而言,流畅度的重要性是不言而喻。为了提升流畅度,Google对Android系统进行了大量的优化,包括使用了GPU硬件加速,引入了VSync,把Dalvik换成了Art等。那么对于开发者而言,应该如何提升手机App的流畅性呢。

    1.定量测评手机流畅性

       对于界面流畅性,很多人会想到FPS。在Android中,系统获取FPS的原理是这样的:手机屏幕显示的内容是通过Android系统的surfaceFLinger类,把当前系统里所有的进程需要显示的信息合成一帧,然后提交到屏幕进行显示,FPS就是1s内SufaceFLinger提交到屏幕的帧数。
      如果当前界面是静止的,并没有进程需要展示信息,那么可能当前FPS就是0,然后此时用户的体验却并不觉得卡顿,因此,依靠FPS来判断流畅度并不合理。

      为什么定量测评手机流畅性,我们需要寻找手机卡顿现象出现的原因。

    Android显示画面的机制,我们可以简单理解为:首先应用层绘制到缓冲区,CPU准备数据,其中CPU主要负责Measure,Layout,Record,Execute的数据计算工作,GPU负责栅格化,渲染。图形驱动层维护了CPU和GPU之间的关联,他们是通过一个队列来联系的。CPU把数据传入到队列中,GPU从这个队列中取出数据来进行绘制,才最终在屏幕上显示出来。

    Android在4.1版本引入了VSync机制,我们可以简单得理解为定时中断。我们暂且定为1s内发出60次VSync信号,即每隔16ms,Android系统发出一次Vsync信号,当Vsync信号到来时会触发对UI的渲染。理想状态,每次中断信号到来,UI就会正常渲染,这时1秒内渲染了60次,系统达到了最佳流畅度。

    但是如果某一次一个操作花费了20ms,那么在中断信号到来的时候,缓存中的数据没有准备好,没办法进行绘制,那么老的界面会占据下一个16ms,即有一个画面持续显示了32ms,这时用户就会有卡的感觉出现了。我们可以称之为掉帧。

    

android fps流畅度优化 手机fps优化_数据


       上图中,B缓存的数据没有准备好,导致A缓存的数据显示了两个16ms,造成了卡顿掉帧。

      清楚了APP界面卡顿的由来,我们可以定量的判定APP卡顿的程度。理想状态下,1s内,app界面会执行60次中断,展示60个界面,如果出现了卡顿,那么绘制次数就少于这60次,次数越少就说明越卡。

    Android提供了Choregrapher这个类,它的作用在于协调animations、input以及drawing的时序,并且每个Looper共用一个Choregrapher对象。

    在Choregrapher中有一个回调接口,叫做FrameCallback    

public interface FrameCallback { 
   public void doFrame(long frameTimeNanos); 
}

当新的一帧被绘制时,doFrame方法就会被调用。我们可以继承这个方法,在doFrame中统计两帧绘制的时间,并进一步统计App的流畅度。

2.优化手机卡顿策略

    假如有一天,我们在分析自身产品和竞队产品时,发现竞队产品的流畅度要优于我们。那么我们应当如何进行优化呢?

2.1 优化UI过度绘制和界面布局不合理的问题

进入手机开发者选项,打开调试GPU过度绘制的的开关。




android fps流畅度优化 手机fps优化_android fps流畅度优化_02




颜色

像素绘制次数

无色

1

蓝色

2

绿色

3

浅红

4

暗红

大于5

当界面上有大片绿色乃至红色的区域后,开发者就应当进行重视,并开始着手优化。

在很多情况下,我们会为了贪图方便对控件设置白色等,尽管不设置的话,控件会显示背景的颜色,也会达到相同的效果。在这种情况下,我们就可以进行优化,减少过度绘制的情况出现。

此外,布局不合理会导致嵌套层级过深,从而影响界面渲染的效率。

关于这点,我们可以通过ADM中的Viewer来查看界面控件的嵌套层级,也可以通过Hierarchy Viewer来查看无用的嵌套层级。

在开发中,在修改代码后,常常会出现无用的层级嵌套,比如LinearLayout嵌套LinearLayout,得益于日益强大的硬件设施,这种情况并不会为开发者所在意,但是日积月累就会影响APP的性能。此外,LinearLayout的滥用也会导致UI层级加深,推荐如果一个布局内的元素超过了两个,如果没有明显的线性关系的话,就使用RelativeLayout。此外,就是对于开发者的要求,如果要实现一段文字,文字的后三个字可以实现跳转。

android fps流畅度优化 手机fps优化_数据_03

可能有人会使用LinearLayout中设置两个textview控件,这样灵活,更主要是简单。但是事实上我们也可以使用spannablestring和clickspan来实现同样的效果,并且不用加深层级。灵活应用merge和viewstub可以大大减少层级,只是对于它们的使用有时候会增加代码的复杂度和影响复用性,因此要根据实际情况来使用。

2.2 代码性能

    Android Studio通过Android Lint进行静态扫描代码的方式发现在代码中潜在的问题,并给出问题的原因和在代码中的位置,并给出相应的优化建议。

    Android Lint的使用很简单,从工具栏上的Analyze —— Inspect Code,在弹出的对话框中选择需要扫描的代码范围,就可以进行扫描了。我们主要关注结果中的performance选项。

    这里主要有以下几个选项:

    1)避免在draw/layout时分配对象。我们在定义自定义控件时常常会忽略这个细节,事实上在界面渲染时,系统会频繁调用onDraw,onLayout等函数,分配临时对象时会引起不必要的GC。

    2)Recycle:某些资源,比如TypedArrays,VelocityTrackers,在使用完之后应该被回收,但是却忘记了回收。

    3)布局优化:比如可以使用CompoundDrawable一个控件来实现需要ImageView和TextView组成的线性布局才能达到的效果,XML布局中避免嵌套weight,这样会拖累执行效率,去掉Layout中的无用的参数等等,在开发时,IDE也常常会对不规范的地方进行黄色标注,注意细节即可。

    4)避免内存泄漏:例如关注Handler使用不当造成的内存泄漏,因为在使用Handler时,Handler常常会隐式持有当前上下文,因此使用Handler时需要注意,避免造成内存泄漏。

    5)去除没有用到的资源。

    6)移除没用的父类布局

    .............

相关的选项还有很多,读者可以自己多做尝试。

2.3 代码逻辑层优化

     这方面的优化,主要是找出在主线程中耗时较大的函数,通过优化逻辑去减少API的耗时,分析CPU的工作,尽量让cpu执行主线程中的工作。

      这方面的工作有很多,比如优化逻辑去减少API的耗时,缓存数据以方便更快地对数据进行加载,把耗时的操作移出主线程,在子线程中进行操作。

       我们可以通过traceView来对应用线程进行分析。它能帮我们了解要跟踪程序的性能,并能够具体到每一个函数的耗时和调用次数。对于主线程中占用CPU时间过长的函数以及主线程调用过多的函数,我们要注意,并尽力去优化。

      需要着重的一点是:IO在APP中往往是最耗资源的。这里主要讲一下SharedPreferences优化以及数据库使用及优化。

      SharedPreferences实际上是对一个XML文件存储的key-value键值对,每一次commit和apply操作都是一次I/O操作。IO性能是代码优化的重中之重。在处理SharedPreferences操作时,我们可以注意以下几点。

    (1)  避免频繁地读写SharedPreferences,建议最后进行统一的数据读写

  (2)Editor有apply和commit这两个提交数据的方法,区别在于commit是同步写入,apply是异步写入,因此在不需要返回值的情况下,使用apply可以极大地提高性能。

   数据库的优化主要是对于sqlite的使用优化。这里提出以下建议。

   (1)数据库的初始化在应用启动阶段就要准备好,建议放在Application的onCreate方法中。便于掌控数据库的生命周期。

   (2)通过Android提供的SQLiteStatement类来代替ContentValues来将数据插入数据库中,这可以在一定程度上提高性能,也可以解决依赖注入的问题。

  (3)显式使用事物。事实上每次插入一条数据,程序都是隐式创建一个事务。如果我们要插入一万条数据,那么就会创建一万个事务,大大影响了数据插入的效率。因此最佳的做法就是显式创建事务。使得插入一万条数据,只创建一个事务。

db.beginTransaction();
  插入一万条数据。
db.endTransaction();

  (4)异步处理数据库。将一些常用的数据放到缓存中,在异步更新到数据库中。可以把所有的数据库操作都统一放到一个线程队列当中。

  (5)对数据库表结构进行优化。

     以上只是对于App卡顿优化的一些建议,现实开发中当然要复杂很多。需要注意的一点是,养成良好的编程风格,优良的程序架构,对于流畅的App也具有着巨大的作用。

参考书籍:

《Android性能优化最佳实践》

《移动APP性能测评和优化》