Choreographer 丢帧计算

 

在Logcat中,我们经常会看到系统输出如下Log:

Choreographer: Skipped 180 frames!  The application may be doing too much work on its main thread.

这句log是提示我们在主线程做了耗时操作,那么本文就来分析一下丢帧是怎么计算的,以及如何解决以下2个问题:

1. 在主线程不可避免要做比较耗时操作时,如何避免系统输出这句log,也就是绕过系统丢帧检测;

2. 如何让UI绘制优先于主线程不可避免的耗时任务,也就是让UI先显示。

首先我们来看一次完整的绘制流程: Choreographer 丢帧计算_第1张图片

其中关注2个点:

1. 在发出请求vsync信号前,ViewRootImpl会在当前线程(主线程)消息队列中插入 barrier ,barrier的作用是限制时间排在它后面的消息执行,除了异步消息(可以通过Message#setAsynchornous来把消息设置为异步消息),这么做是为了等vsync信号回来后主线程能够及时响应;

2. vsync 信号回来是异步的(对应上图 10 和 11 的操作), 因此如果在请求完vsync信号后紧接着做了一个耗时任务J,那么等J还没完成vsync信号就回来了,那这个时候J的执行时长就计算到了丢帧时间里面去了;

对于本文开头的第1个问题, 我们怎么避免耗时任务的执行时长被计算到丢帧时间里面呢?通过上面时序图,因为丢帧时长是从vsync信号回来开始计算的,因此只要vsync信号回来后得到及时处理那就不会出现丢帧,反过来,越不及时处理就会丢的越多。所以为了使vsync信号回来能够及时被处理,在发出vsync请求到vsync信号回来这段时间内不要插入耗时任务。vsync信号回来得到处理之后再插入耗时任务也是可以避免丢帧的。

比如以下场景就可能导致丢帧:

textView.setText("hello");
Thread.sleep(1000);

一般情况下 setText 后会执行上图中1到10, 接着sleep(1000),在这1s内vsync信号回来时发送了一个异步消息到主线程,但这个时候主线程还在sleep,等sleep完成后才得到执行,所以sleep的这1s会被计算到丢帧时长里面;

为了不让sleep的时间被算入内,我们可以对上面代码进行2种不同的改造:

首先看第1种:

textview.setText("hello");
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
            public void doFrame(long frameTimeNanos) {
                Thread.sleep(1000);
            }
        });

通过向Choreographer 注册一个帧回调,使得sleep操作和vsync信号在同一个消息里面处理, 在FrameCallback执行回调时丢帧时长已经计算完成,所以成功避免了sleep被算入丢帧时长;但这种方法并没有从实质上意义,因为setText("hello") 这个操作始终是等到sleep完成后用户才能看见的。

因此,我们尝试第2种方法:

textView.setText("hello");
new Handler().post(new Runnable(){
    public void run(){
        Thread.sleep(1000);
    }
});

这样执行sleep操作的消息就被setText插入的barrier 挡住了, 必须等到vsync信号处理完成后才能得到执行,避免了丢帧,同时,在一般的情况下,setText的内容不需要等到sleep完成就可以显示出来。那什么场景下会需要等sleep完成才能显示出来呢?就是textView 可能还没有attach到window上,setText没有发起绘制,而是在post耗时任务之后attach到window时发起了绘制请求,这样我们post出去的耗时任务又卡在了vsync信号前面,所以setText仍然要等sleep完才能显示; 这样的典型场景就是把上面代码放到Activity#onCreate里面。 那么怎么能够确保setText 能够及时生效呢? 这也是本文开头的第2个问题。

     既然要让sleep在绘制完成后执行,那么我们就监听textView的绘制方法draw就可以了。此优化方法的典型应用就是在activity启动阶段中耗时操作在UI第1帧绘制结束后执行。

你可能感兴趣的:(Android)