前言
在[036]Choreographer Skipped真正含义中,我介绍了一种可以产生Choreographer Skipped的情况。就是在onVsync被调用之前,往主线程post的一个Message。那还有没有其他方式可以产生这个Choreographer Skipped呢?
一、仔细看看代码
代码语言:javascript复制@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
...
//onVsync方法将会在Vsync信号接收之后被回调
//mTimestampNanos就是这次Vsync信号接收的时间
mTimestampNanos = timestampNanos;
mFrame = frame;
//往主线程的Looper中投放一个Asynchronous的Message,callback为this
//这个Message被处理的时候就会调用下面run-doFrame的方法
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
请注意onVsync参数中timestampNanos,这个值代表什么呢,其实代表的是Vsync信号到达App的时间,Vsync信号在通过socket通信发给App时候,会带上这个时间戳timestampNanos,这个过程其实是不会受主线程影响的。
在Vsync信号到来之后,onVsync方法没有被立刻调用,也可以产生Choreographer Skipped
二、写个Demo验证一下
代码语言:javascript复制public class Main2Activity extends AppCompatActivity implements View.OnClickListener {
private TextView mTxtView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mTxtView = findViewById(R.id.txt_view);
mTxtView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
mTxtView.setText("请求Vsync信号");//会触发scheduleTraversals,所以16ms以内会接受到Vsync信号
try {
Thread.sleep(1000);//这样子onVsync会推迟1000ms,才能被调用
} catch (Exception e) {
}
}
}
注意我这里采用的是TextView,因为TextView点击没有UI刷新,所以不会触发scheduleTraversals,我在onClick中主动调用mTxtView.setText,会触发scheduleTraversals,所以App会在16ms以内会接受到Vsync信号,请注意16ms以内,时间不固定。
Vsync信号到来的时间点就是onVsync的形参timestampNanos。
然后mTxtView.setText完了之后再sleep 1000ms,处理完onClick代码。主线程会去处理onVsync的方法,由于Vsync信号早就到了,所以就算此时onVsync投放的Asynchronous的Message被立刻处理,但是已经晚了,所以还是会出现Choreographer Skipped。
代码语言:javascript复制D KobeWang2: onClick : start
D KobeWang2: onClick : end
I Choreographer: Skipped 60 frames! The application may be doing too much work on its main thread.
三、总结
其实有很多Demo可以产生Choreographer Skipped,但是不管你怎么写,肯定是下面两种场景之一。
3.1 场景一
[036]Choreographer Skipped真正含义里介绍的Demo,虽然Vsync信号到了,onVsync被及时调用,但是主线程中有未开始处理的耗时Message,推迟了doFrame的执行时间。
场景一
3.2 场景二
本文介绍的Demo,Vsync信号早早到了,但是由于主线程的耗时操作,onVsync无法被及时调用
场景二
3.3 更正
更正一下我在[036]Choreographer Skipped真正含义说的话
Choreographer Skipped真正反映的是onVsync和doFrame两个方法调用的时间间隔
修正为
Choreographer Skipped真正反映的是Vsync信号到达App的时间和doFrame方法调用的时间间隔
场景一和场景二,只不过是通过两种方式增大了这个时间间隔而已。
3.4 onVsync被调用
我无数次的提到onVsync被调用,那到底onVsync是怎么被调用的,其实主线程的Looper.loop中一次循环会先处理native层监听的vsync信号和Input事件,处理一次java层的Message,就是类似这样子的伪代码。
代码语言:javascript复制public void loop() {
for(;;) {
//处理native层的任务,处理完所有vsync信号,input事件。
//如果发现Vsync信号已经抵达APP,就会通过JNI回调onVsync方法
doNativeTasks();
//处理java层的Message,一次处理只能处理一个Message
doJavaTasks();
}
}
从主线程的Looper角度分析场景一和场景二的流程图如下:
充分展示了Vsync黄色块和doFrame紫色块之间的时间间隔是怎么被增大的。
尾巴
还记得神雕侠侣中找到情花毒解药的天竺神僧嘛,他先让自己中毒,才找到解药。所以我们在解决一些疑难BUG的时候,需要学会如何制造BUG,才能了解BUG产生的原理,才能找到解决BUG的方案。