以前文章提到 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
直接交互的。