终于建了一个自己个人小站:https://huangtianyu.gitee.io,以后优先更新小站博客,欢迎进站,O(∩_∩)O~~
先上测试代码:
MyView.java
import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.widget.TextView; public class MyView extends TextView { public MyView(Context context) { super(context); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); Log.e("test","view constructor"); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); Log.e("test", "onAttachedToWindow"); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); Log.e("test", "onDetachedFromWindow"); } }
MainActivity.java
import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e("test", "before setContextView"); setContentView(R.layout.activity_main); Log.e("test", "after setContextView"); } @Override protected void onResume() { super.onResume(); Log.e("test", "onResume"); } @Override protected void onDestroy() { super.onDestroy(); Log.e("test", "onDestroy"); } }运行后输出的Log如下:
点击返回键退出后,输出的Log如下:
根据Log的onAttachedToWindow和onDetachedFromWindow的输出情况一目了然。
下面通过源码分析下,他两的调用时机到底在哪。
首先看下onAttachedToWindow的调用时机,在Android源码中onResume调用前会先调用了ActivityThread中的handleResumeActivity,下面是相应的代码:
ActivityThread.java
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); mSomeActivitiesChanged = true; // TODO Push resumeArgs into the activity for consideration ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { final Activity a = r.activity; if (localLOGV) Slog.v( TAG, "Resume " + r + " started activity: " + a.mStartedActivity + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished); final int forwardBit = isForward ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; // If the window hasn't yet been added to the window manager, // and this guy didn't finish itself or start another activity, // then go ahead and add the window. boolean willBeVisible = !a.mStartedActivity; if (!willBeVisible) { try { willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible( a.getActivityToken()); } catch (RemoteException e) { } } 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) { a.mWindowAdded = true; wm.addView(decor, l);//这里调用了ViewManager中的addView方法。 } // If the window has already been added, but during resume // we started another activity, then don't yet make the // window visible. } else if (!willBeVisible) { if (localLOGV) Slog.v( TAG, "Launch " + r + " mStartedActivity set"); r.hideForNow = true; } // Get rid of anything left hanging around. cleanUpPendingRemoveWindows(r); // The window is now visible if it has been added, we are not // simply finishing, and we are not starting another activity. if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) { if (r.newConfig != null) { r.tmpConfig.setTo(r.newConfig); if (r.overrideConfig != null) { r.tmpConfig.updateFrom(r.overrideConfig); } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig " + r.tmpConfig); performConfigurationChanged(r.activity, r.tmpConfig); freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig)); r.newConfig = null; } if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward); WindowManager.LayoutParams l = r.window.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != forwardBit) { l.softInputMode = (l.softInputMode & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) | forwardBit; if (r.activity.mVisibleFromClient) { ViewManager wm = a.getWindowManager(); View decor = r.window.getDecorView(); wm.updateViewLayout(decor, l); } } r.activity.mVisibleFromServer = true; mNumVisibleActivities++; if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } } .... }看代码中的wm.addView(devor,l);通过该方法将View添加到Window当中(在当前Window也就是Activity,不过Window也可以是Dialog或Toast),而wm是ViewManager类型的,查看对应代码是:
package android.view; /** Interface to let you add and remove child views to an Activity. To get an instance * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. */ public interface ViewManager { /** * Assign the passed LayoutParams to the passed View and add the view to the window. * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming * errors, such as adding a second view to a window without removing the first view. * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a * secondary {@link Display} and the specified display can't be found * (see {@link android.app.Presentation}). * @param view The view to be added to this window. * @param params The LayoutParams to assign to view. */ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); }该类是一个接口,在他下面还有一个WindowManager继承于ViewManager,而真正的实现代码在WindowManagerImpl类中,代码如下:
WindowManagerImpl.java
/* * @see WindowManager * @see WindowManagerGlobal * @hide */ public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); private final Display mDisplay; private final Window mParentWindow; private IBinder mDefaultToken; public WindowManagerImpl(Display display) { this(display, null); } private WindowManagerImpl(Display display, Window parentWindow) { mDisplay = display; mParentWindow = parentWindow; } public WindowManagerImpl createLocalWindowManager(Window parentWindow) { return new WindowManagerImpl(mDisplay, parentWindow); } public WindowManagerImpl createPresentationWindowManager(Display display) { return new WindowManagerImpl(display, mParentWindow); } /** * Sets the window token to assign when none is specified by the client or * available from the parent window. * * @param token The default token to assign. */ public void setDefaultToken(IBinder token) { mDefaultToken = token; } @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); } @Override public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.updateViewLayout(view, params); } private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) { // Only use the default token if we don't have a parent window. if (mDefaultToken != null && mParentWindow == null) { if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } // Only use the default token if we don't already have a token. final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (wparams.token == null) { wparams.token = mDefaultToken; } } } @Override public void removeView(View view) { mGlobal.removeView(view, false); } @Override public void removeViewImmediate(View view) { mGlobal.removeView(view, true); } @Override public Display getDefaultDisplay() { return mDisplay; } }
从中可以看到addView又调用了 WindowManagerGlobal.java类中的addView,下面看看WindowManagerGlobal.java类的源码:
WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
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 {
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.
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);
}
}
}
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 {
root.setView(view, wparams, panelParentView);//这里调用ViewRootImpl类中的setView方法,在该方法中触发了ViewRootImpl.performTraversals()
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
在该方法中的root.setView(view,wparams,panelParentView)方法,调用的是ViewRootImpl类中的setView方法,正是该setView方法触发了ViewRootImpl.performTraversals()方法,也就是View绘制的起点,之后会进行measure,layout,draw三个步骤从而完成一个View的显示工作。
ViewRootImpl.java
/** * We have one child */ 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();//这里开始请求view的绘制 if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } 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(); } } .... } } }在setView的requestLayout方法中开始View的绘制。
ViewRootImpl.java
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } } void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } } final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } }在scheduleTraversals()方法中向mChoreographer中postCallback,而具体的Runable内容在TraversalRunnable类中,该类在run函数中直接执行doTraversal()方法,可以看到在该方法中最终调用了performTraversals()开启View的绘制工作。
查看ViewRootImpl.java中的performTraversals()的源码如下:
ViewRootImpl.java中的performTraversals()方法
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; final int viewVisibility = getHostVisibility(); boolean viewVisibilityChanged = mViewVisibility != viewVisibility || mNewSurfaceNeeded; WindowManager.LayoutParams params = null; if (mWindowAttributesChanged) { mWindowAttributesChanged = false; surfaceChanged = true; params = lp; } CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo(); if (compatibilityInfo.supportsScreen() == mLastInCompatMode) { params = lp; mFullRedrawNeeded = true; mLayoutRequested = true; if (mLastInCompatMode) { params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; mLastInCompatMode = false; } else { params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; mLastInCompatMode = true; } } mWindowAttributesChangesFlag = 0; Rect frame = mWinFrame; if (mFirst) { mFullRedrawNeeded = true; mLayoutRequested = true; if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) { // NOTE -- system code, won't try to do compat mode. Point size = new Point(); mDisplay.getRealSize(size); desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else { DisplayMetrics packageMetrics = mView.getContext().getResources().getDisplayMetrics(); desiredWindowWidth = packageMetrics.widthPixels; desiredWindowHeight = packageMetrics.heightPixels; } // We used to use the following condition to choose 32 bits drawing caches: // PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888 // However, windows are now always 32 bits by default, so choose 32 bits mAttachInfo.mUse32BitDrawingCache = true; mAttachInfo.mHasWindowFocus = false; mAttachInfo.mWindowVisibility = viewVisibility; mAttachInfo.mRecomputeGlobalAttributes = false; viewVisibilityChanged = false; mLastConfiguration.setTo(host.getResources().getConfiguration()); 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(mLastConfiguration.getLayoutDirection()); } host.dispatchAttachedToWindow(mAttachInfo, 0);//这里调用了View的dispatchAttachedToWindow,也就是这里回调了onAttachedToWindow方法。 mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); dispatchApplyInsets(host); //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn); } else { desiredWindowWidth = frame.width(); desiredWindowHeight = frame.height(); if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) { if (DEBUG_ORIENTATION) Log.v(TAG, "View " + host + " resized to: " + frame); mFullRedrawNeeded = true; mLayoutRequested = true; windowSizeMayChange = true; } } ... }
在该方法中调用了host.dispatchAttachedToWindow(mAttachInfo, 0);方法。host是上面传下来的DecodView,该类继承与FrameLayout类,也就是ViewGroup的子类,所以先调用的是ViewGroup中的dispatchAttachedToWindow,其代码如下:
ViewGroup.java
@Override
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
super.dispatchAttachedToWindow(info, visibility);//这里先调用父类,也就是View的dispathcAttachedToWindow。
mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchAttachedToWindow(info,
combineVisibility(visibility, child.getVisibility()));//这里调用子View的dispatchAttachedToWindow
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchAttachedToWindow(info,
combineVisibility(visibility, view.getVisibility()));
}
}
下面查看对应的View类中的dispatchAttacToWindow。代码如下:
View.java
void dispatchAttachedToWindow(AttachInfo info, int visibility) { //System.out.println("Attached! " + this); mAttachInfo = info; if (mOverlay != null) { mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility); } mWindowAttachCount++; // We will need to evaluate the drawable state at least once. mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY; if (mFloatingTreeObserver != null) { info.mTreeObserver.merge(mFloatingTreeObserver); mFloatingTreeObserver = null; } if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) { mAttachInfo.mScrollContainers.add(this); mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED; } performCollectViewAttributes(mAttachInfo, visibility); onAttachedToWindow();//快看,快看,在这里!终于找到这个方法调用的位置了 ListenerInfo li = mListenerInfo; final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners = li != null ? li.mOnAttachStateChangeListeners : null; if (listeners != null && listeners.size() > 0) { // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. for (OnAttachStateChangeListener listener : listeners) { listener.onViewAttachedToWindow(this);// } } int vis = info.mWindowVisibility; if (vis != GONE) { onWindowVisibilityChanged(vis); } // Send onVisibilityChanged directly instead of dispatchVisibilityChanged. // As all views in the subtree will already receive dispatchAttachedToWindow // traversing the subtree again here is not desired. onVisibilityChanged(this, visibility); if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) { // If nobody has evaluated the drawable state yet, then do it now. refreshDrawableState(); } needGlobalAttributesUpdate(false); }从上面代码可以看出一个布局的onAttachedToWindow会先调用自己的,然后再调用自己孩子的。而且从View.java的代码中也可以看出onAttachedToWindow和View自身的visibility无关,即使visibility==GONE,该方法也会调用。
好,下面来分析下onDetachedFromWindow方法的调用时机。在onDestory调用前会调用ActivityThread.java中的handleDestroyActivity方法,贴出代码:
ActivityThread.java
private void handleDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance) { ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance); if (r != null) { cleanUpPendingRemoveWindows(r); WindowManager wm = r.activity.getWindowManager(); View v = r.activity.mDecor; if (v != null) { if (r.activity.mVisibleFromServer) { mNumVisibleActivities--; } IBinder wtoken = v.getWindowToken(); if (r.activity.mWindowAdded) { if (r.onlyLocalRequest) { // Hold off on removing this until the new activity's // window is being added. r.mPendingRemoveWindow = v; r.mPendingRemoveWindowManager = wm; } else { wm.removeViewImmediate(v);//看这里,看这里 } } if (wtoken != null && r.mPendingRemoveWindow == null) { WindowManagerGlobal.getInstance().closeAll(wtoken, r.activity.getClass().getName(), "Activity"); } r.activity.mDecor = null; } if (r.mPendingRemoveWindow == null) { // If we are delaying the removal of the activity window, then // we can't clean up all windows here. Note that we can't do // so later either, which means any windows that aren't closed // by the app will leak. Well we try to warning them a lot // about leaking windows, because that is a bug, so if they are // using this recreate facility then they get to live with leaks. WindowManagerGlobal.getInstance().closeAll(token, r.activity.getClass().getName(), "Activity"); } // Mocked out contexts won't be participating in the normal // process lifecycle, but if we're running with a proper // ApplicationContext we need to have it tear down things // cleanly. Context c = r.activity.getBaseContext(); if (c instanceof ContextImpl) { ((ContextImpl) c).scheduleFinalCleanup( r.activity.getClass().getName(), "Activity"); } } if (finishing) { try { ActivityManagerNative.getDefault().activityDestroyed(token); } catch (RemoteException ex) { // If the system process has died, it's game over for everyone. } } mSomeActivitiesChanged = true; }看代码中的wm.removeViewImmediate方法,还是走到WindowManagerImpl类中的removeViewImmediate,代码如下:
WindowManagerImpl.java
@Override public void removeViewImmediate(View view) { mGlobal.removeView(view, true); }好熟悉啊,还是走到了WindowManagerGlobal类中的removeView,代码如下:
WindowManagerGlobal.java
public void removeView(View view, boolean immediate) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } synchronized (mLock) { int index = findViewLocked(view, true); View curView = mRoots.get(index).getView(); removeViewLocked(index, immediate); if (curView == view) { return; } throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to " + curView); } } 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()); } } boolean deferred = root.die(immediate); if (view != null) { view.assignParent(null); if (deferred) { mDyingViews.add(view); } } }跟着代码继续走,到了ViewRootImpl类中的die,代码如下:
ViewRootImpl.java
/** * @param immediate True, do now if not in traversal. False, put on queue and do later. * @return True, request has been queued. False, request has been completed. */ 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(TAG, "Attempting to destroy the window while drawing!\n" + " window=" + this + ", title=" + mWindowAttributes.getTitle()); } mHandler.sendEmptyMessage(MSG_DIE); return true; } void doDie() { checkThread(); if (LOCAL_LOGV) Log.v(TAG, "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里面调用了dispatchDetachedFromWindow()方法,代码如下:
ViewRootImpl.java
void dispatchDetachedFromWindow() { if (mView != null && mView.mAttachInfo != null) { mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false); mView.dispatchDetachedFromWindow();//看这里,看这里 } mAccessibilityInteractionConnectionManager.ensureNoConnection(); mAccessibilityManager.removeAccessibilityStateChangeListener( mAccessibilityInteractionConnectionManager); mAccessibilityManager.removeHighTextContrastStateChangeListener( mHighContrastTextManager); removeSendWindowContentChangedCallback(); 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 { 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(); }还记着在WindowManagerGlobal里面的root.setView(view, wparams, panelParentView);调用吧,这里的mView.dispatchDetachedFromWindow();这个mView也即是上面传过来的view。也就是先看DecorView即ViewGroup里面的dispatchDetachedFromWindow,代码如下:
ViewGroup.java
@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. cancelAndClearTouchTargets(null); // Similarly, set ACTION_EXIT to all hover targets and clear them. exitHoverTargets(); // In case view is detached while transition is running mLayoutCalledWhileSuppressed = false; // Tear down our drag tracking mDragNotifiedChildren = null; if (mCurrentDrag != null) { mCurrentDrag.recycle(); mCurrentDrag = null; } final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { children[i].dispatchDetachedFromWindow();//这里会先调子类的dispatchDetachedFromWindow } clearDisappearingChildren(); final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size(); for (int i = 0; i < transientCount; ++i) { View view = mTransientViews.get(i); view.dispatchDetachedFromWindow(); } super.dispatchDetachedFromWindow();//然后这里才调用自己的。 }这之后又到View的dispatchDetachedFromWindow了,代码如下:
View.java
void dispatchDetachedFromWindow() { AttachInfo info = mAttachInfo; if (info != null) { int vis = info.mWindowVisibility; if (vis != GONE) { onWindowVisibilityChanged(GONE); } } onDetachedFromWindow();//绕了一大圈,还是找到你了。快看快看,揪出来了。 onDetachedFromWindowInternal(); InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { imm.onViewDetachedFromWindow(this); } ListenerInfo li = mListenerInfo; final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners = li != null ? li.mOnAttachStateChangeListeners : null; if (listeners != null && listeners.size() > 0) { // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. for (OnAttachStateChangeListener listener : listeners) { listener.onViewDetachedFromWindow(this); } } if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) { mAttachInfo.mScrollContainers.remove(this); mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED; } mAttachInfo = null; if (mOverlay != null) { mOverlay.getOverlayView().dispatchDetachedFromWindow(); } }看代码终于找到了onDetachedFromWindow的调用地方了。
这里总结下:
1.onAttachedToWindow调用顺序:ActivityThread.handleResumeActivity->WindowManagerImpl.addView->WindowManagerGlobal.addView->ViewRootImpl.performTraversals->ViewGroup.dispatchAttachedToWindow->View.dispatchAttachedToWindow->onAttachedToWindow
2.onDetachedFromWindow调用顺序:ActivityThread.handleDestroyActivity->WindowManagerImpl.removeViewImmediate->WindowManagerGlobal.removeView->ViewRootImpl.die->ViewRootImpl.doDie->ViewRootImpl.dispatchDetachedFromWindow->ViewGroup.dispatchDetachedFromWindow->View.dispatchDetachedFromWindow->onDetachedToWindow
3.onAttachedToWindow和onDetachedFromWindow的调用与visibility无关。
4.onAttachedToWindow是先调用自己,然后调用儿子View的。onDetachedFromWindow是先调用儿子View的,然后再调用自己的。