- Android刷新機制
- SurfaceView理解
1、Android屏幕刷新機制canvas
首先須要瞭解一些基本概念bash
在以前的幾篇文章裏,例如 Window和WindowManager相關知識點(六) 以及 Android View相關知識點以及原理(四) 內對DecorView和setContentView以及Window的聯繫能夠知道在onResume的時候纔會建立ViewRootImpl將DecorView和Window關聯起來,而且和任意View調用invalidate等刷新同樣都會走ViewRootImpl中的 scheduleTraversals()方法,而後調用Choreographer的postCallBack方法數據結構
void scheduleTraversals() {
if (!mTraversalScheduled) {
//編號1
mTraversalScheduled = true;
//編號2
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//編號3
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
複製代碼
Choreographer的postCallBack中傳了一個mTraversalRunnable,你們能夠看下面代碼,run方法內調用了doTraversal方法多線程
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
//編號4
mTraversalScheduled = false;
//編號5
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//這裏開始對view樹進行測量、佈局、繪製
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
複製代碼
這裏會產生一個疑問,調用了scheduleTraversals方法後,代碼裏只是將Runnable做爲參數傳遞到了Choreographer的postCallback方法中,要想調用doTraversal方法,那必需要有某處執行這個Runnable。爲了解清楚執行邏輯,請看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);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
//編號6
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);
}
}
}
複製代碼
它最終會調用postCallbackDelayedInternal方法,而傳遞進來的delayMillis是0,因此dueTime = now,因此調用的是scheduleFrameLocked方法,接下來看它的調用代碼ide
//方法
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
//是否在主線程
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);
}
}
}
//方法
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
//方法 DisplayEventReceiver
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);
}
}
//方法 FrameDisplayEventReceiver類
private static native void nativeScheduleVsync(long receiverPtr);
複製代碼
能夠發現最終調用的是native方法,到這裏就無法跟下去了,換種思路,既然以前把Runnable放到了CallbackQueue中,見第4點的編號6註釋,那麼調用時機必定對應於從CallbackQueue取出Runnable,也就是CallbackQueue的extractDueCallbacksLocked方法,通過查找,能夠得出是doCallbacks方法內調用了該方法( CallbackQueue 是Choreographer的內部類 ) ,而調用doCallbacks方法的是doFrame方法。(這裏能夠看到一個callbackType對應一個Runnable隊列,mCallbackQueue[callbackType])oop
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (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. final long now = System.nanoTime(); callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( now / TimeUtils.NANOS_PER_MS); ..... } } void doFrame(long frameTimeNanos, int frame) { final long startNanos; synchronized (mLock) { ...... try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame"); AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart(); doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); ...... 複製代碼
那麼doFrame方法是哪裏調用的呢?從以前得出了是調用FrameDisplayEventReceiver的native方法後就跟不下去了,那麼進入這個類中能夠發現它的 run 方法偏偏調用了doFrame方法,而官方對它其中的onVsync方法也有註釋 Called when a vertical sync pulse is received ,說明這是底層回調用的。佈局
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
Log.d(TAG, "Received vsync from secondary display, but we don't support "
+ "this case yet. Choreographer needs a way to explicitly request "
+ "vsync for a specific display to ensure it doesn't lose track "
+ "of its scheduled vsync.");
scheduleVsync();
return;
}
// Post the vsync event to the Handler.
long now = System.nanoTime();
if (timestampNanos > now) {
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;
//編號7
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);
}
複製代碼
從Handler的處理消息機制來看,咱們知道通常若是傳遞了Runnable,會執行它的run方法,不然看你有沒有指定Callback,有的話會執行你的Callback,沒有則會調用handleMessage,因此 編號7 處,將this傳遞給了Message,後續將執行本身的run方法,繼而執行doFrame方法,而後就會執行doCallbacks方法,而後會執行Runnable對象的run方法,run方法內又會執行doTraversals方法,這就開始刷新view咯post
小結一下,FrameDisplayEventReceiver繼承DisplayEventReceiver接受底層的VSync信號開始處理UI過程,VSync信號由SurfaceFlinger實現並定時發送。FrameDisplayEventReceiver收到信號後,調用onVsync方法組織消息發送到主線程處理。這個消息的內容主要就是run方法裏面的doFrame了。FrameDsiplayEventReceiver之因此能收到信號,回調onVsync方法,能夠理解爲APP層在調用native方法nativeScheduleVsync時向底層註冊了一個屏幕刷新信號監聽事件,要否則底層怎麼知道APP須要刷新數據呢?而且APP對底層是隱藏的,底層壓根不知道APP的存在。優化
梳理一下,APP經過native方法向底層註冊了下一幀的屏幕刷新界面,而後在每16.6ms的幀信號到來時,它就會回調onVsync刷新屏幕了
那麼問題來了,是否是在16.6ms內或者是屏幕刷新前,我能夠無限註冊該監聽事件,也就是一幀內是否是會註冊不少重複監聽
首先看段代碼
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//編號 8
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//編號 9
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
......
}
複製代碼
首先看scheduleTraversals方法,它在向底層註冊監聽前,有一個mTraversalScheduled變量,該變量默認false,屢次調用scheduleTraversals方法,只要mTraversalScheduled爲false纔會重複註冊,而從doTraversal方法中能夠看出只有接收到一幀信號是該變量纔會重置爲false。那爲何這麼設計呢?查看performTraversals方法就能夠知道,界面刷新調用了這個方法,方法內能夠層層遍歷View樹的,須要刷新的View都會遍歷到並刷新的,因此也就沒有必要重複註冊吧
接下來細心的人可能會問,在onVsync方法內的 編號7 處代碼,這裏會將刷新消息封裝到Message裏面放到主線程的MessageQueue中,而我們的主線程也是一直在處理MessageQueue裏面的消息的,同一時間只能處理一個Message,若是其它消息耗時,致使刷新消息在16.6ms內還沒處理,該怎麼辦
小結
2、SurfaceView