之前文章提到 View
的根是 ViewRootImpl
這個類。那么他們又是由誰關(guān)聯(lián)起來的呢?
要說這些關(guān)系之前,先了解一些接口:
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
實現(xiàn) ViewManager
這個接口之后,你具有對View的基本操作(增刪改),另外還有之前常用到的 ViewParent
接口,每一個 ViewGroup
,都實現(xiàn)了這兩個接口。
接下來提出4 個問題:
-
ViewRootImpl
被誰創(chuàng)建和管理 -
ViewRootImpl
和Window
對應(yīng)關(guān)系 -
View
什么時候可以拿到具體寬高 -
View
的事件分發(fā)源頭在哪兒
ViewRootImpl
先看看 ViewRootImpl
的定義:
/**
* The top of a view hierarchy, implementing the needed protocol between View
* and the WindowManager. This is for the most part an internal implementation
* detail of {@link WindowManagerGlobal}.
*
* {@hide}
*/
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
先看注釋,ViewRootImpl
是 View
樹的頂層。強調(diào)它有實現(xiàn) View
和 WindowManager
之間必要的協(xié)議,是WindowManagerGlobal
內(nèi)部實現(xiàn)中重要的組成部分。其實信息點挺多,直接就涉及到開頭要說的那個問題,View
和 Window
之間的關(guān)系。另外還提到它和 WindowManagerGlobal
的關(guān)系,看這架勢,它的很多方法可能都是被 WindowManagerGlobal
調(diào)用。
在看接口實現(xiàn)和調(diào)用,它實現(xiàn)了 ViewParent
接口,但是,它并沒有實現(xiàn) ViewManager
接口。對比 ViewGroup
,它少了 ViewManager
對 View
的增刪改能力。
接著看看它的構(gòu)造方法:
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
mBasePackageName = context.getBasePackageName();
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
mWidth = -1;
mHeight = -1;
mDirty = new Rect();
mTempRect = new Rect();
mVisRect = new Rect();
mWinFrame = new Rect();
mWindow = new W(this);
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
mViewVisibility = View.GONE;
mTransparentRegion = new Region();
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mAdded = false;
// attachInfo 很重要
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
mAccessibilityManager = AccessibilityManager.getInstance(context);
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager, mHandler);
mHighContrastTextManager = new HighContrastTextManager();
mAccessibilityManager.addHighTextContrastStateChangeListener(
mHighContrastTextManager, mHandler);
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
mFallbackEventHandler = new PhoneFallbackEventHandler(context);
mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
if (!sCompatibilityDone) {
sAlwaysAssignFocus = true;
sCompatibilityDone = true;
}
loadSystemProperties();
}
ViewRootImpl
創(chuàng)建,初始化準(zhǔn)備了很多東西,著重強調(diào) AttachInfo
創(chuàng)建,這個類很重要,之前說的 軟解時 Canvas
的保存和復(fù)用,還有 View.post()
方法執(zhí)行等等。
之前文章中,已經(jīng)或多或少提到 ViewRootImpl
的職責(zé)。比如說測量繪制最后都是由它發(fā)起。
在 ViewRootImpl
中,TraversalRunnable
是一個重要的角色。
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
它內(nèi)部調(diào)用 doTraversal()
方法,最終觸發(fā) performTraversals()
,在這個方法中,就開始了對整個 View
樹的測量繪制等等一系列操作。再細(xì)分就是根據(jù)情況調(diào)用 performMeasure()
performLayout()
performDraw()
方法,最后就回調(diào)到具體 View
的 onMeasure()
onLayout()
和 onDraw()
方法,這個具體流程,在前面文章中有相關(guān)分析。
ViewRootImpl
根據(jù)注釋,是 WindowManagerGlobal
的重要組成部分,那就先瞅瞅 WindowManagerGlobal
是個啥呢?
WindowManagerGlobal
要說 WindowManagerGlobal
,那就要先看 WindowManager
接口啦,這個接口實現(xiàn)了 ViewManager,
說明它擁有對 View
的控制能力。根據(jù)注釋,這個接口就是我們用來和遠(yuǎn)程服務(wù) WindowManager
service 溝通的。它的實現(xiàn)類就是 WindowManagerImpl
。WindowManagerImpl
中,有一個 mGlobal
字段:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
到這里,看到 WindowManagerGlobal
的一點蹤影了。接著深入其內(nèi)部一探究竟。
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
從定義的這些字段就可以看出,在 WindowManagerGlobal
內(nèi)部是管理著 ViewRootImpl
和 其對應(yīng)的 RootView
還有對應(yīng)的 LayoutParams
等等。
上面講過,ViewRootImpl
只實現(xiàn)了 ViewParent
接口,并沒有實現(xiàn) ViewManager
接口,喪失了部分 parent 的能力。其實這部分能力就交由 WindowManager
,對于 ViewRootImpl
來說,再向上的 View
增傷改功能是和 Window
交互,需要和系統(tǒng)服務(wù)打交道。
在 WindowManagerImpl
中,我們看看 ViewManager
接口相關(guān)方法具體實現(xiàn):
//WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
可以看到,全都是交由 WindowManagerGlobal
做具體實現(xiàn)。 WindowManagerImpl
代理了遠(yuǎn)程系統(tǒng)服務(wù), WindowManagerGlobal
代理了 WindowManagerImpl
的具體實現(xiàn)。
//WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//設(shè)置Window一些基本參數(shù)
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
//監(jiān)聽系統(tǒng)屬性設(shè)置變化:邊界布局 硬件加速支持等設(shè)置變化
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
// 判斷有沒有已經(jīng)添加過
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
類似 ViewGroup 中child 已經(jīng)有 parent 就會拋出異常
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
// 這里涉及到 Window 類型處理
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
// 創(chuàng)建 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// 保存到對應(yīng)集合
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
// 調(diào)用 ViewRootImpl 的 set 方法
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
可以看到,在 WindowManager
的 addView()
方法中,最后會創(chuàng)建出一個新的 ViewRootImpl
,并調(diào)用 ViewRootImpl
的 setView()
方法。
前面提到的前兩個問題已經(jīng)有了答案, ViewRootImpl
被 WindowManagerGlobal
創(chuàng)建, ViewRootImpl
和 Window
的對應(yīng)關(guān)系是多對一,一個 Window
可以有多個 ViewRootImpl
。
接著看看 ViewRootImpl
中的 setView()
方法。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
mAttachInfo.mDisplayState = mDisplay.getState();
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
...
mSoftInputMode = attrs.softInputMode;
mWindowAttributesChanged = true;
mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED;
mAttachInfo.mRootView = view;
mAttachInfo.mScalingRequired = mTranslator != null;
mAttachInfo.mApplicationScale =
mTranslator == null ? 1.0f : mTranslator.applicationScale;
if (panelParentView != null) {
mAttachInfo.mPanelParentWindowToken
= panelParentView.getApplicationWindowToken();
}
mAdded = true;
int res; /* = WindowManagerImpl.ADD_OKAY; */
// 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.
// 調(diào)用這個方法之后會直接出發(fā) requestLayout()
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
...
if (res < WindowManagerGlobal.ADD_OKAY) {
mAttachInfo.mRootView = null;
mAdded = false;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
switch (res) {
// 異常處理
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
...
}
throw new RuntimeException(
"Unable to add window -- unknown error code " + res);
}
if (view instanceof RootViewSurfaceTaker) {
mInputQueueCallback =
((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
}
// 輸入事件相關(guān)
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
// Window 事件 receiver
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
view.assignParent(this);
mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;
if (mAccessibilityManager.isEnabled()) {
mAccessibilityInteractionConnectionManager.ensureConnection();
}
if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
}
}
}
setView()
方法里面邏輯挺多的,首先會直接調(diào)用一次 requestLayout()
,然后會處理 addView()
的異常情況,類似于 Badtoken 這些異常。最后還有添加 Window
事件相關(guān)的監(jiān)聽。
在調(diào)用 requestLayout()
之后, View
會進(jìn)行相關(guān)測量繪制。在這之后,肯定能拿到 View
對應(yīng)寬高。那么第三個問題, View
什么時候能拿到對應(yīng)寬高,似乎說是在 ViewRootImpl
調(diào)用 setView()
方法之后也沒什么毛病。
那么對于 Activity
而言,什么時候會調(diào)用到 WindowManager.addView()
呢?通常我們會在 onCreate()
回調(diào)方法中調(diào)用 setContentView()
添加對應(yīng)布局。那么這個方法的 View
是什么時候被真正添加到 Window
上的呢,或者說,這個 View
到底被添加到哪里了呢 ?
Activity
的啟動是在 ActivityThread
類中進(jìn)行的。在 ActivityThread
的 performLaunchActivity()
中,會完成 Activity
的創(chuàng)建(反射),并且會調(diào)用 Activity.attach()
方法,這個方法在第一篇文章 Activity
何時添加 LayoutInflater
Factory
時有提及。
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
// 創(chuàng)建出 PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// 設(shè)置相關(guān) callback
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
}
對于本文最重要的就是在 attach()
中給 Activity
創(chuàng)建了對應(yīng)的 PhoneWindow
, 有了 PhoneWindow
才能有后文。對于 Activity
,我們設(shè)置的 ContentView
并不是頂層 View
,最頂層應(yīng)該是 DecorView
, DecorView
是定義在 PhoneWindow
中:
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
所以,現(xiàn)在的問題就是 DecorView
什么時候被添加到 PhoneWindow
上。在 ActivityThread
中有 handleResumeActivity()
方法,在這個方法中:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
...
// 在 performResumeActivity() 中會回調(diào) onResume()
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
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 (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 調(diào)用 windowManager.addView()
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
...
}
代碼只保留 addView()
相關(guān)部分,在調(diào)用這個之前,會先調(diào)用 performResumeActivity()
,在這個方法中,就會回調(diào) Activity
的 onResume()
方法。也就是說,在 Activity
中,一個 View
的寬高,是在 Activity
第一次回調(diào) onResume()
方法之后,第一次的 onResume()
方法時并不能拿到寬高。 在這之后, DecorView
才添加到 PhoneWindow
中,接著觸發(fā) windowManagerGlobal.addView()
方法,接著調(diào)用 ViewRootImlp.setView()
方法,然后開始 requestLayout()
,最后觸發(fā) performTraversals()
方法,在這個方法中將會調(diào)用 View
的 measure()
layout()
和 draw()
方法。
performTraversals()
// ViewRootImpl
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
if (host == null || !mAdded)
return;
mIsInTraversal = true;
mWillDrawSoon = true;
boolean windowSizeMayChange = false;
boolean newSurface = false;
boolean surfaceChanged = false;
WindowManager.LayoutParams lp = mWindowAttributes;
int desiredWindowWidth;
int desiredWindowHeight;
...
Rect frame = mWinFrame;
// 第一次 執(zhí)行
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
...
// 設(shè)置一些基本參數(shù)
mAttachInfo.mUse32BitDrawingCache = true;
mAttachInfo.mHasWindowFocus = false;
mAttachInfo.mWindowVisibility = viewVisibility;
mAttachInfo.mRecomputeGlobalAttributes = false;
mLastConfigurationFromResources.setTo(config);
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
// Set the layout direction if it has not been set before (inherit is the default)
if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
host.setLayoutDirection(config.getLayoutDirection());
}
// mFirst 時調(diào)用 dispatchAttachedToWindow()
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
//Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
} else {
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
mFullRedrawNeeded = true;
mLayoutRequested = true;
windowSizeMayChange = true;
}
}
...
// Non-visible windows can't hold accessibility focus.
if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
host.clearAccessibilityFocus();
}
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
boolean insetsChanged = false;
// 是否需要 layout 的標(biāo)志 第一次為 true
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
// 第一次測量
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
} else {
...
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
Configuration config = res.getConfiguration();
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
}
}
// measureHierarchy() 可能會修改 Windowsize 內(nèi)部會調(diào)用 performMeasure()
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
if (collectViewAttributes()) {
params = lp;
}
if (mAttachInfo.mForceReportNewAttributes) {
mAttachInfo.mForceReportNewAttributes = false;
params = lp;
}
...
if (layoutRequested) {
// 這里已經(jīng)重置 mLayoutRequested
mLayoutRequested = false;
}
<---- 判斷 Window是否改變 --->
boolean windowShouldResize = layoutRequested && windowSizeMayChange
&& ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
// If the activity was just relaunched, it might have unfrozen the task bounds (while
// relaunching), so we need to force a call into window manager to pick up the latest
// bounds.
windowShouldResize |= mActivityRelaunched;
<----------- windowShouldResize over --------------->
...
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
mForceNextWindowRelayout = false;
...
boolean hwInitialized = false;
boolean contentInsetsChanged = false;
boolean hadSurface = mSurface.isValid();
...
if (mWidth != frame.width() || mHeight != frame.height()) {
// 更新對應(yīng) 寬高
mWidth = frame.width();
mHeight = frame.height();
}
...
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);
// Ask host how big it wants to be
// 第二次測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
// 再次測量 Again
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
} else {
// Not the first pass and no window/insets/visibility change but the window
// may have moved and we need check that and if so to update the left and right
// in the attach info. We translate only the window frame since on window move
// the window manager tells us only for the new frame but the insets are the
// same and we do not want to translate them more than once.
maybeHandleWindowMove(frame);
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
// 開始 layout
performLayout(lp, mWidth, mHeight);
...
}
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
...
final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
if (regainedFocus) {
mLostWindowFocus = false;
} else if (!hasWindowFocus && mHadWindowFocus) {
mLostWindowFocus = true;
}
...
// 更新一些 flag
mFirst = false;
mWillDrawSoon = false;
mNewSurfaceNeeded = false;
mActivityRelaunched = false;
mViewVisibility = viewVisibility;
mHadWindowFocus = hasWindowFocus;
...
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
...
// 開始繪制
performDraw();
} else {
// cancelDraw 之后,如果 View 是可見的 那么會重走 scheduleTraversals() 方法
if (isViewVisible) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
...
}
}
// 執(zhí)行完畢 mIsInTraversal 重置 false
mIsInTraversal = false;
}
performTraversals()
方法太長邏輯太多,這里只保留回調(diào) View measure()
layout()
draw()
方法的核心部分。在該方法中,首先有在 mFirst
中調(diào)用 dispatchAttachedToWindow()
。
這也是 View
或者 Activity
中 onAttachedToWindow()
觸發(fā)的地方。回調(diào) View
的 onAttachedToWindow()
很好理解,但是,怎么又和 Activity
關(guān)聯(lián)起來的呢? 這又要說到 DecorView
。
// DecorView
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
final Window.Callback cb = mWindow.getCallback();
if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
cb.onAttachedToWindow();
}
...
}
在 DecorView
中又會去調(diào)用 Window.Callback
的 onAttachedToWindow()
, 而這個 callback
就在上面 Activity
的 attach()
方法中有設(shè)置,其實就是 Activity
自己。
接著再看看在第一次測量時調(diào)用的 measureHierarchy()
方法。
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;
boolean goodMeasure = false;
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);
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));
// 之前文章中有提到的 MEASURED_STATE_TOO_SMALL 異常狀態(tài)
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
// Didn't fit in that size... try expanding a bit.
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;
}
}
return windowSizeMayChange;
}
在這個方法中,如果傳入的 layoutparameter
是 wrap_content
這種類型。那么就會去計算是否存在 MEASURED_STATE_TOO_SMALL
的異常狀態(tài),如果是的話,那就把對應(yīng) size 再調(diào)大一些,可以看到,這里存在多次調(diào)用 performMeasure()
的情況。之前 Android 源碼分析二 View 測量
講 resolveSizeAndState()
方法時看到的 MEASURED_STATE_TOO_SMALL
狀態(tài)也有相關(guān)使用的地方啦。
第一次測量之后,如果 contentInsetsChanged
或者 updatedConfiguration
為 true ,將再次觸發(fā) performMeasure()
。
接著會根據(jù) layoutRequested
等字段決定是否 調(diào)用 performLayout()
方法。
最后是執(zhí)行調(diào)用 performDraw()
方法相關(guān),如果 mAttachInfo.mTreeObserver.dispatchOnPreDraw()
返回了 true ,那么它將要跳過這次 performDraw()
的執(zhí)行,但是,它居然會重新調(diào)用 scheduleTraversals()
方法,這是我之前不清楚,那么如果我的 viewTreeObserver.addOnPreDrawListener
一直返回 false ,它會不會就死循環(huán)然后掛了呢?當(dāng)然不會,因為 mLayoutRequested
在第一次測量之后就被重置為 false ,此時你再調(diào)用 performTravels()
方法,
layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw)
直接就是 false 。
到這里, ViewRootImpl
創(chuàng)建(在 WindowManagerGlobal
中),管理 View
樹的測量繪制分析完畢。最后還有觸摸事件的分發(fā)。
事件分發(fā)
既然有了 Window
概念,觸摸事件肯定是從物理層面的觸摸屏,最后分發(fā)到每一個抽象的 View
上。一開始毫無思緒,不知從何看起。這時候就想到一招,拋個異常看看咯。
java.lang.RuntimeException: xxxx
at com.lovejjfg.demo.FloatViewHelper$addFloatView$1.onTouch(FloatViewHelper.kt:94)
<---------View --------->
at android.view.View.dispatchTouchEvent(View.java:10719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2865)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2492)
at android.view.View.dispatchPointerEvent(View.java:10952)
<---------ViewRootImpl --------->
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5121)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4973)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4504)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4557)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4523)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4656)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4531)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4713)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4504)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4557)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4523)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4531)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4504)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7011)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6940)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6901)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:7121)
<---------InputEventReceiver --------->
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
<----和遠(yuǎn)程服務(wù)通過 handler 發(fā)的 message ---->
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:323)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:6682)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)
從這個堆棧信息中闊以看到在應(yīng)用層相關(guān)堆棧,但是幕后黑手是誰還是一個謎。 InputEventReceiver
是一個抽象類,ViewRootImpl$WindowInputEventReceiver
是它的一個實現(xiàn)類。
// Called from native code.
@SuppressWarnings("unused")
private void dispatchInputEvent(int seq, InputEvent event, int displayId) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event, displayId);
}
在 ViewRootImpl
類的 setView()
方法中有 WindowInputEventReceiver
的創(chuàng)建。
...
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
...
這里有涉及到 InputChannel
這個類。
/**
* An input channel specifies the file descriptors used to send input events to
* a window in another process. It is Parcelable so that it can be sent
* to the process that is to receive events. Only one thread should be reading
* from an InputChannel at a time.
* @hide
*/
public final class InputChannel implements Parcelable
那結(jié)合起來,屏幕上的觸摸事件,通過 WindowInputEventReceiver
的 dispatchInputEvent()
方法調(diào)用到當(dāng)前進(jìn)程,接著 在 onInputEvent()
方法中開始一次分發(fā)傳遞。
在 ViewRootImpl
中定義了三個 inputStage
,
InputStage mFirstInputStage;
InputStage mFirstPostImeInputStage;
InputStage mSyntheticInputStage;
接著在 setView()
方法末尾:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
}
結(jié)合堆棧信息可以看到,第一個是 EarlyPostImeInputStage
第二個是 NativePostImeInputStage
第三個是 ViewPostImeInputStage
。
// ViewPostImeInputStage
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}
// ViewPostImeInputStage
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false;
mAttachInfo.mHandlingPointerEvent = true;
// 回調(diào) View dispatchPointerEvent
boolean handled = mView.dispatchPointerEvent(event);
maybeUpdatePointerIcon(event);
maybeUpdateTooltip(event);
mAttachInfo.mHandlingPointerEvent = false;
if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
mUnbufferedInputDispatch = true;
if (mConsumeBatchedInputScheduled) {
scheduleConsumeBatchedInputImmediately();
}
}
return handled ? FINISH_HANDLED : FORWARD;
}
// View
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
// DecorView
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
在 DecorView
中有復(fù)寫 dispatchTouchEvent()
方法,上面講過,這個 callback
就是 Activity
,所以說事件分發(fā)首先傳入到 Activity
中。
// Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
在 Activity
中,又會優(yōu)先調(diào)用 Window.superDispatchTouchEvent(ev)
,如果它返回 false ,然后才是自己消費。
// PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
// DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
在 PhoneWindow
中,接著又會調(diào)用 DecorView.superDispatchTouchEvent()
,這個方法最終會走自己的 dispatchTouchEvent()
方法,轉(zhuǎn)了一圈,互相客氣了一下。
removeView
說完 View
的創(chuàng)建和添加到 Window
整個過程,接著再看下一個 View
是如何移除的呢?先看看 ViewGroup
中。
// ViewGroup
private void removeViewInternal(int index, View view) {
if (mTransition != null) {
mTransition.removeChild(this, view);
}
boolean clearChildFocus = false;
if (view == mFocused) {
// 清除焦點
view.unFocus(null);
clearChildFocus = true;
}
if (view == mFocusedInCluster) {
clearFocusedInCluster(view);
}
view.clearAccessibilityFocus();
// 觸摸 target 相關(guān)清除
cancelTouchTarget(view);
cancelHoverTarget(view);
if (view.getAnimation() != null ||
(mTransitioningViews != null && mTransitioningViews.contains(view))) {
// 加入 mDisappearingChildren 集合
addDisappearingView(view);
} else if (view.mAttachInfo != null) {
// 回調(diào) onDetachFromWindow()
view.dispatchDetachedFromWindow();
}
if (view.hasTransientState()) {
childHasTransientStateChanged(view, false);
}
needGlobalAttributesUpdate(false);
// 將 parent 置空 并將自己移除
removeFromArray(index);
if (view == mDefaultFocus) {
clearDefaultFocus(view);
}
if (clearChildFocus) {
clearChildFocus(view);
if (!rootViewRequestFocus()) {
notifyGlobalFocusCleared(this);
}
}
dispatchViewRemoved(view);
if (view.getVisibility() != View.GONE) {
notifySubtreeAccessibilityStateChangedIfNeeded();
}
int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
final int oldIndex = mTransientIndices.get(i);
if (index < oldIndex) {
mTransientIndices.set(i, oldIndex - 1);
}
}
if (mCurrentDragStartEvent != null) {
mChildrenInterestedInDrag.remove(view);
}
}
需要注意的是 addDisappearingView()
,調(diào)用這個方法之后,就回到上篇文章 Android 源碼分析三 View 繪制 分析的 draw()
方法中。它會繼續(xù)執(zhí)行動畫,在動畫結(jié)束后調(diào)用 finishAnimatingView()
, 在這個方法中將其 detachFromWindow 。
那這個 ViewTree 被移除呢?
// WindowManagerGlobal
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
// 調(diào)用 ViewRootImpl 的 die() 方法
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
核心方法就是 ViewRootImpl.die()
,這個方法接受一個參數(shù),是否立即執(zhí)行,但是其實這個也有一個前提條件,就是當(dāng)前必須沒有正在執(zhí)行
performTraversals()
方法。直接執(zhí)行連隊列都不會放,直接干,其他時候是走 Handler
。另外這個方法還有返回值,如果返回 true ,添加到 Handler
隊列沒有馬上移除,在 WindowManagerGlobal
中就會將它放入 mDyingViews
集合暫存。
// ViewRootImpl
boolean die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
die()
方法的核心又在 doDie()
方法中。
// ViewRootImpl
void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
dispatchDetachedFromWindow();
}
if (mAdded && !mFirst) {
destroyHardwareRenderer();
if (mView != null) {
int viewVisibility = mView.getVisibility();
boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
if (mWindowAttributesChanged || viewVisibilityChanged) {
// If layout params have been changed, first give them
// to the window manager to make sure it has the correct
// animation info.
try {
if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
& WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mWindowSession.finishDrawing(mWindow);
}
} catch (RemoteException e) {
}
}
mSurface.release();
}
}
mAdded = false;
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}
在 doDie()
方法中,有幾個關(guān)鍵點,首先是重置 mRemoved
字段,接著,如果已經(jīng) add 過,將會調(diào)用 dispatchDetachedFromWindow()
方法開始分發(fā)。
// ViewRootImpl
void dispatchDetachedFromWindow() {
if (mView != null && mView.mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
mView.dispatchDetachedFromWindow();
}
...
// 釋放 Render
destroyHardwareRenderer();
setAccessibilityFocus(null, null);
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
mSurface.release();
// 觸摸事件處理相關(guān)解除
if (mInputQueueCallback != null && mInputQueue != null) {
mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
mInputQueue.dispose();
mInputQueueCallback = null;
mInputQueue = null;
}
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
try {
// 移除 該 window
mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
// Dispose the input channel after removing the window so the Window Manager
// doesn't interpret the input channel being closed as an abnormal termination.
if (mInputChannel != null) {
mInputChannel.dispose();
mInputChannel = null;
}
mDisplayManager.unregisterDisplayListener(mDisplayListener);
unscheduleTraversals();
}
// ViewRootImpl
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
可以看到,首先調(diào)用 view.dispatchDetachedFromWindow()
,這里的 View
有可能是 DecorView
嘛,我特意看了下,它并沒有復(fù)寫該方法,那直接先看到 ViewGroup
的 dispatchDetachedFromWindow()
方法。
// ViewGroup.dispatchDetachedFromWindow
@Override
void dispatchDetachedFromWindow() {
// If we still have a touch target, we are still in the process of
// dispatching motion events to a child; we need to get rid of that
// child to avoid dispatching events to it after the window is torn
// down. To make sure we keep the child in a consistent state, we
// first send it an ACTION_CANCEL motion event.
// 給之前的 View 分發(fā)一個 cancel 結(jié)束觸摸事件
cancelAndClearTouchTargets(null);
// Similarly, set ACTION_EXIT to all hover targets and clear them.
exitHoverTargets();
exitTooltipHoverTargets();
// In case view is detached while transition is running
mLayoutCalledWhileSuppressed = false;
// Tear down our drag tracking
mChildrenInterestedInDrag = null;
mIsInterestedInDrag = false;
if (mCurrentDragStartEvent != null) {
mCurrentDragStartEvent.recycle();
mCurrentDragStartEvent = null;
}
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
// child 回調(diào) dispatchDetachedFromWindow
children[i].dispatchDetachedFromWindow();
}
// 清楚 Disappearing child
clearDisappearingChildren();
final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchDetachedFromWindow();
}
// 調(diào)用 View.dispatchDetachedFromWindow()
super.dispatchDetachedFromWindow();
}
// View.dispatchDetachedFromWindow
void dispatchDetachedFromWindow() {
...
onDetachedFromWindow();
onDetachedFromWindowInternal();
...
}
最后在 View.dispatchDetachedFromWindow()
方法中,回調(diào) onDetachedFromWindow()
方法,在 DecorView
的該方法中,會調(diào)用到 Activity.onDetachedFromWindow()
,并做其他一些資源釋放。接著看看 onDetachedFromWindowInternal()
方法,看看內(nèi)部一個 View
最后的釋放操作。
protected void onDetachedFromWindowInternal() {
// 清除標(biāo)志位
mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH;
// 移除callback
removeUnsetPressCallback();
removeLongPressCallback();
removePerformClickCallback();
removeSendViewScrolledAccessibilityEventCallback();
stopNestedScroll();
// Anything that started animating right before detach should already
// be in its final state when re-attached.
jumpDrawablesToCurrentState();
// 清除 DrawingCache
destroyDrawingCache();
// 清除 RenderNode
cleanupDraw();
// 講 Animation 重置
mCurrentAnimation = null;
if ((mViewFlags & TOOLTIP) == TOOLTIP) {
hideTooltip();
}
}
到這里, ViewRootImpl
的創(chuàng)建以及銷毀分析完畢,期間將之前分析的一些方法和細(xì)節(jié)串聯(lián)起來了,比如說測量時 resolveSizeAndState()
方法中的 state 的作用。繪制時, mDisappearingChildren
中的 child 什么時候有添加,最后再釋放等等。 ViewRootImpl
+ WindowManagerGlobal
可以理解為最終的 ViewGroup
,這個 ViewGroup
是頂級的,和 Window
直接交互的。