之前文章提到 View
的根是 ViewRootImpl
这个类。那么他们又是由谁关联起来的呢?
要说这些关系之前,先了解一些接口:
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
,都实现了这两个接口。
接下来提出4 个问题:
-
ViewRootImpl
被谁创建和管理 -
ViewRootImpl
和Window
对应关系 -
View
什么时候可以拿到具体宽高 -
View
的事件分发源头在哪儿
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
树的顶层。强调它有实现 View
和 WindowManager
之间必要的协议,是WindowManagerGlobal
内部实现中重要的组成部分。其实信息点挺多,直接就涉及到开头要说的那个问题,View
和 Window
之间的关系。另外还提到它和 WindowManagerGlobal
的关系,看这架势,它的很多方法可能都是被 WindowManagerGlobal
调用。
在看接口实现和调用,它实现了 ViewParent
接口,但是,它并没有实现 ViewManager
接口。对比 ViewGroup
,它少了 ViewManager
对 View
的增删改能力。
接着看看它的构造方法:
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
要说 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()
方法。
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;
// 第一次 执行
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()
方法,转了一圈,互相客气了一下。
removeView
说完 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
直接交互的。
网友评论