以前文章提到 View
的根是 ViewRootImpl
這個類。那麼他們又是由誰關聯起來的呢?java
要說這些關係以前,先了解一些接口:android
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
複製代碼
實現 ViewManager
這個接口以後,你具備對View的基本操做(增刪改),另外還有以前經常使用到的 ViewParent
接口,每個 ViewGroup
,都實現了這兩個接口。windows
接下來提出4 個問題:markdown
ViewRootImpl
被誰建立和管理ViewRootImpl
和 Window
對應關係View
何時能夠拿到具體寬高View
的事件分發源頭在哪兒先看看 ViewRootImpl
的定義:app
/**
* 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
樹的頂層。強調它有實現 View
和 WindowManager
之間必要的協議,是WindowManagerGlobal
內部實現中重要的組成部分。其實信息點挺多,直接就涉及到開頭要說的那個問題,View
和 Window
之間的關係。另外還提到它和 WindowManagerGlobal
的關係,看這架勢,它的不少方法可能都是被 WindowManagerGlobal
調用。less
在看接口實現和調用,它實現了 ViewParent
接口,可是,它並無實現 ViewManager
接口。對比 ViewGroup
,它少了 ViewManager
對 View
的增刪改能力。ide
接着看看它的構造方法:oop
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
建立,初始化準備了不少東西,着重強調 AttachInfo
建立,這個類很重要,以前說的 軟解時 Canvas
的保存和複用,還有 View.post()
方法執行等等。源碼分析
以前文章中,已經或多或少提到 ViewRootImpl
的職責。好比說測量繪製最後都是由它發起。佈局
在 ViewRootImpl
中,TraversalRunnable
是一個重要的角色。
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
複製代碼
它內部調用 doTraversal()
方法,最終觸發 performTraversals()
,在這個方法中,就開始了對整個 View
樹的測量繪製等等一系列操做。再細分就是根據狀況調用 performMeasure()
performLayout()
performDraw()
方法,最後就回調到具體 View
的 onMeasure()
onLayout()
和 onDraw()
方法,這個具體流程,在前面文章中有相關分析。
ViewRootImpl
根據註釋,是 WindowManagerGlobal
的重要組成部分,那就先瞅瞅 WindowManagerGlobal
是個啥呢?
要說 WindowManagerGlobal
,那就要先看 WindowManager
接口啦,這個接口實現了 ViewManager,
說明它擁有對 View
的控制能力。根據註釋,這個接口就是咱們用來和遠程服務 WindowManager
service 溝通的。它的實現類就是 WindowManagerImpl
。WindowManagerImpl
中,有一個 mGlobal
字段:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
複製代碼
到這裏,看到 WindowManagerGlobal
的一點蹤跡了。接着深刻其內部一探究竟。
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
內部是管理着 ViewRootImpl
和 其對應的 RootView
還有對應的 LayoutParams
等等。
上面講過,ViewRootImpl
只實現了 ViewParent
接口,並無實現 ViewManager
接口,喪失了部分 parent 的能力。其實這部分能力就交由 WindowManager
,對於 ViewRootImpl
來講,再向上的 View
增傷改功能是和 Window
交互,須要和系統服務打交道。
在 WindowManagerImpl
中,咱們看看 ViewManager
接口相關方法具體實現:
//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
作具體實現。 WindowManagerImpl
代理了遠程系統服務, WindowManagerGlobal
代理了 WindowManagerImpl
的具體實現。
//WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//設置Window一些基本參數
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();
}
}
}
};
//監聽系統屬性設置變化:邊界佈局 硬件加速支持等設置變化
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
// 判斷有沒有已經添加過
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 已經有 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);
}
}
}
// 建立 ViewRootImpl
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 {
// 調用 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()
方法中,最後會建立出一個新的 ViewRootImpl
,並調用 ViewRootImpl
的 setView()
方法。
前面提到的前兩個問題已經有了答案, ViewRootImpl
被 WindowManagerGlobal
建立, ViewRootImpl
和 Window
的對應關係是多對一,一個 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.
// 調用這個方法以後會直接出發 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();
}
// 輸入事件相關
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()
方法裏面邏輯挺多的,首先會直接調用一次 requestLayout()
,而後會處理 addView()
的異常狀況,相似於 Badtoken 這些異常。最後還有添加 Window
事件相關的監聽。
在調用 requestLayout()
以後, View
會進行相關測量繪製。在這以後,確定能拿到 View
對應寬高。那麼第三個問題, View
何時能拿到對應寬高,彷佛說是在 ViewRootImpl
調用 setView()
方法以後也沒什麼毛病。
那麼對於 Activity
而言,何時會調用到 WindowManager.addView()
呢?一般咱們會在 onCreate()
回調方法中調用 setContentView()
添加對應佈局。那麼這個方法的 View
是何時被真正添加到 Window
上的呢,或者說,這個 View
到底被添加到哪裏了呢 ?
Activity
的啓動是在 ActivityThread
類中進行的。在 ActivityThread
的 performLaunchActivity()
中,會完成 Activity
的建立(反射),而且會調用 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*/);
// 建立出 PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// 設置相關 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
建立了對應的 PhoneWindow
, 有了 PhoneWindow
纔能有後文。對於 Activity
,咱們設置的 ContentView
並非頂層 View
,最頂層應該是 DecorView
, DecorView
是定義在 PhoneWindow
中:
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
複製代碼
因此,如今的問題就是 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() 中會回調 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;
// 調用 windowManager.addView()
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
...
}
複製代碼
代碼只保留 addView()
相關部分,在調用這個以前,會先調用 performResumeActivity()
,在這個方法中,就會回調 Activity
的 onResume()
方法。也就是說,在 Activity
中,一個 View
的寬高,是在 Activity
第一次回調 onResume()
方法以後,第一次的 onResume()
方法時並不能拿到寬高。 在這以後, DecorView
才添加到 PhoneWindow
中,接着觸發 windowManagerGlobal.addView()
方法,接着調用 ViewRootImlp.setView()
方法,而後開始 requestLayout()
,最後觸發 performTraversals()
方法,在這個方法中將會調用 View
的 measure()
layout()
和 draw()
方法。
// 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;
// 第一次 執行
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
...
// 設置一些基本參數
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 時調用 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 的標誌 第一次爲 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 內部會調用 performMeasure()
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
if (collectViewAttributes()) {
params = lp;
}
if (mAttachInfo.mForceReportNewAttributes) {
mAttachInfo.mForceReportNewAttributes = false;
params = lp;
}
...
if (layoutRequested) {
// 這裏已經重置 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()) {
// 更新對應 寬高
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) {
...
}
}
// 執行完畢 mIsInTraversal 重置 false
mIsInTraversal = false;
}
複製代碼
performTraversals()
方法太長邏輯太多,這裏只保留回調 View measure()
layout()
draw()
方法的核心部分。在該方法中,首先有在 mFirst
中調用 dispatchAttachedToWindow()
。
這也是 View
或者 Activity
中 onAttachedToWindow()
觸發的地方。回調 View
的 onAttachedToWindow()
很好理解,可是,怎麼又和 Activity
關聯起來的呢? 這又要說到 DecorView
。
// DecorView
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
final Window.Callback cb = mWindow.getCallback();
if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
cb.onAttachedToWindow();
}
...
}
複製代碼
在 DecorView
中又會去調用 Window.Callback
的 onAttachedToWindow()
, 而這個 callback
就在上面 Activity
的 attach()
方法中有設置,其實就是 Activity
本身。
接着再看看在第一次測量時調用的 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 異常狀態
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
的異常狀態,若是是的話,那就把對應 size 再調大一些,能夠看到,這裏存在屢次調用 performMeasure()
的狀況。以前 Android 源碼分析二 View 測量 講 resolveSizeAndState()
方法時看到的 MEASURED_STATE_TOO_SMALL
狀態也有相關使用的地方啦。
第一次測量以後,若是 contentInsetsChanged
或者 updatedConfiguration
爲 true ,將再次觸發 performMeasure()
。
接着會根據 layoutRequested
等字段決定是否 調用 performLayout()
方法。
最後是執行調用 performDraw()
方法相關,若是 mAttachInfo.mTreeObserver.dispatchOnPreDraw()
返回了 true ,那麼它將要跳過此次 performDraw()
的執行,可是,它竟然會從新調用 scheduleTraversals()
方法,這是我以前不清楚,那麼若是個人 viewTreeObserver.addOnPreDrawListener
一直返回 false ,它會不會就死循環而後掛了呢?固然不會,由於 mLayoutRequested
在第一次測量以後就被重置爲 false ,此時你再調用 performTravels()
方法, layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw)
直接就是 false 。
到這裏, ViewRootImpl
建立(在 WindowManagerGlobal
中),管理 View
樹的測量繪製分析完畢。最後還有觸摸事件的分發。
既然有了 Window
概念,觸摸事件確定是從物理層面的觸摸屏,最後分發到每個抽象的 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)
<----和遠程服務經過 handler 發的 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)
複製代碼
從這個堆棧信息中闊以看到在應用層相關堆棧,可是幕後黑手是誰仍是一個謎。 InputEventReceiver
是一個抽象類,ViewRootImpl$WindowInputEventReceiver
是它的一個實現類。
// 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
的建立。
...
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
複製代碼
那結合起來,屏幕上的觸摸事件,經過 WindowInputEventReceiver
的 dispatchInputEvent()
方法調用到當前進程,接着 在 onInputEvent()
方法中開始一次分發傳遞。
在 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;
}
複製代碼
結合堆棧信息能夠看到,第一個是 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;
// 回調 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
中有複寫 dispatchTouchEvent()
方法,上面講過,這個 callback
就是 Activity
,因此說事件分發首先傳入到 Activity
中。
// Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
複製代碼
在 Activity
中,又會優先調用 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
中,接着又會調用 DecorView.superDispatchTouchEvent()
,這個方法最終會走本身的 dispatchTouchEvent()
方法,轉了一圈,互相客氣了一下。
說完 View
的建立和添加到 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 相關清除
cancelTouchTarget(view);
cancelHoverTarget(view);
if (view.getAnimation() != null ||
(mTransitioningViews != null && mTransitioningViews.contains(view))) {
// 加入 mDisappearingChildren 集合
addDisappearingView(view);
} else if (view.mAttachInfo != null) {
// 回調 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()
,調用這個方法以後,就回到上篇文章 Android 源碼分析三 View 繪製 分析的 draw()
方法中。它會繼續執行動畫,在動畫結束後調用 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());
}
}
// 調用 ViewRootImpl 的 die() 方法
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
複製代碼
核心方法就是 ViewRootImpl.die()
,這個方法接受一個參數,是否當即執行,可是其實這個也有一個前提條件,就是當前必須沒有正在執行 performTraversals()
方法。直接執行連隊列都不會放,直接幹,其餘時候是走 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()
方法中,有幾個關鍵點,首先是重置 mRemoved
字段,接着,若是已經 add 過,將會調用 dispatchDetachedFromWindow()
方法開始分發。
// 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();
// 觸摸事件處理相關解除
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);
}
}
複製代碼
能夠看到,首先調用 view.dispatchDetachedFromWindow()
,這裏的 View
有多是 DecorView
嘛,我特地看了下,它並無複寫該方法,那直接先看到 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 分發一個 cancel 結束觸摸事件
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 回調 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();
}
// 調用 View.dispatchDetachedFromWindow()
super.dispatchDetachedFromWindow();
}
// View.dispatchDetachedFromWindow
void dispatchDetachedFromWindow() {
...
onDetachedFromWindow();
onDetachedFromWindowInternal();
...
}
複製代碼
最後在 View.dispatchDetachedFromWindow()
方法中,回調 onDetachedFromWindow()
方法,在 DecorView
的該方法中,會調用到 Activity.onDetachedFromWindow()
,並作其餘一些資源釋放。接着看看 onDetachedFromWindowInternal()
方法,看看內部一個 View
最後的釋放操做。
protected void onDetachedFromWindowInternal() {
// 清除標誌位
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
的建立以及銷燬分析完畢,期間將以前分析的一些方法和細節串聯起來了,好比說測量時 resolveSizeAndState()
方法中的 state 的做用。繪製時, mDisappearingChildren
中的 child 何時有添加,最後再釋放等等。 ViewRootImpl
+ WindowManagerGlobal
能夠理解爲最終的 ViewGroup
,這個 ViewGroup
是頂級的,和 Window
直接交互的。