1. 前言
當 Activity 啓動時候,會調用到 ActivityThread 的 handleResumeActivity 方法。在瞭解此篇文章的時候,先了解 Activity 、 Window 、 View 之間的關係,從這裏可以瞭解到 PhoneWindow、DecorView 和 ViewGroup/View 的關係。此篇文章將從 ActivityThread 中的 handleResumeActivity 開始解析。
2. 繪製流程
在 ActivityThread 的 handleResumeActivity 中:
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
if (r == null) {
// We didn't actually resume the activity, so skipping any follow-up actions.
return;
}
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l); // 1
} else {
a.onWindowAttributesChanged(l);
}
}
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
...
}
這裏的核心代碼 wm.addView(decor, l), vm 是 WindowManagerImpl,decor 是 DecorView。其中 WindowManagerImpl 的繼承關係如圖所示:
WindowManagerImpl 中 addView 方法:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
調用 WindowManagerGlobal 的 addView 方法,這裏的 view 是前面傳遞進來的 decorView,addView 方法如下:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
此方法中創建了 ViewRootImpl 對象,並且調用 ViewRootImpl 的 setView 方法,並且將 decorView 傳遞進去,這樣 ViewRootImpl 就跟 decorView 聯繫起來了,ViewRootImpl 的繼承關係如下:
setView 方法:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
...
}
}
}
此方法中的核心是調用了 requestLayout 方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
接着調用了 scheduleTraversals 方法:
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
通過調用 Choreographer 啓動一個TraversalRunnable:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
接着調用 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 方法:
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
...
if (!mStopped || mReportNextDraw) {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
...
if (didLayout) {
performLayout(lp, mWidth, mHeight);
...
}
...
performDraw();
...
}
核心就是三個方法:performMeasure、performLayout、performDraw。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
這裏的 mView 是前面傳遞過來的 DecorView,DecorView 是 FrameLayout ,本質是一個 ViewGroup。所以一開始執行的是 ViewGroup 的 measure 方法,並且按照以下的流程逐一對子 View 進行測量:
這裏以 DecorView 爲例子,在 DecorView 和 ViewGroup 中是找不到 measure 方法,measure 方法的實現在父類 View 中:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if (forceLayout || needsLayout) {
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
...
}
...
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
...
}
這裏的核心方法是調用了自己本身的 onMeasure 方法,對於 DecorView 來講,onMeasure 方法的實現在 FrameLayout 中:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
...
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
...
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
如果子 View 的個數大於0,則又調用了子 View 的 measure 方法,整體邏輯符合上面的流程圖。
3. 屏幕刷新機制
Android 屏幕每16秒會刷新一次,也就是每秒會刷新60次,人眼能感覺到卡頓的幀率是每秒24幀。要求我們的應用都能在 16ms 內繪製完成。如果有一次的界面繪製用了 22ms,那麼,用戶在 32ms 內看見的都是同一個界面。就會讓用戶覺得卡頓。而 Android 是如何進行刷新的呢?
在上面的流程中,方法 scheduleTraversals:
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//1
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//2
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
在上面的分析中,並沒有具體分析這裏面執行的方法,接下來將具體分析下。
註釋1:設置了同步屏障,這裏的作用是爲了更快的響應 Ui 刷新事件。設置了同步屏障之後,同步屏障爲 Handler 消息機制增加了一種簡單的優先級機制,異步消息的優先級要高於同步消息。關於同步屏障,可以參考Handler之同步屏障機制(sync barrier)。
在註釋 2 中調用了 Choreographer 的 postCallback 方法:
@UnsupportedAppUsage
@TestApi
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
postCallbackDelayed 方法,這裏的參數 delayMillis 傳遞 0 進來:
@UnsupportedAppUsage
@TestApi
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) {
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;
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);
}
}
}
由於傳遞進來的 delayMillis 爲0,則執行 scheduleFrameLocked 方法:
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on 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()) { // 3
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);
}
}
}
註釋3:判斷當前線程是 UI線程,執行 scheduleVsyncLocked 方法:
@UnsupportedAppUsage
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
調用 DisplayEventReceiver 的 scheduleVsync 方法 ,DisplayEventReceiver 是一個抽象類,FrameDisplayEventReceiver 是它的實現類:
/**
* Schedules a single vertical sync pulse to be delivered when the next
* display frame begins.
*/
@UnsupportedAppUsage
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);
}
}
這裏調用了一個 native層的方法,此方法的作用:通知底層,下一個 VSync 信號來的時候請通知我,當 VSync 信號來的時候,就會收到底層的 JNI 回調,會調用到 dispatchVsync 方法:
// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
onVsync(timestampNanos, physicalDisplayId, frame);
}
onVsync 方法的實現在 Choreographer 的內部類 FrameDisplayEventReceiver 中:
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, CONFIG_CHANGED_EVENT_SUPPRESS);
}
// TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
// the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
// for the internal display implicitly.
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
// Post the vsync event to the Handler.
// The idea is to prevent incoming vsync events from completely starving
// the message queue. If there are no messages in the queue with timestamps
// earlier than the frame time, then the vsync event will be processed immediately.
// Otherwise, messages that predate the vsync event will be handled first.
long now = System.nanoTime();
if (timestampNanos > now) {
Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+ " ms in the future! Check that graphics HAL is generating vsync "
+ "timestamps using the correct timebase.");
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;
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);
}
}
當接收到信號時候,就會執行 run 方法中的 doFrame 方法:
@UnsupportedAppUsage
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
...
long intendedFrameTimeNanos = frameTimeNanos;//設置當前幀的Vsync信號到來時間
startNanos = System.nanoTime();//實際開始執行當前幀的時間
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {//時間差大於一個時鐘週期,認爲跳幀
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
frameTimeNanos = startNanos - lastFrameOffset;// 出現掉幀,把時間修正一下,對比的是上一幀時間
}
//時間倒退了,可能是由於改了系統時間,此時就重新申請vsync信號(一般不會走這裏)
if (frameTimeNanos < mLastFrameTimeNanos) {//
if (DEBUG_JANK) {
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
}
scheduleVsyncLocked();
return;
}
if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
scheduleVsyncLocked();//申請下一次vsync信號,流程跟上面分析一樣
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);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
...
}
計算收到 VSync 信號到 doFrame 被調用的時間差,VSync 信號間隔是16毫秒一次,大於16毫秒就是掉幀。如果超過30幀(默認30),就打印log提示開發者檢查主線程是否有耗時操作。
接着執行 doCallbacks 方法:
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
...
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
...
c.run(frameTimeNanos);
}
} finally {
...
}
}
這裏的核心的就是取出隊列中任務,也就是 doTraversal 方法。
4. 總結
當 Activity 啓動時候,會調用到 ActivityThread 的 handleResumeActivity 方法,接着調用 ViewRootImpl 的 setView 方法。setView 方法中會調用 requestLayout,requestLayout 方法中調用 scheduleTraversals,接着 doTraversal ,最終調用到了 performTraversals。接着分別執行 performMeasure、performLayout、performDraw 方法。performMeasure 方法中首先會執行 measure 方法,接着執行 onMeasure 方法。在 onMeasure 方法中如果當前 View 存在子 View,則遍歷執行子 View 的 measure ,最終完成測量的工作。performLayout 和 performDraw 的邏輯也一樣。
在 scheduleTraversals 方法中,通過 Choreographer 類向 native 請求 VSync(垂直同步)信號,下一次 VSync(垂直同步)信號來的時候通過 JNI 調用 onVsync 方法通知應用層,並把消息發送到主線程,請求執行 doFrame 渲染下一幀。