前语
针对Android UI不流通的问题,Google提出了Project Butter对Android的显现体系进行了重构。 这次重构的三个要害点
- VSynch 笔直同步
- Triple Buffer 三重缓存
- Choreographer 编舞者
这篇文章咱们主要聊一聊Choregrapher,后续的咱们写关于其他。
choreographer
界面的显现大体会经过CPU的计算-> GPU合成栅格化->显现设备显现。咱们知道Android设备的改写频率一般都是60HZ也便是一秒60次,假如一次制作在约16毫喵内完成时没有问题的。但是一旦出现不协调的当地就会出问题如下图
- 第一个周期内cpu计算、GPU操作、显现设备显现没有问题
- 第二周期内,显现上一个周期制作准备好的图像,没有问题。CPU计算是在周期将要完毕的时分才开始计算
- 第三个周期,因为进入第第二个周期的时分CPU动手计算晚点了导致后续,进入第三个周期的时分本应该显现的图像没有准备好,导致整个第三个个周期内仍是显现上一个周期的图像,这样看上去会卡,掉帧!google工程师们管整个叫Jank延误军机。
这事不小啊,怎样解决呢?笔直同步 简略的说,便是让CPU计算别没有方案没有规律而是在每个周期开始的时分开始计算,紧接着这样就有条有理的有序进行了(如下图)。这个在android4.1及以后的版别中加入了Choreographer这个类,让咱们扒开看看他是怎样完成的。
一、入口
1.1 postCallbackDelayedInternal
ViewRoot的 doTravle()办法中有这样一行代码
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
它的意思便是对整个View树发起丈量布局制作操作。关于ViewRootImpl的更多内容这儿就不多介绍了。
以下办法
- public void postCallback(…) ;
- public void postCallbackDelayed(…);
- public void postFrameCallback(FrameCallback callback);
- public void postFrameCallbackDelayed(FrameCallback callback, long 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;//到期时刻
//将履行动作放在mCallback数组中
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
//假如现已到期就注册恳求笔直同步信号
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
//假如还没有到期,运用handler在发送一个延时音讯。这个延时音讯会在到期的时分履行。
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
1.2 FrameHandler
上一末节,假如现已到期就直接履行scheduleFrameLocked()办法,假如没有履行就运用mHandler(FrameHandler类型)发送一个what值为MSG_DO_SCHEDULE_CALLBACK的Message。到期后怎样履行的呢。这要看FrameHandler怎样处理的。
private 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;
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
//postCallbackDelayedInternal()办法中当未到期的时分发送过来的
doScheduleCallback(msg.arg1);
break;
}
}
}
以上代码咱们能够看出这个,FramHandler拿到 whate特点值为MSG_DO_SCHEDULE_CALLBACK的时分会去履行 doScheduleCallback(msg.arg1);办法,跟进去看下
1.3 Choreography#doScheduleCallback
void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
scheduleFrameLocked(now);
}
}
}
}
这个办法中先是做了一些判别,mFrameSceduled为false 并且hasDueCallbacksLocked()这个办法的回来值为true,看办法名就能猜出这个callback是否到期了,下面咱们再剖析这个。终究假如满足条件的情况下它会调用 scheduleFrameLocked()这个办法,咦这个办法眼熟不?对,没错,postCallbackDelayedInternal()办法中假如到期了的话就直接履行的那个办法。是时分看这个办法里面搞的什么工作了。
1.4 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 {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
- 这个办法的意图很清晰便是组织,组织笔直同步而且立刻立刻赶快。组织笔直同步的条件是USE_VSYNC为true,也便是设备支撑笔直同步
- 假如不是笔直同步就经过handler发送一个延时一个周期的音讯组织笔直同步,这个Message的what值为 MSG_DO_FRAME,参照1.2的代码块对what为MSG_DO_FRAME的音讯会去履行doFrame()办法。
- 一个细节,当这个值mFrameScheduled为true的时分就直接回来不组织恳求下一帧烘托了,假如为false,履行scheduleFrameLocked()办法继续履行,并且将其设置为ture;在什么时分设置为false的呢?详细细节看附录二
组织笔直同步的具体完成是FrameDisplayEventReceiver类他是DisplayEventReceiver的用于接收笔直信号
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) {
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);//Message设置为异步
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
}
接收到笔直同步信号后回调onVsync办法,这个办法运用handler发送带callback(Runnable类型,自身已承继)的message,最终run()中也是调用doFrame();(关于这个handler的这个操作详细信息逻辑,参照下面本文附录一 handler 分发message
这个message设置为了异步 (msg.setAsynchronous(true);)这意味这他有优先履行的权利,他是怎样被优先履行的呢?参照附录三 message的异步形式
综上,增加callback流程
二、履行
doFrame
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}
//当时时刻
startNanos = System.nanoTime();
//当时时刻和笔直同步时刻
final long jitterNanos = startNanos - frameTimeNanos;
//笔直同步时刻和当时时刻的差值假如大于一个周期就批改一下
if (jitterNanos >= mFrameIntervalNanos) {
//取插值和一直周期的余数
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
//当时时刻减去上一步得到的余数当作最新的一直信号时刻
frameTimeNanos = startNanos - lastFrameOffset;
}
//笔直同步时刻上一次时刻还小,就组织下次笔直,直接回来
if (frameTimeNanos < mLastFrameTimeNanos) {
scheduleVsyncLocked();
return;
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
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);
}
if (DEBUG_FRAMES) {
final long endNanos = System.nanoTime();
Log.d(TAG, "Frame " + frame + ": Finished, took "
+ (endNanos - startNanos) * 0.000001f + " ms, latency "
+ (startNanos - frameTimeNanos) * 0.000001f + " ms.");
}
}
- 第一步批改判别
-
当时时刻 startNanos = System.nanoTime();
-
求当时时刻和笔直同步时刻的差值 :jitterNanos = startNanos – frameTimeNanos;
-
笔直同步时刻和当时时刻的差值假如大于一个周期(jitterNanos >= mFrameIntervalNanos)就批改一下
- 取插值和一直周期的余数:lastFrameOffset = jitterNanos % mFrameIntervalNanos;
- 当时时刻减去上一步得到的余数当作最新的一直信号时刻:frameTimeNanos = startNanos – lastFrameOffset;
-
笔直同步时刻上一次时刻还小,就组织下次烘托: frameTimeNanos < mLastFrameTimeNanos,直接回来
- 第二步履行callback callback的履行次序是:
- CALLBACK_INPUT输入时刻优先级最高
- CALLBACK_ANIMATION 动画的次之
- CALLBACK_TRAVERSAL UI制作布局的再次之
- CALLBACK_COMMIT动画批改相关最终。
2.2 doCallbacks();
- 在 CallbackQueue[] mCallbackQueues在取特定类型(输入,动画,布局,Commit)的单向链表
- 然后取出已到期的Callback/Runable履行
取出需求被履行的Actions
Action包装在CallbackRecord中,是一个单向列表,依照时刻的大小次序排列的。 取出待履行的Actions是经过CallBackQueue的extractDueCallbacksLocked()办法,能够把CallBackQueue看做是CallBack的管理类,其中还包含增加Action addCallbackLocked(),移除Action removeCallbacksLocked(),是否有带起的Anction hasDueCallbacksLocked()办法。
private final class CallbackQueue {
//链表头
private CallbackRecord mHead;
//是否存在现已到期的Action
public boolean hasDueCallbacksLocked(long now) {
return mHead != null && mHead.dueTime <= now;
}
//获取现已到期的Action
public CallbackRecord extractDueCallbacksLocked(long now) {
...
return callbacks;
}
//增加Action
public void addCallbackLocked(long dueTime, Object action, Object token) {
...
}
//移除Action
public void removeCallbacksLocked(Object action, Object token) {
...
}
}
履行Action
for (CallbackRecord c = callbacks; c != null; c = c.next) {
c.run(frameTimeNanos);
}
从callback中遍历出CallBcakRecord,挨个履行。
三、小结
-
Choreographer对外供给了postCallback等办法,终究他们内部都是经过调用postCallbackDelayedInternal()完成这个办法主要会做两件工作
- 存储Action
- 恳求笔直同步,笔直同步
-
笔直同步回调立马履行Action(CallBack/Runnable)。
-
Action一个动作内容的类型可能是
- CALLBACK_INPUT输入时刻优先级最高
- CALLBACK_ANIMATION 动画的次之
- CALLBACK_TRAVERSAL UI制作布局的再次之
- CALLBACK_COMMIT动画批改相关最终。
-
温习Hanlder机制,我以为他是Android体系跑起来的大引擎结尾重视下,handler对message的分发履行,以及“异步形式”。
附
附一、关于handler履行Message
下面是handler分发逻辑,Looper在MessageQueue得到要履行的message之后就会交给message的target(Handler类型)特点处理msg.target.dispatchMessage(msg);;
public void dispatchMessage(Message msg) {
//当msg的callback不为空的时分直接履行msg的callback它是一个Runnable目标
if (msg.callback != null) {
handleCallback(msg);
} else {
//然后再交给mCallBack,它是handler的一个特点,
//创建Handler的时分能够选择传入一个CallBack目标
//当callBack中handleMessage回来true的时分表明:True if no further handling is desired(不需求进一步处理)
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//当mCallback处理回来为false的时分才去履行Handler自身的handleMessage()办法
handleMessage(msg);
}
}
要害逻辑在已注释,小结一下 handler的履行分发Message逻辑
- 假如message的callback(runnable)特点不为空,调用这个runable的run()办法履行
private static void handleCallback(Message message) {
message.callback.run();
}
当咱们运用handler.post(Runnable r)办法时分便是将r设置给message的callback
- 上述条件不满足的情况下,假如handler自身的mCallback不为空的时分就会,将message交给mCallback处理,handlerMessage()完毕。这个特点是在handler创建的时分传入的。mCallback是CallBack类型,他是handler的一个内部接口。
public interface Callback {
boolean handleMessage(Message msg);
}
3.当messaga 的callBak为空,且handler的mCallBack为空的时分就交给自己的handlerMessage()办法履行了。咱们在自定义handler的时分能够重写这个办法对message进行相应的操作。
附二 、mFrameScheduled特点效果
- 履行callcack的时分会判别mFrameScheduled特点假如为false表明没有组织烘托下一帧就直接回来,不履行。
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}
...
...
mFrameScheduled = false;
...
}
- 在scheduleFrameLocked()办法中,将mFrameScheduled值设置为ture表明组织了恳求烘托下一帧。假如这时mFrameScheduled为true表明现已组织了下一帧那么就回来,不添乱!
附三、Handler机制的异步形式
效果
“异步形式”的效果便是优先,asynchronous 的message在异步形式下有优先履行的权。
用法
MessageQueue运用postSyncBarrier()办法增加屏障,removeSyncBarrier()办法移除屏障这个两个办法是成对运用的。
完成原理
- messageQueued的postSyncBarrier办法向messagequeue的头部增加一个target特点为null的message
- messageQueue的next()当碰到target为null的message的时分就只会在message链表中取出去“异步message”,而疏忽一般的message,交给Looper做进一步分发处理。
Message next() {
...
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous messagin the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
} ...
return msg;
}
...