前言

Android功能优化不是一个能完全讲解清楚的标题。Android中的功能优化触及的内容实在过分广泛,需求把握的技能实在太多,且详细的项目所运用的优化计划也大不相同。想全面讲解功能优化,是万万不能的,实践上目前我学习到的还差得很远。

本专题内容包含对过往作业、技能学习的总结,以及对优化方向的考虑与梳理。内容包含的点或许不够全面,其实也没必要做到全面,更多的是考虑和实践。

系列估计分为五篇:

  • 《“总算懂了“系列:Android功能优化(一)流通度优化—FPS提高实战》
  • 《“总算懂了“系列:Android功能优化(二)包体积优化—减包实战》
  • 《“总算懂了“系列:Android功能优化(三)内存优化
  • 《“总算懂了“系列:Android功能优化(四)动态策略》
  • 《“总算懂了“系列:Android功能优化(五)降本增效与AB实验》

再加上之前的《发动优化》,基本上相对重要的Android功能优化的方向都会触及到。

本篇就首先来介绍我以为在功能优化中地位仅次于包体积优化、发动速度优化的流通度优化

一、流通度

1.1 流通度认知

流通度在本篇中是指 可滑动列表在滑动时的流通度,流通度越高则体会越好。流通度优化,便是让列表滑动地更流通,以希望带来像留存率停留时长等事务目标的收益。

它和所谓的 布局优化卡顿优化制作优化 仍是有区别的:流通度优化有确定的衡量目标——fps,fps越大则滑动时的体会越流通。也便是说,流通度优化是 有目标衡量的、且目标能反映用户直接体会好坏的 优化方向。

fps,每秒帧数,即帧率单位。可见文章《Android屏幕改写机制》

像电商、新闻等典型app的中心页面都是一个可滑动的列表,用户滑动列表以浏览更多的商品或信息,那么滑动时的流通程度是影响用户是否持续滑动的一个重要因素。手指滑动时 列表不跟手、滑动出现显着卡顿等这些问题 咱们是需求竭力避免的。

1.2 准备常识

想要处理滑动流通度问题、提高fps,需求把握较多的技能点:

  • View作业原理,包含三大流程measure/layout/draw,自定义view等
  • 屏幕改写机制,包含VSync、Choreographer、fps的计算
  • 烘托流程,包含UIThread与RenderThread、CPU与GPU 别离经过哪些步骤
  • 滑动列表RecycleView的原理,包含四级缓存、onCreateViewHolder与onBindViewHodler的调用机遇等

这儿罗列的是本篇强相关的技能点,功能优化自身是对触及技能点地归纳运用,需求具有厚实的基础常识。

1.3 优化东西

在流通度优化中所运用的东西最重要的有2个:

  • Systrace,功能数据采样和剖析东西,经过生成的Systrace文件能够帮助剖析问题,是Android功能优化中有必要把握的东西。在文章《发动优化》中有介绍过,《Systrace系列》能够帮你全面深入学习Systrace。另外还有更便利的根据Systrac的btrace,高功能且支持主动注入自定义事情
  • GPU出现模型剖析(HWUI出现形式剖析),以滚动直方图的形式直观地显现烘托界面窗口帧所花费的时刻

此外老练的功能优化计划 除了实施优化外,还应包含 线上监控APM东西防劣化计划,本篇不会触及。

1.4 优化计划

在许多介绍 布局优化、卡顿优化、制作优化的文章中,提到的处理卡顿问题计划有许多:

  • 削减view层级、异步加载view、运用x2c框架
  • 经过Looper设置Printer来监控并处理主线程耗时函数
  • 滑动时暂停后台下载使命/IO读写,例如在列表idle时才加载图片/视频
  • 对各种IPC成果进行缓存

这些计划在实践项目中也的确能带来不错的收益,可是在项目的流通度优化中经实验对比却没有获得fps的较大提高。而最终使fps有大幅提高的计划是 处理一切帧的公共问题——重度制作,也是本篇重点介绍的内容。

二、Android烘托流程

在需求的准备常识中,View作业原理、屏幕改写机制 我之前有文章做了专门的介绍,网上关于RecycleView原理的文章也是比较多的。关于烘托流程则是一个被提及比较少的常识点,本节会整体介绍烘托流程,以及与GPU出现模型剖析图的联系。

2.1 烘托流程

由《Android屏幕改写机制》咱们知道,屏幕上每一帧的烘托都要从 VSync开始,会先在UI线程处理 Input、Animations、Traversal(measure/layout/draw)事情,在draw中(现在Android默认敞开GPU硬件加速)会产生用来描制作作行为的DisplayList。

然后UI线程把DisplayList同步给烘托线程 RenderThread,RenderThread这儿做一些优化的操作,到这儿都是在CPU中完结。接着RenderThread把制作信息提交给 GPU 进行制作(这儿会进行dequeueBuffer),当制作结束后经过 queueBuffer把Buffer放回到 BufferQueue里。最终在Vsync-sf时SurfaceFlinger会将Frame Buffer进行组成,然后咱们就能够在屏幕上看到这一帧了。

“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战
  • 榜首个阶段,其实主要做的便是构建DrawOp树(里边封装OpenGL烘托指令),一同,预处理分组一些相似指令,以便提高GPU处理效率,这个阶段主要是CPU在作业,不过这个阶段前期运行在UI线程,后期部分运行在RenderThread(烘托线程)。如下图
“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战
  • 第二个阶段主要运行在烘托线程,CPU将数据同步给GPU,之后,告诉GPU进行烘托,不过这儿需求留意的是,CPU一般不会堵塞等候GPU烘托结束,而是告诉结束后就回来,除非GPU十分繁忙,来不及呼应CPU的请求,没有给CPU发送告诉,CPU才会堵塞等候。 CPU回来后,会直接将GraphicBuffer提交给SurfaceFlinger,告诉SurfaceFlinger进行组成,SF组成后提交显现,如此完结图像的烘托显现。

示意图:

“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战

烘托流程在Systrace图中的描绘:

“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战

2.2 GPU出现模型剖析

烘托流程的耗时能够经过东西——GPU出现模型剖析 来剖析,这十分有助于耗时点寻找和剖析。

“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战

绿色的横线是16.6ms基准线;每一个竖条就代表一个帧的制作流程,色彩块及其长度则是对应某个阶段所用的相对时刻,详细如下:

“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战

色彩称谓从低向上青色、深绿色、浅绿色、深蓝色、浅蓝色、赤色、黄色

  • VSync推迟:收到VSync信号到履行此次制作的时刻间隔。收到VSync信号后会post一个Message放入行列,当UI线程有耗时操作,那么handleMessage/doFrame就会被推迟。一般前一帧制作较久,那么本帧就会被推迟

  • 输入和动画:编舞者doFrame中履行InputCallback、AnimationCallback的时刻

  • 丈量/布局:编舞者doFrame中履行TraversalCallback的的performMeasure/performLayout的时刻

  • 制作:编舞者doFrame中履行TraversalCallback的的performDraw的时刻

  • 同步和上传:主线程与烘托线程同步烘托数据、将位图信息上传到 GPU 所花的时刻。

  • 指令问题(宣布指令):CPU-RenderThreader将制作显现列表的指令发送给GPU所花的时刻。之后,GPU才能根据这些OpenGL指令进行烘托。

  • 交流缓冲区:之前的GPU指令被发送结束后,CPU一般会发送最终一个指令给GPU,告诉GPU当时指令发送结束,能够处理,GPU一般而言需求回来一个确认的指令,不过,这儿并不代表GPU烘托结束,仅仅是告诉CPU,GPU有空开始烘托罢了,并未烘托完结,可是之后的问题APP端无需关怀了,CPU能够持续处理下一帧的使命了。假设GPU比较忙,来不及回复告诉,则CPU需求堵塞等候,直到收到告诉,才会唤起当时堵塞的Render线程,持续处理下一条消息,这个阶段是在swapBuffers中完结的。

尽管此东西名为“GPU 烘托形式剖析”,但一切受监控的进程实践上发生在 CPU 中。经过将指令提交到 GPU 来触发烘托,GPU 也会异步烘托屏幕。在某些情况下,GPU 或许会有太多作业要处理,因而您的 CPU 有必要先等候一段时刻,然后才能提交新指令。假设发生这种情况,您将看到橙色竖条和赤色竖条上出现峰值,且指令提交将被阻挠,直到 GPU 指令行列中腾出更多空间。

三、滑动列表常见fps劣化场景

了解了烘托流程,以及对应的GPU出现模型剖析图,那么就来看看滑动列表在滑动时的现象。咱们模拟各阶段的耗时,用来测试和深度了解。

首先看看正常无耗时的滑动列表,在滑动时的GPU出现模型剖析图(疏忽图中右上角,看底部GPU出现图即可):

“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战

下面咱们别离来看不同场景下对应的GPU出现模型剖析图有什么特点。

3.1 onBindViewHolder

咱们在onBindViewHolder做一个耗时操作:

class PerfAdapter(layoutId:Int): BaseQuickAdapter<PerfBean, PerfItemViewHolder>(layoutId) {
    override fun convert(holder: PerfItemViewHolder, item: PerfBean) {
        try {
            Thread.sleep(30)
        } catch (e: InterruptedException) {
        }
        holder.binding?.tvPerfName?.text = item.name
    }
}

3.1.1 青色

滑动时假设被触发的 onBindViewHolder 的触发来自recycleView的prefetch,那么在接收到VSync信号后这一帧的doframe却被当时UI线程的Message—onBindViewHolder耗时耽误了履行,这个便是VSync推迟了,即这一帧占比最大的便是青色

“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战
“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战

3.1.2 深绿色

假设被触发的onBindViewHolder 来自doFrame中的InputCallback,那便是这一帧占比最大的便是深绿色。

“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战
“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战
“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战

3.2 onMeasure/onLayout

咱们在在item的根布局的onMeasure做耗时操作:

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    try {
        Thread.sleep(30)
    } catch (e: InterruptedException) {
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}

3.2.1 深绿色

“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战
“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战
“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战

正常滑动时,滑进屏幕的一个item,它的onMeasure/onLayout是来自doFrame中的InputCallback、AnimationCallback,那便是深绿色,即榜首根柱子。

第二根柱子,由于前一帧占用主线程时刻较长,那这一帧的VSync就被推迟了,即青色,即第二根柱子。

onLayout中耗时和onMeasure体现一同。

3.2.2 浅绿色

onLayout/onMeasure需求来自performTraversal

3.2.2.1 页面首帧

刚进入页面后,首帧的每个item的 onLayout和onMeasure 都是来自doFrame的performTraversal。

“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战

3.2.2.2onBindViewHodler中推迟改写的帧

在onBindViewHodler中推迟2秒更新文字,那2s后的item的onMeasure就来自TraversalCallback,即浅绿色

```
class PerfAdapter(layoutId:Int): BaseQuickAdapter<PerfBean, PerfItemViewHolder>(layoutId) {
    override fun convert(holder: PerfItemViewHolder, item: PerfBean) {
        holder.binding?.tvPerfName?.text = item.name
        holder.binding?.root?.postDelayed( {
        holder.binding?.tvPerfName?.text = "我变了"
        } ,3000)
}
}
```
“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战
“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战

onLayout和onMeasure体现一同。

3.3 draw/onDraw/dispatchDraw-深蓝色

Item view的dispatchDraw:

override fun dispatchDraw(canvas: Canvas?) {
    try {
        Thread.sleep(30)
    } catch (e: InterruptedException) {
    }
    super.dispatchDraw(canvas)
}
“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战
“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战

正常滑动时,滑进屏幕的一个item,它的draw/onDraw/dispatchDraw来自从traversalCallback: traversalCallback->Recyleview.draw->Item.draw->Item.dispatchDraw,体现为深蓝色

完整的draw进程: 1. 画背景 2.画自己– onDraw,自己完成 3.画子view– dispatchDraw 4.画装饰,这儿每一个耗时都会体现为深蓝色。

留意:假设是ViewGroup,要设置.setWillNotDraw(false),才会走完整的draw进程,不然只会走dispatchDraw。

3.4 多图-浅蓝色

如下图,列表中有许多图片时 浅蓝色区段 的确增大(在低端机上或许更为显着)。

“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战

3.5 制作指令-赤色

3.5.1 高频制作

draw/onDraw/dispatchDraw中有许多制作指令,即多次调用canvas.drawXXX办法:

 /**
* 众所周知,当咱们自定义一个View时会重写他的3个办法,onMeasure(),onLayout(),onDraw()办法,
* 可是自定义一个ViewGroup的时候要重写onMeasure(),onLayout(),dispatchDraw()这3个办法
* setWillNotDraw(false),才会走draw(canvas: Canvas?),不然直接走了draw(Canvas canvas, ViewGroup parent, long drawingTime)(经过ViewGroup.drawChild())
*/
@RequiresApi(Build.VERSION_CODES.M)
override fun draw(canvas: Canvas?) {
    for (i in 1..1000){
        canvas?.drawCircle(i*5.toFloat(), 80.toFloat(),5f, mPaint)
    }
    mPaint.color = context.getColor(R.color.colorAccent)
    for (i in 1..1000){
        canvas?.drawCircle(i*5.toFloat(), 100.toFloat(),5f, mPaint)
    }
    mPaint.color = context.getColor(R.color.colorPrimary)
    for (i in 1..1000){
        canvas?.drawCircle(i*5.toFloat(), 120.toFloat(),5f, mPaint)
    }
    super.draw(canvas)
}
“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战
  • 可见 的赤色部分都变长:每帧需求宣布的指令都是3000个。(由于是影响到每一帧,所以需特别留意此情况)
  • 当新item出现时会有一帧 深蓝色变长:深绿色是由于,深蓝色是由于7.3

原因是:体系会尽或许地缓存显现列表。因而某些情况下,滚动、转换或动画会要求体系从头发送显现列表(即赤色),但不用实践从头构建它(即从头捕获制作指令)(即draw进程-深蓝色)。因而,您或许会看到“宣布指令”条较高,但“制作指令”条并不高(即赤色高但深蓝色不高)。

“终于懂了“系列:Android性能优化(一)流畅度优化—FPS提升实战

上图是低端机的情况,可见对于低端机来说,每一帧赤色都满了,对fps影响巨大。(实测低端机中50个drawCircle就会形成每帧都超出16.6ms。)

  • 这种情况,不管快滑仍是慢滑,每帧都是满的。
  • 2.1-2.3中的场景,只影响行将出现item的一帧,慢滑时对整体fps影响较小,快滑时影响比较大。

3.5.2 重度制作

canvas.saveLayer相关办法,一次调用就很耗费功能,请不要运用!重要!

3.5节与3.3节的不同

  • 3.3中是单纯draw办法的耗时,只影响行将出现item的那一帧
  • 而本节中的 高频制作指令、重度制作指令,会作用于每一 ****(尽管也是写在draw相关办法中)

总结:当你慢滑时,发现每一 都超出16.6且赤色占比很大,那么就能够判断是制作指令的问题,需求去查itemView中的自定义view的draw相关办法。

3.6 交流buffer-黄色 TODO

怎么让黄色变长呢?GPU繁忙?暂未测试出~

四、实战剖析

在实践项目首页的列表滑动fps优化时,发现在慢滑时:赤色块占有一帧大部分耗时、且是一切帧的共性问题,如3.5节中一样,可见是制作指令的问题。这就需求排查view制作相关代码,尤其是自定义view。最终发现,在列表的item view中运用了较多的自定义圆角view:

//RoundedImageView.java,用于完成圆角图片
@Override
protected void onDraw(Canvas canvas) {
    // 运用图形混合形式来显现指定区域的图片
    canvas.saveLayer(srcRectF, null, Canvas.ALL_SAVE_FLAG);
    ...
    super.onDraw(canvas);
    paint.reset();
    path.reset();
    if (isCircle) {
        path.addCircle(width / 2.0f, height / 2.0f, radius, Path.Direction.CCW);
    } else {
        path.addRoundRect(srcRectF, srcRadii, Path.Direction.CCW);
    }
    ...
    paint.setXfermode(xfermode); 
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) {
        canvas.drawPath(path, paint);
    } else {
        srcPath.reset();
        srcPath.addRect(srcRectF, Path.Direction.CCW);
        srcPath.op(path, Path.Op.DIFFERENCE);
        canvas.drawPath(srcPath, paint);
    }
    paint.setXfermode(null);
    ...
    // 恢复画布
    canvas.restore();
    ...
}

完成制作圆角的计划为 saveLayer+Xfermode混合形式,而此计划中的saveLayer办法则是重度制作办法。

替换计划:运用setOutlineProvider体系办法即可:

this.setOutlineProvider(new ViewOutlineProvider() {
    @Override
    public void getOutline(View view, Outline outline) {
        outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius);
    }
});
this.setClipToOutline(true);

修改后,每帧的赤色条占比大幅下降,实践fps也大幅提高。

五、总结

5.1 疑问点

为啥有的优化操作不达预期,对FPS绝对值提高很有限?

  1. 假设一次滑动有100帧,有80帧每帧耗时40ms,有10帧耗时16.6ms以内,10帧耗时80ms,那么咱们只把80ms的10个帧优化到40ms,那么对FPS均值的提高是有限的(尤其是快速滑动时)。例如只优化了onCreateViewHolder或许onBindViewHodler,那么只对出现新item的帧有影响,这只占很小的比例。
  2. 假设把40ms的80个帧优化到16.6ms以内,80ms的10个帧优化到40ms,那么对FPS均值的提高便是明显的。例如上面实战中,一切帧都有的制作耗时,这影响到一切帧。

5.2 优化剖析思路

先在慢滑状态下,检查GPU出现形式东西,优先看大都帧的一同耗时点,再看非共性问题(例如进入新item时的帧耗时)。详细耗时点剖析,可经过SysTrace剖析

  1. 慢滑,是由于避免两帧之间的搅扰,若当时帧耗时较多,那么很或许会导致下一帧的VSync推迟
  2. 优先看大都帧的耗时点,是由于要优先处理帧耗时的共性问题 进而大幅提高FPS
  3. 运用Systrace打点来剖析详细耗时的代码
  4. UIThread和RenderThread都需求剖析

本篇重点介绍了烘托流程和对应的GPU出现形式剖析图,以及对应色条的了解。然后对滑动阶段各耗时场景进行的详细的剖析,最终进行了优化实战。功能优化需求实在的实践,只要真实做过并取得了明显的收益才会有更深的了解。我们能够针对自己的项目看看有无流通度的问题,尝试去剖析和优化,看看是否能有明显的提高。

好了,本篇就到这儿,欢迎留言评论~

你的 点赞、谈论,是对我的巨大鼓舞!

欢迎重视我的公众号 胡飞洋,文章更新可榜首时刻收到;

假设有问题或许想进群,号内有加我微信的入口,我拉你进技能评论群。在技能学习、个人成长的道路上,咱们一同前进!