GVKun编程网logo

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~(android ui性能优化)

9

在这篇文章中,我们将带领您了解AndroidUI卡顿响应慢性能差怎么办?请用Systrace来分析查看~的全貌,包括androidui性能优化的相关情况。同时,我们还将为您介绍有关AndroidCho

在这篇文章中,我们将带领您了解Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~的全貌,包括android ui性能优化的相关情况。同时,我们还将为您介绍有关Android Choreographer 源码分析 Android 教你如何发现 APP 卡顿、android studio 不出现【start a new android project】, 怎么办?、Android Studio 中 System Trace 的新增功能、Android Studio 里sync下载慢,怎么办?的知识,以帮助您更好地理解这个主题。

本文目录一览:

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~(android ui性能优化)

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~(android ui性能优化)

目录

一、Systrace 简介
二、Systrace 使用方法
三、使用命令行抓取 Systrace
四、使用Systrace 检测UI 性能
五、使用Systrace 检测警告以及掉帧问题
六、查看trace 文件的快捷键
七、代码中添加trace 标记具体分析问题所在
八、使用TraceView 分析trace Log

一、Systrace 简介

Systrace 允许你收集和检查设备上运行的所有进程的计时信息。 它包括Androidkernel的一些数据(例如cpu调度程序,IO和APP Thread),并且会生成HTML报告,方便用户查看分析trace内容。

二、Systrace 使用方法

1. Systrace 的作用

如果想分析Android系统或者某个app的卡顿性能或者渲染问题,这时候Systrace 就非常有用。
首先我们需要抓取Systrace文件,然后分析并找出引起系统卡顿,或者app反应慢的原因,最好在源码上解决引起卡顿、响应慢的问题。

2. 抓取Systrace 的方法

抓取Systrace的方法如下:

    1. 链接手机,打开DDMS

首先链接手机,打开Android Device Monitor,选择要分析的进程(比如com.google.process.gapps),点击Capture system wide trace using Android systrace(下图右上角箭头所指的地方)

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

    1. 配置需要抓取Systrace的内容

此时根据不同的卡顿问题需求,我们配置抓取不同的trace。 抓取的Systrace时间请勿过长,否则会导致抓取内容部分丢失,然后点击OK,操作要分析系统卡顿或app运行缓慢的部分,系统会自动收集运行时的信息,然后用Chrome 浏览器打开生成的trace 文件 。

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

    1. 抓取过多次trace,请及时清理缓存

假如抓取过多次trace,为避免数据丢失,请及时清除缓存中的内容,清理地方在 Android Device Monitor的右下角,如下图所示

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

    1. 使用Chrome 分析trace

Chrome 浏览器打开生成的trace 文件,如下图所示,里面会包含每个cpu,以及图形渲染,输入事件等等内容。

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

抓取的Trace报告提供了Android系统进程在特定时间段内的整体情况。 例如在显示Activity或动画时卡顿,Systrace会提供关于如何解决这些问题的建议。
但是,systrace不会在应用程序进程中收集有关代码执行的信息。 有关您的应用程序执行哪些方法以及使用多少cpu资源的更多详细信息,请使用Android Studio的内置cpuProfiler,或生成跟踪日志并使用Traceview查看它们。

三、使用命令行抓取 Systrace

1. 使用命令行抓取Systrace的准备工作

抓取systrace之前,请完成以下步骤:

  1. 下载并安装Android SDK Tools
  2. 安装Python ,并将其包含在系统环境变量的path中。
  3. 连接手机,打开开发者选项中的USB Debug选项 。
  4. 查找Systrace脚本,存储路径如下:android-sdk/platform-tools/systrace/

2. 使用命令行抓取 Systrace的语法

使用命令行抓取 Systrace的语法如下:
python systrace.py [options] [categories]

3. 使用命令行抓取 Systrace举例

例如,以下命令调用systrace10秒钟内记录设备进程,包括图形进程,并生成一个名为mynewtraceHTML报告:

python systrace.py --time=10 -o mynewtrace.html gfx

如果不指定任何类别或选项,systrace将生成包含所有可用类别的报告,并使用默认设置。 可用的类别取决于您使用的连接设备。

4. Systrace 参数解释

  1. Global options

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

  1. Commands and command options

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

四、使用Systrace 检测UI 性能

systrace对于检查应用程序的UI性能特别有用,因为它可以分析您的代码和帧速率,进而识别问题区域,同时提供可工参考的解决方案。

1.使用Systrace 检测UI 性能

首先,按照以下步骤进行操作:

  1. 连接手机并运行您的app
  2. 使用以下命令运行systrace:
    python systrace.py view --time = 10
  3. 操作您的应用 10秒后,systrace生成一个HTML报告。
  4. 使用网络浏览器打开HTML报告。

使用Chrome 打开生成的trace 文件,检测记录期间设备cpu使用情况,丢帧情况,卡顿耗时情况等等。

五、使用Systrace 检测卡顿丢帧问题

如下Systrace报告列出了每个进程呈现UI frame,并显示沿着时间线的每个渲染帧。 在绿色框架圆圈中,是指在16.6ms内呈现每秒稳定60帧。 花费16.6ms以上渲染的帧用黄色红色圆圈表示。

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

在运行Android 5.0(API级别21)或更高版本的设备上, UI 渲染的工作主要由UI ThreadRenderThread两个线程完成。 在之前的版本中,创建渲染框架的所有工作都是在UI Thread上完成的。

点击一个F圆圈,它可以提供系统为渲染该frame 完成所包含的工作信息,包括警报,丢帧,建议等。
同时它还向您展示了在渲染该frame时系统正在执行的方法,因此您可以调查这些方法是否导致UI jank

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

选择黄色的frame后,您可能会在报告的底部窗格中看到如上提示信息。
上图中显示的Alert , 主要问题是在ListView回收和重新bind中花费了太多的时间。 trace中有相关事件的链接,点击可以获取更多关于系统在这段时间内正在做什么的事情。

要查看Systrace中发现的每个Alert以及设备触发Alert的次数,请单击窗口最右侧的Alerts选项卡,如下图所示。
Alerts面板可帮助您查看发生了哪些问题,以及发生的频率。 将Alert面板看作是要修复的错误列表, 通常情况下,一个区域的微小变化或改进就可以消除应用程序中的全部Alert。

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

如果你的代码在UI Thread上做太多的工作,你需要找出哪些方法消耗了太多的cpu时间
一种方法是添加systrace(请参阅检测应用代码)到您认为会导致这些卡顿或者导致慢的方法地方,然后查看这些函数调用是否显示在systrace中。 如果您不确定哪些方法可能会在UI线程上造成卡顿,请使用Android Studio的内置cpu Profiler,或者生成跟踪日志并使用Traceview查看它们。

六、查看trace 文件的快捷键

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

七、代码中添加trace 标记方法

由于systrace是在系统级显示有关进程的信息,因此很难在HTML报告中查看某个特定时间内,您的应用程序正在执行什么方法。 在Android 4.3(API级别18)及更高版本中,您可以使用代码中的Trace类在HTML报告中标记执行事件。 您不需要用代码来记录systrace的跟踪记录,这样做可以帮助您查看app代码的哪些部分可能导致线程hung或UI丢帧。 但是这种方法与使用Debug类不同,Trace类简单地将标志添加到systrace报告中,而Debug类可帮助您生成.trace文件,并且检查app cpu使用情况。

要生成包含已检测的跟踪事件的systrace HTML报告,需要使用-a--app命令行选项运行systrace,并指定应用程序的包名称。

通常我们在怀疑引起jank代码地方,添加如下内容:
Trace.beginSection("MyAdapter.onCreateViewHolder");
Trace.endSection(); 可以查看自定义的从开始到结束期间的Systrace信息。这两个是成对出现的,需要注意一下。

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

多次调用beginSection(String)时,调用endSection()只会结束最近调用的beginSection(String)方法。 因此,对于嵌套的调用,例如上面示例中的调用,您需要确保通过调用endSection()将每个调用正确匹配到beginSection()

此外,您不能在一个线程上调用beginSection()并从另一个线程结束 - 您必须从同一线程调用endSection()

八、使用TraceView 分析trace Log

Traceview是提供Systrace的图形显示工具。 您可以通过使用Debug类来设置代码来生成log。 这种跟踪方法非常精确,因为您可以准确指定要启动的代码中的哪个位置,并停止记录Systrace数据。 使用Traceview检查这些log可帮助您调试您的应用程序并剖析其性能。

另外,可以使用命令行中的dmtracedump来生成跟踪日志文件的图形调用堆栈图。

如果您不需要查看通过使用Debug类检测应用程序来记录的Systrace日志,则可以使用Android Studio 3.0及更高版本中包含的cpu Profiler来查看应用程序的线程和记录方法跟踪。

1.trace Log 的打开方法

使用Android Device Monitor可以查看trace Log内容,步骤如下,打开Android Device Monitor,选择File,然后打开*.trace log分析。
当然,你也可以使用Android Device Monitor 的图形按键进行trace的抓取与查看。

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

2.Trace log 的分析

打开Trace log后,Traceview使用以下两个窗格显示log数据:

    1. 时间轴窗格:
      描述每个线程何时进入和退出方法的时间轴窗格
    1. 配置文件窗格:
      总结每个线程在跟踪日志的执行期间的配置文件窗格
      以下各节提供有关traceview输出窗格的附加信息。

3.Trace log 时间轴窗格

每个线程的执行都显示在自己的进程中,并且时间向右增加。 每种方法都以不同的颜色显示。 第一行下方的细线显示所选方法的子项(从入口到出口),如下图所示。

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

4.Trace log配置文件窗格

如下图所示,配置文件窗格提供了系统在Systrace期间每种方法的执行的列表以及耗时。

另外,调用另一个方法的方法称为父级方法,父级调用的方法称为其子级。 当您通过单击方法选择一种方法时,它会在两个单独的节点下显示其父项和子项。

对于配置文件窗格中的每个顶级节点,表中的Calls + RecCalls / Total列(图2中未显示)将显示该方法调用次数和递归调用次数。或者,对于父级和子级方法,此列显示方法在顶级节点中是方法的子级或父级的调用次数。

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~

上述【高清技术脑图】以及【配套的架构技术PDF】可以 加我wx:X1524478394 免费获取!

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

Android Choreographer 源码分析 Android 教你如何发现 APP 卡顿

Android Choreographer 源码分析 Android 教你如何发现 APP 卡顿

Choreographer 的作用主要是配合 Vsync ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,来控制每一帧绘制操作的时机。目前大部分手机都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系统为了配合屏幕的刷新频率,将 Vsync 的周期也设置为 16.6 ms,每个 16.6 ms , Vsync 信号唤醒 Choreographer 来做 App 的绘制操作,这就是引入 Choreographer 的主要作用。了解 Choreographer 还可以帮助 App 开发者知道程序每一帧运行的基本原理,也可以加深对 Message、Handler、Looper、MessageQueue、Measure、Layout、Draw 的理解

主线程运行机制的本质

在介绍 Choreographer 之前,我们先理一下 Android 主线程运行的本质,其实就是 Message 的处理过程,我们的各种操作,包括每一帧的渲染,手势操作 ,都是通过 Message 的形式发给主线程的 MessageQueue ,MessageQueue 处理完消息继续等下一个消息,如下图所示

MethodTrace 图示

Systrace 图示

可以发现,每一帧时间都是固定的。所以一旦一个 Message 的处理时间超过了 16.6ms 就会引起卡顿。关于如何发现卡顿,可以参考文章:

Android 教你如何发现 APP 卡顿

Choreographer 简介

Choreographer 扮演 Android 渲染链路中承上启下的角色

  1. 承上:负责接收和处理 App 的各种更新消息和回调,等到 Vsync 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等

  2. 启下:负责请求和接收 Vsync 信号。接收 Vsync 事件回调(通过 FramedisplayEventReceiver.onVsync );请求 Vsync(FramedisplayEventReceiver.scheduleVsync) .

从上面可以看出来, Choreographer 担任的是一个工具人的角色,他之所以重要,是因为通过 Choreographer + SurfaceFlinger + Vsync + TripleBuffer 这一套从上到下的机制,保证了 Android App 可以以一个稳定的帧率运行(目前大部分是 60fps),减少帧率波动带来的不适感。

Choreographer 的工作流程

  • Choreographer 初始化SurfaceFlinger 的 appEventThread 唤醒发送 Vsync ,Choreographer 回调 FramedisplayEventReceiver.onVsync,进入 Choreographer 的主处理函数 doFrame

    • 初始化 FrameHandler ,绑定 Looper

    • 初始化 FramedisplayEventReceiver ,与 SurfaceFlinger 建立通信用于接收和请求 Vsync

    • 初始化 CallBackQueues

  • Choreographer.doFrame 计算掉帧逻辑

  • Choreographer.doFrame 处理 Choreographer 的第一个 callback : input

  • Choreographer.doFrame 处理 Choreographer 的第二个 callback : animation

  • Choreographer.doFrame 处理 Choreographer 的第三个 callback : insets animation

  • Choreographer.doFrame 处理 Choreographer 的第四个 callback : traversalChoreographer.doFrame 处理 Choreographer 的第五个 callback : commit ?

    • traversal-draw 中 UIThread 与 RenderThread 同步数据

  • RenderThread 处理绘制数据,真正进行渲染

  • 将渲染好的 Buffer swap 给 SurfaceFlinger 进行合成

Choreographer 源码分析

Choreographer 的单例初始化

    // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper,VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };

这里采用的是 ThreadLocal 来构造单例,这样每个线程都会有一个属于自己的 choreographer 实例。

接下去看 choreographer 的构造函数

    private Choreographer(Looper looper,int vsyncSource) {
        mLooper = looper;
        mHandler =  FrameHandler(looper);
     // 这里可以发现只有在为 true 的时候才会使用 vsync mdisplayEventReceiver
= USE_VSYNC ? FramedisplayEventReceiver(looper,vsyncSource) : ; mLastFrameTimeNanos = Long.MIN_VALUE;      // 每一帧的间隔是根据刷新频率来的 mFrameIntervalNanos = (long)(1000000000 / getRefreshRate()); mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
     // 给每一种回调类型都创建了一个队列
for (int i = 0; i <= CALLBACK_LAST; i++) { mCallbackQueues[i] = CallbackQueue(); } b/68769804: For low FPS experiments. setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR,1)); }

这里做了几个初始化操作,根据Looper对象生成,Looper和线程是一对一的关系,对应上面说明里的每个线程对应一个Choreographer。

  • 初始化FrameHandler。接收处理消息。

  • 初始化FramedisplayEventReceiver。FramedisplayEventReceiver用来接收垂直同步脉冲,就是VSync信号,VSync信号是一个时间脉冲,一般为60HZ,用来控制系统同步操作,怎么同ChoreoGrapher一起工作的,将在下文介绍。

  • 初始化mLastFrameTimeNanos(标记上一个frame的渲染时间)以及mFrameIntervalNanos(帧率,fps,一般手机上为1s/60)。

  • 初始化CallbackQueue,callback队列,将在下一帧开始渲染时回调。

接下去看看 FrameHandler 和 FramedisplayEventReceiver 的结构。

    final class FrameHandler extends Handler {
        public FrameHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DO_FRAME:
                    doFrame(System.nanoTime(),0);
                    break;
                 MSG_DO_SCHEDULE_VSYNC:
                    doScheduleVsync();
                     MSG_DO_SCHEDULE_CALLBACK:
                    doScheduleCallback(msg.arg1);
                    ;
            }
        }
    }

看上面的代码,就是一个简单的Handler。处理3个类型的消息。

  • MSG_DO_FRAME:开始渲染下一帧的操作

  • MSG_DO_SCHEDULE_VSYNC:请求 Vsync 信号

  • MSG_DO_SCHEDULE_CALLBACK:请求执行 callback

下面再细分一下,分别详细看一下这三个步骤是怎么实现的。

FramedisplayEventReceiver

    class FramedisplayEventReceiver  displayEventReceiver
            implements Runnable {
        boolean mHavePendingVsync;
        long mTimestampNanos;
         mFrame;

        public FramedisplayEventReceiver(Looper looper,1)"> vsyncSource) {
            (looper,vsyncSource);
        }

         Todo(b/116025192): physicaldisplayId is ignored because SF only emits VSYNC events for
         the internal display and displayEventReceiver#scheduleVsync only allows requesting VSYNC
         for the internal display implicitly.
        @Override
        void onVsync(long timestampNanos,1)">long physicaldisplayId,1)"> frame) {
             Post the vsync event to the Handler.
             The idea is to prevent incoming vsync events from completely starving
             the message queue.  If there are no messages in the queue with timestamps
             earlier than the frame time,then the vsync event will be processed immediately.
             Otherwise,messages that predate the vsync event will be handled first.
            long Now = System.nanoTime();
            if (timestampNanos > Now) {
                Log.w(TAG,"Frame time is " + ((timestampNanos - Now) * 0.000001f)
                        + " ms in the future!  Check that graphics HAL is generating vsync "
                        + "timestamps using the correct timebase.");
                timestampNanos = Now;
            }

            if (mHavePendingVsync) {
                Log.w(TAG,"Already have a pending vsync event.  There should only be "
                        + "one at a time.");
            } else {
                mHavePendingVsync = true;
            }

            mTimestampNanos = timestampNanos;
            mFrame = frame;
            Message msg = Message.obtain(mHandler,1)">this);
            msg.setAsynchronous();
            mHandler.sendMessageAtTime(msg,timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
         run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos,mFrame);
        }
    }
FramedisplayEventReceiver 继承自 displayEventReceiver,同时也实现了Runnable 接口,是处于 Choreographer 中的私有内部类。当接收到底层的 VSync 信号开始处理 UI 过程。VSync 信号由 SurfaceFlinger 实现并定时发送。FramedisplayEventReceiver 收到信号后,调用 onVsync 方法组织消息发送到主线程处理。这个消息主要内容就是 run 方法里面的 doFrame 了,这里 mTimestampNanos 是信号到来的时间参数。

那么 FramedisplayEventReceiver 是通过什么方式在 Vsync 信号到来的时候回调 onVsync 呢?答案是 FramedisplayEventReceiver 的初始化的时候,最终通过监听文件句柄的形式,其对应的初始化流程如下

 android/view/Choreographer.java
 vsyncSource) {
    mLooper = looper;
    mdisplayEventReceiver = USE_VSYNC
            ? ;
    ......
}
vsyncSource) { android/view/displayEventReceiver.java public displayEventReceiver(Looper looper,1)"> vsyncSource) { ...... mMessageQueue = looper.getQueue(); mReceiverPtr = nativeInit(new WeakReference<displayEventReceiver>(),mMessageQueue,vsyncSource); }

nativeInit 后续的代码可以自己跟一下,可以对照这篇文章和源码,由于篇幅比较多,这里就不细说了。

简单来说,FramedisplayEventReceiver 的初始化过程中,通过 BitTube (本质是一个 socket pair),来传递和请求 Vsync 事件,当 SurfaceFlinger 收到 Vsync 事件之后,通过 appEventThread 将这个事件通过 BitTube 传给 displayEventdispatcher ,displayEventdispatcher 通过 BitTube 的接收端监听到 Vsync 事件之后,回调 Choreographer.FramedisplayEventReceiver.onVsync ,触发开始一帧的绘制。

如下图

ChoreoGrapher 的总体流程

FrameHandler 和 FramedisplayEventReceiver 是怎么工作的呢?ChoreoGrapher 的总体流程图如下图(拷贝的图片):

 以上是总体的流程图:

  1. PostCallBack or postFrameCallback发起添加回调,这个FrameCallBack将在下一帧被渲染时执行。

  2. AddToCallBackQueue,将 FrameCallBack 添加到回调队列里面,等待时机执行回调。每种类型的callback按照设置的执行时间(dueTime)顺序排序分别保存在一个单链表中。

  3. 判断 FrameCallBack设定的执行时间是否在当前时间之后,若是,发送 MSG_DO_SCHEDULE_CALLBACK 消息到主线程,安排执行doScheduleCallback,安排执行CallBack。否则直接跳到第4步。

  4. 执行 scheduleFrameLocked,安排执行下一帧。

  5. 判断上一帧是否已经执行,若未执行,当前操作直接结束。若已经执行,根据情况执行以下6、7步。

  6. 若使用垂直同步信号进行同步,则执行7.否则,直接跳到9。

  7. 若当前线程是UI线程,则通过执行scheduleVsyncLocked请求垂直同步信号。否则,送MSG_DO_SCHEDULE_VSYNC消息到主线程,安排执行doScheduleVsync,在主线程调用scheduleVsyncLocked。

  8. 收到垂直同步信号,调用FramedisplayEventReceiver.onVsync(),发送消息到主线程,请求执行doFrame。

  9. 执行doFrame,渲染下一帧。

主要的工作在 doFrame 中,接下来我们具体看看 doFrame 函数都干了些什么。
从名字看很容易理解 doFrame 函数就是开始进行下一帧的显示工作。好了以下源代码又来了,我们一行一行分析一下吧。

doFrame

    @UnsupportedAppUsage
    void doFrame(long frameTimeNanos,1)"> frame) {
         startNanos;
        synchronized (mlock) {
       // 为false,说明还未开始
if (!mFrameScheduled) { return; no work to do } if (DEBUG_JANK && mDebugPrintNextFrametimedelta) { mDebugPrintNextFrametimedelta = ; Log.d(TAG,"Frame time delta: " + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms"); } long intendedFrameTimeNanos = frameTimeNanos; startNanos = System.nanoTime();
       // 计算当前时间与 vsync 信号的时间差
long jitterNanos = startNanos - frameTimeNanos;
       // 说明出现掉帧情况,注意只有 jitterNanos 大于 16.6 ms 才说明掉帧,否则只是轻微的延迟。
if (jitterNanos >= mFrameIntervalNanos) { long skippedFrames = jitterNanos / mFrameIntervalNanos; if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { Log.i(TAG,"Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); }
          // 当发生掉帧后,需要计算被耽误的时间。比如处理了 36.6ms,一个周期是 16.6 ms,相当于延迟了 3.4 ms 执行
long lastFrameOffset = jitterNanos % (DEBUG_JANK) { Log.d(TAG,"Missed vsync by " + (jitterNanos * 0.000001f) + " ms " + "which is more than the frame interval of " + (mFrameIntervalNanos * 0.000001f) + " ms! " + "Skipping " + skippedFrames + " frames and setting frame " + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."          // 修正当前帧的时间 = 开始时间 - 耽误时间 frameTimeNanos = startNanos - lastFrameOffset; }        // 当前时间小于前一帧时间,不执行操作 if (frameTimeNanos < mLastFrameTimeNanos) {           // 直接请求下一个 vsync 信号 scheduleVsyncLocked(); ; }        // 大于 1 说明采用的默认帧数的一半,因此需要根据时间间隔来判断是否有必要执行绘制 if (mfpSDivisor > 1long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos; if (timeSinceVsync < (mFrameIntervalNanos * mfpSDivisor) && timeSinceVsync > 0) {
            // 时间间隔小于指定的时间,继续请求下一个 vsync 信号 scheduleVsyncLocked();
; } }        // 保存当前帧的相关信息 mFrameInfo.setVsync(intendedFrameTimeNanos,frameTimeNanos); mFrameScheduled = ; mLastFrameTimeNanos = frameTimeNanos; } try {
       // 执行相关 callbacks Trace.traceBegin(Trace.TRACE_TAG_VIEW,
"Choreographer#doFrame"); AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart(); doCallbacks(Choreographer.CALLBACK_INPUT,frameTimeNanos); mFrameInfo.markAnimationsstart(); doCallbacks(Choreographer.CALLBACK_ANIMATION,frameTimeNanos); doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION,frameTimeNanos); mFrameInfo.markPerformTraversalsstart(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL,frameTimeNanos); doCallbacks(Choreographer.CALLBACK_COMMIT,frameTimeNanos); } finally { AnimationUtils.unlockAnimationClock(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); } (DEBUG_FRAMES) { long endNanos = System.nanoTime(); Log.d(TAG,"Frame " + frame + ": Finished,took " + (endNanos - startNanos) * 0.000001f + " ms,latency " + (startNanos - frameTimeNanos) * 0.000001f + " ms."); } } 

总结起来其实主要是两个操作:

设置当前 frame 的启动时间

判断是否跳帧,若跳帧修正当前 frame 的启动时间到最近的 VSync 信号时间。如果没跳帧,当前 frame 启动时间直接设置为当前 VSync 信号时间。修正完时间后,无论当前 frame 是否跳帧,使得当前 frame 的启动时间与 VSync 信号还是在一个节奏上的,可能延后了一到几个周期,但是都是在下一个 vsync 信号到来才进行处理 。

如下图所示是时间修正的一个例子,


 
没有跳帧但延迟
 

由于第二个 frame 执行超时,第三个 frame 实际启动时间比第三个 VSync 信号到来时间要晚,因为这时候延时比较小,没有超过一个时钟周期,系统还是将 frameTimeNanos3 传给回调,回调拿到的时间和 VSync 信号同步。

再来看看下图:

跳帧

由于第二个 frame执行时间超过 2 个时钟周期,导致第三个 frame 延后执行时间大于一个时钟周期,系统认为这时候影响较大,判定为跳帧了,将第三个 frame 的时间修正为 frameTimeNanos4,比 VSync 真正到来的时间晚了一个时钟周期。

时间修正,既保证了doFrame操作和 VSync 保持同步节奏,又保证实际启动时间与记录的时间点相差不会太大,便于同步及分析。

顺序执行callBack队列里面的callback

然后接下来看看 doCallbacks 的执行过程:

    void doCallbacks(int callbackType,1)"> frameTimeNanos) {
        CallbackRecord callbacks;
         (mlock) {
             We use "Now" to determine when callbacks become due because it''s possible
             for earlier processing phases in a frame to post callbacks that should run
             in a following phase,such as an input event that causes an animation to start.
             System.nanoTime();
       // callbacks
= mCallbackQueues[callbackType].extractDueCallbacksLocked( Now / TimeUtils.NANOS_PER_MS); if (callbacks == ; } mCallbacksRunning = ; Update the frame time if necessary when committing the frame. We only update the frame time if we are more than 2 frames late reaching the commit phase. This ensures that the frame time which is observed by the callbacks will always increase from one frame to the next and never repeat. We never want the next frame''s starting frame time to end up being less than or equal to the prevIoUs frame''s commit frame time. Keep in mind that the next frame has most likely already been scheduled by Now so we play it safe by ensuring the commit time is always at least one frame behind.
       // commit 类型是最后执行的,如果此时发现前面处理时间过长,就会进行纠正。
if (callbackType == Choreographer.CALLBACK_COMMIT) { long jitterNanos = Now - frameTimeNanos; Trace.traceCounter(Trace.TRACE_TAG_VIEW,"jitterNanos",() jitterNanos); if (jitterNanos >= 2 * mFrameIntervalNanos) { mFrameIntervalNanos + mFrameIntervalNanos; (DEBUG_JANK) { Log.d(TAG,"Commit callback delayed by " + (jitterNanos * 0.000001f) + " ms which is more than twice the frame interval of " + (mFrameIntervalNanos * 0.000001f) + " ms! " + "Setting frame time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); mDebugPrintNextFrametimedelta = ; } frameTimeNanos = Now - lastFrameOffset; mLastFrameTimeNanos = frameTimeNanos; } } } { Trace.traceBegin(Trace.TRACE_TAG_VIEW,CALLBACK_TRACE_TITLES[callbackType]); for (CallbackRecord c = callbacks; c != null; c = c.next) { (DEBUG_FRAMES) { Log.d(TAG,"runcallback: type=" + callbackType + ",action=" + c.action + ",token=" + c.token + ",latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime)); }
          // 运行每一个 callback c.run(frameTimeNanos); } }
{ (mlock) { mCallbacksRunning = do { final CallbackRecord next = callbacks.next; recycleCallbackLocked(callbacks); callbacks = next; } while (callbacks != ); } Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }

callback的类型有以下 4 种,除了文章一开始提到的 3 种外,还有一个 CALLBACK_COMMIT。

  1. CALLBACK_INPUT:输入

  2. CALLBACK_ANIMATION:动画

  3. CALLBACK_TRAVERSAL:遍历,执行 measure、layout、draw

  4. CALLBACK_COMMIT:遍历完成的提交操作,用来修正动画启动时间

然后看上面的源码,分析一下每个 callback 的执行过程:

1. callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( Now / TimeUtils.NANOS_PER_MS);

得到执行时间在当前时间之前的所有 CallBack,保存在单链表中。每种类型的 callback 按执行时间先后顺序排序分别存在一个单链表里面。为了保证当前 callback 执行时新 post 进来的 callback 在下一个 frame 时才被执行,这个地方 extractDueCallbacksLocked 会将需要执行的 callback 和以后执行的 callback 断开变成两个链表,新 post 进来的 callback 会被放到后面一个链表中。当前 frame 只会执行前一个链表中的 callback,保证了在执行 callback 时,如果callback中Post相同类型的callback,这些新加的 callback 将在下一个 frame 启动后才会被执行。

2. 接下来,看一大段注释,如果类型是 CALLBACK_COMMIT,并且当前 frame 渲染时间超过了两个时钟周期,则将当前提交时间修正为上一个垂直同步信号时间。为了保证下一个frame 的提交时间和当前 frame 时间相差为一且不重复。
这个地方注释挺难看懂,实际上这个地方 CALLBACK_COMMIT 是为了解决 ValueAnimator 的一个问题而引入的,主要是解决因为遍历时间过长导致动画时间启动过长,时间缩短,导致跳帧,这里修正动画第一个 frame 开始时间延后来改善,这时候才表示动画真正启动。为什么不直接设置当前时间而是回溯一个时钟周期之前的时间呢?看注释,这里如果设置为当前 frame 时间,因为动画的第一个 frame 其实已经绘制完成,第二个 frame 这时候已经开始了,设置为当前时间会导致这两个 frame 时间一样,导致冲突。

详细情况请看官方针对这个问题的修改。Fix animation start jank due to expensive layout operations.

如下图所示:

 
修正commit时间

比如说在第二个frame开始执行时,开始渲染动画的第一个画面,第二个frame执行时间超过了两个时钟周期,Draw操作执行结束后,这时候完成了动画第一帧的渲染,动画实际上还没开始,但是时间已经过了两个时钟周期,后面动画实际执行时间将会缩短一个时钟周期。这时候系统通过修正commit时间到frameTimeNanos的上一个VSync信号时间,即完成动画第一帧渲染之前的VSync信号到来时间,修正了动画启动时间,保证动画执行时间的正确性。

调用 c.run(frameTimeNanos) 执行回调

    class CallbackRecord {
         CallbackRecord next;
         dueTime;
        public Object action;  Runnable or FrameCallback
         Object token;

        @UnsupportedAppUsage
        void run( frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                ((FrameCallback)action).doFrame(frameTimeNanos);
            }  {
                ((Runnable)action).run();
            }
        }
    }

CallbackRecord 的代码如上所示。

发起绘制的请求

doFrame 的逻辑了解清楚了,但是关于发起 vsync 请求的逻辑却没有讲。

 ViewRootImpl    
 @UnsupportedAppUsage
     scheduleTraversals() {
     // 如果已经请求绘制了,就不会再次请求,因为多次请求,只有有一个执行就满足要求了
mTraversalScheduled) { mTraversalScheduled = ;
       // 同步 mTraversalBarrier
= mHandler.getLooper().getQueue().postSyncBarrier();
       // 发送一个 callback 用于在下一帧来临时候处理 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL,mTraversalRunnable,
null); mUnbufferedInputdispatch) { scheduleConsumeBatchedinput(); }
       // 通知稍候绘制 notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }

 接着会调用 postCallbackDelayed:

    void postCallbackDelayed( callbackType,Runnable action,Object token, delayMillis) {
        if (action == ) {
            new IllegalArgumentException("action must not be null");
        }
        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
            new IllegalArgumentException("callbackType is invalid");
        }

        postCallbackDelayedInternal(callbackType,action,token,delayMillis);
    }

主要是做一些逻辑判断,确保传入的是对的。 

接着又会调用 postCallbackDelayedInternal,保存 callback,并发起请求下一帧。

    void postCallbackDelayedInternal( (DEBUG_FRAMES) {
            Log.d(TAG,"PostCallback: type=" + callbackType
                    + ",action=" + action + ",1)"> token
                    + ",delayMillis=" + delayMillis);
        }

         SystemClock.uptimeMillis();
            long dueTime = Now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime,token);
        // 小于当前时间,说明需要立即执行
            if (dueTime <= Now) {
                scheduleFrameLocked(Now);
            }           // 发送一个延迟 msg
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK,action);
                msg.arg1 = callbackType;
                msg.setAsynchronous();
                mHandler.sendMessageAtTime(msg,dueTime);
            }
        }
    }

简单来说,就是判断当前是否有必要发起一个绘制请求,比如你发了一个 500ms 后重绘的消息,对于这个消息,会在 500ms 后在进行处理。但如果不是延迟消息,那说明需要立即处理。但是对于 view 的绘制逻辑,必须得等到下一个 vsync 到来的时候才会真正进行绘制。

接下来看看 scheduleFrameLocked 的逻辑:

   void scheduleFrameLocked( Now) {
        mFrameScheduled) {
            mFrameScheduled = ;
             (USE_VSYNC) {
                );
                }

                 If running on the Looper thread,then schedule the vsync immediately,1)"> otherwise post a message to schedule the vsync from the UI thread
                 as soon as possible.
         // 如果是在一个 looper 线程中,那么直接执行请求就好 (isRunningOnLooperThreadLocked()) { scheduleVsyncLocked(); }             // 如果是在主线程,那么需要发送一个请求 vsync 的消息,并插到最前面,需要确保前一个消息处理完后在开始请求 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(); mHandler.sendMessageAtFrontOfQueue(msg); } }           // 如果不用 vsync 信号,那么就可以直接执行,只是需要记录每一帧的时间 long nextFrameTime = Math.max( mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay,Now); Now) + " ms."); } Message msg = mHandler.obtainMessage(MSG_DO_FRAME); msg.setAsynchronous(一个 MSG_DO_SCHEDULE_VSYNC 的消息,插到最前面,确保可以最早执行。

当收到 MSG_DO_SCHEDULE_VSYNC 消息后,就会给他安排请求 vsync 信号的请求,最后会会调到 onVsync 方法。

     doScheduleVsync() {
         (mFrameScheduled) {
                scheduleVsyncLocked();
            }
        }
    }


    @UnsupportedAppUsage
     scheduleVsyncLocked() {
        发起获取 vsync 信号的请求
        mdisplayEventReceiver.scheduleVsync();
    }   

    /**
     * Schedules a single vertical sync pulse to be delivered when the next
     * display frame begins.
     */
    @UnsupportedAppUsage
     scheduleVsync() {
        if (mReceiverPtr == 0) {
            Log.w(TAG,"Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        }  {
            nativeScheduleVsync(mReceiverPtr);
        }
    } 
到这里,就把 Choreographer 的基本原理都讲完了。
 

源码小结

  • Choreographer 是线程单例的,而且必须要和一个 Looper 绑定,因为其内部有一个 Handler 需要和 Looper 绑定,一般是 App 主线程的 Looper 绑定

  • displayEventReceiver 是一个 abstract class,其 JNI 的代码部分会创建一个IdisplayEventConnection 的 Vsync 监听者对象。这样,来自 AppEventThread 的 VSYNC 中断信号就可以传递给 Choreographer 对象了。当 Vsync 信号到来时,displayEventReceiver 的 onVsync 函数将被调用。

  • displayEventReceiver 还有一个 scheduleVsync 函数。当应用需要绘制UI时,将首先申请一次 Vsync 中断,然后再在中断处理的 onVsync 函数去进行绘制。

  • Choreographer 定义了一个 FrameCallbackinterface,每当 Vsync 到来时,其 doFrame 函数将被调用。这个接口对 Android Animation 的实现起了很大的帮助作用。以前都是自己控制时间,现在终于有了固定的时间中断。

  • Choreographer 的主要功能是,当收到 Vsync 信号时,去调用使用者通过 postCallback 设置的回调函数。目前一共定义了五种类型的回调,它们分别是:ListView 的 Item 初始化(obtain\setup) 会在 input 里面也会在 animation 里面,这取决于

    • CALLBACK_INPUT : 处理输入事件处理有关

    • CALLBACK_ANIMATION : 处理 Animation 的处理有关

    • CALLBACK_INSETS_ANIMATION : 处理 Insets Animation 的相关回调

    • CALLBACK_TRAVERSAL : 处理和 UI 等控件绘制有关

    • CALLBACK_COMMIT : 处理 Commit 相关回调

  • CALLBACK_INPUT 、CALLBACK_ANIMATION 会修改 view 的属性,所以要比 CALLBACK_TRAVERSAL 先执行

 
最后附上一张时序图,来回顾一下整个流程
在这里插入图片描述
 
另外,知道源码后,也就知道如何去发现 app 卡顿情况了。 详见 Android 教你如何发现 APP 卡顿

 

参考文章

Android 基于 Choreographer 的渲染机制详解

Android Choreographer 源码分析

总结

以上是小编为你收集整理的Android Choreographer 源码分析 Android 教你如何发现 APP 卡顿全部内容。

如果觉得小编网站内容还不错,欢迎将小编网站推荐给好友。

android studio 不出现【start a new android project】, 怎么办?

android studio 不出现【start a new android project】, 怎么办?

1、向导界面,找不到【start a new android project】, 出现的是【create new project】, 如下图

2、解决办法是,点击【Configure】, 切换到下面界面

3、点击界面中【Plugin】, 切换到下面界面,选中【Android NDK Support】和【Android Support】,点击【ok】按钮

4、弹出重启界面,点击【Restart】

5、重新启动,恢复正常向导界面

Android Studio 中 System Trace 的新增功能

Android Studio 中 System Trace 的新增功能

在 Android Studio 4.0 中,我们已经对 CPU Profiler 的 UI 做了大量调整来提供更加直观的工作流记录,而在 Android Studio 4.1 中,我们基于开发者们的反馈对此功能进行了持续改进,并且新增了更多特性。

我们将会在本文重点介绍 Android Studio 中 System Trace 的新增功能,System Trace 也就是 "系统跟踪" 的意思,用来记录短时间内的设备活动,它会生成一个 .trace 跟踪文件,该文件可用于生成系统报告,此报告可帮助您了解如何最有效地提升应用或游戏的性能。System Trace 虽然使用率不高,但它是一款功能强大的 CPU 记录配置。不同于函数跟踪,如 Java Method 或 C/C++ Func Trace,System Trace 跟踪的是系统级的内容,如设备活动 (例如 CPU 核心调度) 和 Android 系统进程 (例如 SurfaceFlinger)。此外,您还可以通过在应用中调用 Trace API,使用 自定义事件 检测您的代码,这样您的自定义事件便会与系统事件一起被收集。当您在排查性能问题时 (例如 UI 卡顿或功耗过高),这些组合数据就会显得十分有用。

一次搞定所有线程

为了便于进行分析,我们将 CPU 的记录从主分析器的时间线中分离了出来。在这个专用视图中,跟踪数据被整理到了 Profiler 窗口的左侧区域中。

Android Studio 4.1 Beta 1 中的 System Trace 界面

Android Studio 4.1 Beta 1 中的 System Trace 界面

您可以通过简单拖拽将某一区域或者区域内的某个元素进行向上或向下移动来重新组织列表。

我们从开发者们的反馈得知,选择每个线程来查看它的调用图 (或 System Trace 的跟踪事件) 是一件很麻烦的事,所以我们将所有线程活动整合到了同一个视图中,从而可以在显示线程状态的同时显示调用图。默认情况下,我们根据线程的繁忙程度对其进行排序,但是您也可以拖放任意一个线程以对其重新排序。

拖放线程来改变列表顺序

拖放线程来改变列表顺序

您也可以通过单击三角形图标或双击线程名称来折叠或展开每个线程。注意,对于 Java Method Trace 和 C/C++ Function Trace,由于调用栈很深,我们默认情况下会折叠所有线程视图,以便您可以一目了然地查看所有线程数据。

C/C++ Function Trace 默认以折叠状态展示线程数据

C/C++ Function Trace 默认以折叠状态展示线程数据

为了便于区分,现在每个 System Trace 的跟踪事件都有一个独立的颜色。

System Trace 事件按命名添加了对应颜色

System Trace 事件按命名添加了对应颜色

更加直观的导航

新的 Trace UI 使用了改进的时间轴导航方案,我们用主要 - 细节视图替换了以前的水平滚动条。

在顶部,您可以看到一个时间轴,它仅仅映射了跟踪过程而不是整个分析过程。您可以使用范围选择器快速缩小范围到特定的时间段,而下面的部分则会显示对应的详细数据。

使用范围选择器来专注于时间轴的一小部分

使用范围选择器来专注于时间轴的一小部分

在这里您可以进行更加精细的导航操作:

从 Android Studio 4.1 Canary 9 开始,您可以通过拖动鼠标在  Thread  部分进行框选。这一操作使您可以精确地选择一个矩形区域,并且只要点击右上角的  Zoom to Selection  (或 "M" 键) 便可以放大该区域。您甚至可以跨越多个线程执行选择操作,这个特性在您把相似线程拖放到一起进行检视时十分有用。举例来说,您也许会想对多个辅助线程进行分析,而这种场景在游戏开发中很常见。

框选、拖放与缩放

框选、拖放与缩放

分析面板

说到分析,我们想着重聊一聊 Android Studio 4.0 中引入的新  Analysis Panel ,它位于  Profiler 窗口的右边一列。

基于您所选择的跟踪记录,在这里可以找到相应的分析数据。当您在左边栏中选择一个线程、堆栈帧或者跟踪事件时,Analysis Panel 将会显示对应的特定信息。举例来说,当您选择了一个线程时,该线程的状态与其他一些有用的信息就会被显示出来。

我们希望 Analysis Panel 能对您有所帮助,所以我们一直在探索使用各种形式来展示这些有用的分析数据。在 CPU Profiler 中,我们已经有了 Top Down、Flame Chart 和 Bottom Up。作为补充,我们在 Android Studio 4.1 Canary 10 中添加了  Summary  选项卡,用于展示线程状态分布、跟踪事件统计等信息。举例来说,我们经常需要深入了解一个反复出现的跟踪事件。Summary 选项卡会显示基本的统计信息 (如计数,最小值,最大值等) 以及所选跟踪事件中运行时间最长的一次事件。您也可以通过从表中选择一行来导航到另一个事件。

统计信息以及跟踪事件中运行时间最长的事件

统计信息以及跟踪事件中运行时间最长的事件

稳定性与性能改进

最后但也同样重要的是,我们还改进了 CPU 记录的性能和稳定性:

下载最新的 Android Studio 4.1 预览版 可以尝鲜本文介绍的新特性。也同样欢迎大家 反馈。

Android Studio 里sync下载慢,怎么办?

Android Studio 里sync下载慢,怎么办?

大部分人Android Studio里面sync窗口下载都特别慢,现在有一个解决办法,添加阿里云镜像就好了
在build.gradle里的buildscript 和 allprojects添加阿里镜像就好了

repositories {
maven{ url ‘http://maven.aliyun.com/nexus/content/groups/public/’}
}

添加好之后,下载速度就会非常快(不快的话重启一下就好了)

今天关于Android UI卡顿响应慢性能差怎么办?请用Systrace 来分析查看~android ui性能优化的介绍到此结束,谢谢您的阅读,有关Android Choreographer 源码分析 Android 教你如何发现 APP 卡顿、android studio 不出现【start a new android project】, 怎么办?、Android Studio 中 System Trace 的新增功能、Android Studio 里sync下载慢,怎么办?等更多相关知识的信息可以在本站进行查询。

本文标签:

上一篇Android开发学习笔记 -- 活动相关(android活动的作用)

下一篇【Android休眠】之Android休眠机制(android 休眠机制)