@(Android源码解析)
高级UI系列:
setContentView源码分析_看AppCompatActivity是如何实现兼容的
源码分析_Activity是如何显示的?
源码分析_Android UI何时刷新:Choreographer
requestLayout和invalidate都干了些什么
之前我们在分析Activity是如何显示的时候,看到它调用了requestLayout然后走了重新绘制流程,其实我们在自定义View时经常用到requestLayout,invalidate等方法,我们调用它们的目的就是告诉系统我们要刷新下界面,但是实际上是他们去刷新界面吗?下面我们来看下:
我们之前分析过当我调用requestLayout的时候不管你在哪调最后都会传递到ViewRootImp中,由它来统一调用,其实invalidate也是一样。其实关于页面绘制有关的操作最后都是通过ViewRootImp来实现的。
我们看下ViewRootImp中的requestLayout和invalidate代码:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
他们两个都调用了scheduleTraversals()翻译过来的意思就是安排遍历的意思。
其实我们只要留心会发现很多地方都调了scheduleTraversals();
@Override
public void requestChildFocus(View child, View focused) {
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "Request child focus: focus now " + focused);
}
checkThread();
scheduleTraversals();
}
@Override
public void clearChildFocus(View child) {
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "Clearing child focus");
}
checkThread();
scheduleTraversals();
}
@Override
public void recomputeViewAttributes(View child) {
checkThread();
if (mView == child) {
mAttachInfo.mRecomputeGlobalAttributes = true;
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
}
@Override
public void requestFitSystemWindows() {
checkThread();
mApplyInsetsRequested = true;
scheduleTraversals();
}
非常多我就不一一贴出来了,大体能看出来都是些跟页面显示状态有关的。所以我们猜测下 scheduleTraversals();他可能就是我们能刷新页面的关键。
总结下:在各处调用的invalidate和requestLayout最终都是调ViewRootImp中的 scheduleTraversals()方法
scheduleTraversals
下面看scheduleTraversals()的代码
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
看到里面有个postCallback方法,并有个mTraversalRunnable看名字可以猜应该是执行遍历的一个线程。不过一看这个就是个Handler那种是等待被执行的而非立即执行。我们先看下这个TraversalRunnable
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
最终执行的performTraversals();这个方法干了些什么我们在源码分析--Activity是如何显示的?中已经分析了大约800行代码核心如下:
private void performTraversals(){
...
performMeasure();
...
performLayout();
...
performDraw();
...
}
正是我们熟悉的三步走,所以performTraversals()是实现页面刷新的逻辑,但是很明显这个刷新是等待被通知的,那么何时被刷新呢?
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
我们看到这个Choreographer.CALLBACK_TRAVERSAL,事件是被Choreographer Post的,所以Choreographer应该就是通知我们何时刷新的类。
Choreographer
我们可以看下这个类的注释:Coordinates the timing of animations, input and drawing。
我翻译为控制输入、动画。绘制的时机。
可以看出这个类是告诉我们何时去执行输入、动画和绘制的。
还有一个注释也很重要:
The choreographer receives timing pulses (such as vertical synchronization) from the display subsystem then schedules work to occur as part of rendering the next display frame.
下面是个人的翻译:
Choreographer从显示的子系统中接受到类似垂直同步的时间脉冲,然后安排在下一帧中要呈现的工作。
这个注释告诉我们Choreographer要干两件事:
- 接收垂直同步的时间脉冲,
- 安排在下一帧中要做的工作。
好,我们回到之前的代码postCallback:
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
throw new IllegalArgumentException("action must not be null");
}
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is invalid");
}
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
postCallbackDelayedInternal();
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
...
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
这里有个是否立即执行的判断,如果立即执行则走scheduleFrameLocked(now);否则使用Handler在等待执行,我们搜下MSG_DO_SCHEDULE_CALLBACK可以找到:
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
scheduleFrameLocked(now);
}
}
}
}
所以都是走scheduleFrameLocked(now);区别在于是否立即执行。接着看scheduleFrameLocked()
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
USE_VSYNC:
// Enable/disable vsync for animations and drawing.
private static final boolean USE_VSYNC = SystemProperties.getBoolean(
"debug.choreographer.vsync", true);
所以if里面是true,至于false的代码我个人猜测是安排在下一帧再刷新。看还是看true都干了啥,这里跟上面一样不过是对是否为ui线程做了个判断,如果是ui线程立即执行 scheduleVsyncLocked();否则通过Handler切换回ui线程,并将这个事件放在队列最前方然后执行scheduleVsyncLocked();
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
/**
* Schedules a single vertical sync pulse to be delivered when the next
* display frame begins.
*/
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
nativeScheduleVsync(mReceiverPtr);
}
}
nativeScheduleVsync方式是native的目前看不了,所以下面我们就不跟了。不过我们可以看下scheduleVsync()这个方法的注释:Schedules a single vertical sync pulse to be delivered when the next display frame begins.,我个人翻译为:安排一个垂直同步脉冲在下一次显示帧开始时发送。
其实就是在下一帧的时候安排一个垂直同步脉冲(Vsync)给我,这个我是谁呢?就是这个方法所属的类啊
DisplayEventReceiver。一看就名字:显示事件的接收者。
先总结下:postCallback的核心就是让DisplayEventReceiver注册了个Vsync的通知。
DisplayEventReceiver
上面看到了我们注册了对Vsync的监听,那么在哪接收Vsync呢
我们看到Choreographer中有个FrameDisplayEventReceiver里面重写了onVsync()和run()方法。
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
.......
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
}
我们可以看到当我们接收到Vsync消息后,通过Handler执行了run()里面的代码。那么我们接下来就看doFrame()中做了什么:
void doFrame(long frameTimeNanos, int frame) {
.......
try {
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);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
....
}
除去一些计算操作,我们看到它主要执行了几个doCallBack,而里面有个Choreographer.CALLBACK_ANIMATION就是PostCallBack里面的。我们看下doCallback();
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
.....
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
.....
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
....
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
doCallbacks就是将CallbackQueues中的的CallbackRecord一个个取出来并执行run()方法,
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
run()其实就是执行了postCallback放进去的mTraversalRunnable。
现在在回看postCallback:
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
.....
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
.....
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
....
}
public void addCallbackLocked(long dueTime, Object action, Object token) {
CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
.....
}
private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
CallbackRecord callback = mCallbackPool;
if (callback == null) {
callback = new CallbackRecord();
} else {
mCallbackPool = callback.next;
callback.next = null;
}
.......
}
ViewRootImp在执行scheduleTraversals()方法时就是把包含Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable的信息封装到了CallbackRecord的对象中,并添加到CallbackQueue,现在doCallback中将其取出然后执行run()方法,它的run()就是执行 doTraversal();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
至此基本就串上了。
总结
我们调用View的requestLayout或者invalidate时,最终都会触发ViewRootImp执行scheduleTraversals()方法。这个方法中ViewRootImp会通过Choreographer来注册个接收Vsync的监听,当接收到系统体层发送来的Vsync后我们就执行doTraversal()来重新绘制界面。通过上面的分析我们调用invalidate等刷新操作时,系统并不会立即刷新界面,而是等到Vsync消息后才会刷新页面。
当然Choreographer还会通知动画输入等其他事件。至于关于Vsync的分析可以参考一下博客:
破译Android性能优化中的16ms问题
Android垂直同步信号VSync的产生及传播结构详解