View從setContentView
到onMeasure->onLayout->onDraw
經(jīng)歷哪些流程。下面就來梳理哈View的繪制流程
setContentView做了什么工作?
Activity.setContentView
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
這里調(diào)用PhoneWindow
的 setContentView
户誓,然后在初始化ActionBar
PhoneWindow.setContentView
public void setContentView(int layoutResID) {
...
//第一次調(diào)用這里必為空
if (mContentParent == null) {
//創(chuàng)建DecorView拄养,并將ContentParent區(qū)域的View對象賦值給mContentParent
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
...
}
創(chuàng)建DecorView
,在創(chuàng)建DecorView
時也將Window
設(shè)置DecorView
LayoutInflater.inflate
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
//預(yù)加載的View实抡,比會返回null
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
//得到資源解析器
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
//處理merge標簽
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 創(chuàng)建本層級View,也就是xml中最外層的ViewGroup爬早,并加入到ContentParent中
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// 遞歸創(chuàng)建之View,并添加到他的父View中
rInflateChildren(parser, temp, attrs, true);
}
...
return result;
}
}
createViewFromTag:方法創(chuàng)建xml中最外層父ViewGroup
窃款,將該View
添加至ContentParent
中平匈,并設(shè)置ViewGroup
的LayoutParams
.
rInflateChildren:使用遞歸的方法去創(chuàng)建子View
框沟,并添加到他們的父View
中
這樣setContentView
的工作就結(jié)束了,setContentView
的工作加載xml
解析后生成View
增炭,填充到DecorView
的ContentParent
中忍燥。將xml
資源轉(zhuǎn)為對象,并填充至DecorView
中隙姿。
下面我們繼續(xù)看View的繪制
View繪制
在上一篇《Activity啟動流程-基于Android API31》中有介紹到View在ActivityThread.handleResumeActivity中進行繪制
ActivityThread.handleResumeActivity
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
...
//執(zhí)行onResume方法
if (!performResumeActivity(r, finalStateRequest, reason)) {
return;
}
...
//添加DecorView
if (r.window == null && !a.mFinished && willBeVisible) {
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//添加decor,并使用Handler發(fā)送異步消息梅垄,用于View繪制
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
...
}
WindowManagerImpl.addView
WindowManager的實現(xiàn)類
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
//賦值windowToken, PopupDialog多次添加同一個PopupDialog時提示token問題就是這個
applyTokens(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
WindowManagerGlobal.addView
是一個單例输玷,管理ViewRootImpl
队丝、RootView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
創(chuàng)建ViewRootImpl實例
root = new ViewRootImpl(view.getContext(), display);
//設(shè)置LayoutParams
view.setLayoutParams(wparams);
//WindowManagerGlobal 記錄信息
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
//DecorView設(shè)置給ViewRootImpl
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
ViewRootImpl.setView
View的頂層類。WindowManagerGlobal
為Window提供View管理欲鹏,WindowManagerGlobal
管理所有的ViewRoot
机久、ViewRootImpl
。ViewRootImpl
更像是WindowManagerGlobal
的具體實現(xiàn)類赔嚎。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
....
requestLayout();
...
//IPC 通信膘盖,這里將ViewRootImpl的通訊句柄交給WMS,就是這里傳入的mWindow尤误,mWindow類型是ViewRootImpl的內(nèi)部類M侠畔,用于WMS對ViewRootImpl的通訊,mWindowSession用于ViewRootImpl對WMS的通訊
//這里的IWindowSession實現(xiàn)類為“com.android.server.wm.Session”损晤,最終方法內(nèi)調(diào)用WindowManagerService.addWindow添加window
//res践图,返回添加狀態(tài)詳細參考WindowManagerGlobal.ADD_xxxxx
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
mTempControls);
...
}
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//檢測主線程,不在就拋異常
checkThread();
mLayoutRequested = true;
//開啟調(diào)度
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//發(fā)送屏障消息沉馆,為繪制做準備
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//添加繪制回調(diào)
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
- 第一步發(fā)送消息屏障
- 第二步添加VSYNC信號監(jiān)聽(以60幀率計算 1s/60 最大16.6ms刷新一次)码党,當VSYNC信號來時回調(diào)
mTraversalRunnable
消息同步屏障:postSyncBarrier()
發(fā)送同步屏障消息德崭,這個消息會使Handler停止處理同步普通消息,只處理異步消息揖盘。發(fā)送屏障消息時眉厨,如果Handler處于阻塞狀態(tài)也不會去喚醒。
異步消息:普通message調(diào)用setAsynchronous(true)
兽狭,即可成為異步消息憾股,當設(shè)置了同步屏障后,優(yōu)先同步消息執(zhí)行
如果未發(fā)送屏障消息箕慧,異步消息和同步消息一樣服球,不會獲得優(yōu)先執(zhí)行的權(quán)利。
這里設(shè)置同步屏障颠焦,因為馬上要進行界面繪制斩熊,避免其他耗時消息導(dǎo)致繪制延遲。
Choreographer.postCallback
這個類對上層應(yīng)用提供VSYNC信號監(jiān)聽(FrameDisplayEventReceiver
)伐庭,當有信號量時處理調(diào)監(jiān)聽回調(diào)粉渠。對底層起到接收VSYNC信號的作用(FrameDisplayEventReceiver
)
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;
//添加回調(diào)
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
//延遲時間是否到了,一般情況都是true
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
//還未到時間發(fā)送一個異步消息
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {//true
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
//當前線程是否和looper線程一致
if (isRunningOnLooperThreadLocked()) {
//請求信號量
scheduleVsyncLocked();
} else {
//不一致用Handler切換到Looper線程
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
//老版本使用Handler去處理繪制圾另,新版本使用VSYNC信號
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() {
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
//請求信號量霸株,實際調(diào)用 DisplayEventReceiver.nativeScheduleVsync(mReceiverPtr);
mDisplayEventReceiver.scheduleVsync();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
Choreographer.FrameDisplayEventReceiver
Choreographer的內(nèi)部類繼承DisplayEventReceiver
用于監(jiān)聽信號量回調(diào),當注冊后集乔,有了信號量會回調(diào)FrameDisplayEventReceiver.dispatchVsync
方法去件,該方法為重寫所以會調(diào)用父類DisplayEventReceiver.dispatchVsync
這里有用到2個同步消息的地方,一個為了切換線程扰路,一個是老版本的利用Handler異步消息去繪制界面
FrameDisplayEventReceiver.dispatchVsync
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
...
// 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,
VsyncEventData vsyncEventData) {
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
"Choreographer#onVsync " + vsyncEventData.id);
}
// 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;
mLastVsyncEventData = vsyncEventData;
//發(fā)送同步消息箫攀,Callback為this。
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
@Override
public void run() {
//消息回調(diào)執(zhí)行這里
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}
}
這里為什么使用Handler去異步消息后執(zhí)行run
方法幼衰,不直接調(diào)用run
方法呢靴跛?應(yīng)該也是為了切換線程
Choreographer.doFrame
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
...
//掉幀日志
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= frameIntervalNanos) {
final long skippedFrames = jitterNanos / frameIntervalNanos;
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 % frameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (frameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
frameTimeNanos = startNanos - lastFrameOffset;
}
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.");
}
traceMessage("Frame time goes backward");
scheduleVsyncLocked();
return;
}
if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
traceMessage("Frame skipped due to FPSDivisor");
scheduleVsyncLocked();
return;
}
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
mLastFrameIntervalNanos = frameIntervalNanos;
mLastVsyncEventData = vsyncEventData;
}
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
//輸入事件回調(diào)處理
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);
mFrameInfo.markAnimationsStart();
//動畫回調(diào)處理
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
frameIntervalNanos);
mFrameInfo.markPerformTraversalsStart();
//消息繪制處理
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
//Commit 相關(guān)回調(diào)
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
} 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.");
}
}
void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
//根據(jù)回調(diào)類型,取出所有回調(diào)
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]);
//遍歷回調(diào)
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
//執(zhí)行run方法
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
//回收掉所有的回調(diào)對象
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
- 取出回調(diào)并執(zhí)行
run
方法 - 回收回調(diào)對象渡嚣。因為繪制(包括繪制在內(nèi)的梢睛,動畫,輸入事件等回調(diào)事件超級多)非常超級頻繁识椰,如果一直創(chuàng)建和回收會造成內(nèi)存抖動绝葡。
這里的run
方法為Choreographer.postCallback
方法傳入的TraversalRunnable
對象
TraversalRunnable.run
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
ViewRootImpl.performTraversals
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//刪除屏障信息
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//繪制
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
刪除屏障信息,讓Handler獲得繼續(xù)處理同步消息的能力
ViewRootImpl.performTraversals
private void performTraversals() {
...
//window的寬高
int desiredWindowWidth;
int desiredWindowHeight;
...
//根據(jù)window寬高去測試View樹腹鹉,是否需要修改Window藏畅,這個方法中最少執(zhí)行一次onMeasure,至多執(zhí)行三次
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
...
//重新設(shè)置Window,并且關(guān)聯(lián)surface
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...
//再次測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
if (didLayout) {
//調(diào)用Layout
performLayout(lp, mWidth, mHeight);
}
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw) {
//調(diào)用Draw
performDraw();
}
}
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
"Measuring " + host + " in display " + desiredWindowWidth
+ "x" + desiredWindowHeight + "...");
//是否測量好
boolean goodMeasure = false;
//寬度為WRAP_CONTENT
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
// On large screens, we don't want to allow dialogs to just
// stretch to fill the entire width of the screen to display
// one line of text. First try doing the layout at a smaller
// size to see if it will fit.
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
+ ", desiredWindowWidth=" + desiredWindowWidth);
//window的寬度是否大于基礎(chǔ)尺寸
if (baseSize != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
//第一次測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight()
+ ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
+ " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
//測試的尺寸合適
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
//子view想要更大的空間
baseSize = (baseSize+desiredWindowWidth)/2;
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
+ baseSize);
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
//尺寸不合適愉阎,再次測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
if (DEBUG_DIALOG) Log.v(mTag, "Good!");
goodMeasure = true;
}
}
}
}
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
//第三次測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after measure");
host.debug();
}
return windowSizeMayChange;
}
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
//RootView 希望填充Window绞蹦,則滿足它,此時它尺寸是確切值
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
//RootView 希望根據(jù)自身內(nèi)容來確定尺寸榜旦,則設(shè)置為AT_MOST 模式
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
//RootView 希望直接指定尺寸值幽七,則滿足它,此時它尺寸是確切值
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
...
//根據(jù)測量的view樹的值溅呢,重新設(shè)置window大小
int relayoutResult = mWindowSession.relayout(mWindow, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
mTempControls, mSurfaceSize);
mPendingBackDropFrame.set(mTmpFrames.backdropFrame);
if (mSurfaceControl.isValid()) {
if (!useBLAST()) {
//關(guān)聯(lián)Window和surface
mSurface.copyFrom(mSurfaceControl);
} else {
final Surface blastSurface = getOrCreateBLASTSurface();
// If blastSurface == null that means it hasn't changed since the last time we
// called. In this situation, avoid calling transferFrom as we would then
// inc the generation ID and cause EGL resources to be recreated.
if (blastSurface != null) {
mSurface.transferFrom(blastSurface);
}
}
...
///RootView記錄window大小
setFrame(mTmpFrames.frame);
...
return relayoutResult;
}
首先獲取到window的寬高澡屡,然后使用measureHierarchy去測試ViewTree,看是否需要重新設(shè)置window大小。
measureHierarchy三次測量
- 先用內(nèi)置的基礎(chǔ)值測量ViewTree
- ViewTree要得更大咐旧。于是加大基礎(chǔ)值驶鹉,再次測量
- 第二次測量還是不滿足ViewTree,這次使用window的最大寬度再次測量
設(shè)置Window的寬高為當天ViewTree
的寬高铣墨,并關(guān)聯(lián)surface
重新設(shè)置好Window后再次測量室埋,然后調(diào)用layout
和draw
繪制。
總結(jié)
setContextVIew:將xml資源轉(zhuǎn)化成View對象
onMeasure:測量View大小
onLayout:確定View位置
onDraw:繪制View對象
實際上onDraw后我們?nèi)匀皇强床坏綀D形的踏兜,因為onDraw方法只是將View對象轉(zhuǎn)成一個buffer對象,需要SurfaceFlinger
合成八秃,發(fā)送至屏幕碱妆,才可以見到真正的圖片。
View對象是如何轉(zhuǎn)成buffer
的呢昔驱,這里就會用到上面綁定Surface
-
Surface
通過dequeueBuffer
獲取到一塊繪制緩沖區(qū) -
Canvas
在draw
時會調(diào)用底層Skia
引擎進行數(shù)據(jù)寫入 -
Surface
通過queueBuffer
提交數(shù)據(jù)到SurfaceFlinger
-
SurfaceFlinger
接受區(qū)對這些數(shù)據(jù)進行合疹尾,發(fā)送到屏幕顯示
SurfaceFlinger:接受緩沖區(qū),對它們進行合并骤肛,然后發(fā)送到屏幕顯示