Window,字面意义即窗口,在Android中,所有的视图都是通过Window来呈现的,Window是View的直接管理者。

Window是一个抽象类,具体实现是PhoneWindow。对Wndow的管理工作操作都是通过它的Manager-->WindowManager来做的,然而WindowManager也只是走个形式,真正的执行者是WindowManagerService,WindowManager通过提交工单的形式(IPC)交个WindowManagerService执行。

WindowManager管理Window的方式也很简单,常用的只有三种方法-->增删改(crud工程师),即addView(),updateViewLayout(),removeView()。这三个方法定义在ViewManager中,WIndowManager正是继承了ViewManager。

/**
 * path: /frameworks/base/core/java/android/view/ViewManager.java
 */
public interface ViewManager{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

接下来看看WindowManager是如何将安排Window安排的明明白白的
本文分析的Android源码版本:android-10.0.0_r40

WindowManager的管理手段

每个Window对应一个View和ViewRootImpl,Window和View通过ViewRootImpl建立联系,所以Window是以View的形式存在的

管理Window,需要借助WindowManager提供的方法,WindowManager是一个接口,真正的实现类是WindowManagerImpl,WindowManagerImpl中对这三个操作的实现如下:

/**
 *path: /frameworks/base/core/java/android/view/WindowManagerImpl.java
 */
public final class WindowManagerImpl implements WindowManager {

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

    @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);
    }
}

从上述的源码中可以看到WindowManagerImpl并没有亲自处理这些操作,而是全部交给了它的秘书(WindowManagerGlobal)来做,WindowManagerImpl这种工作方式被称为桥接模式,WindowManagerGlobal通过单例模式向外提供实例,

/**
 * path: /frameworks/base/core/java/android/view/WindowManagerGlobal.java
 */
public final class WindowManagerGlobal {
    @UnsupportedAppUsage
    private static WindowManagerGlobal sDefaultWindowManager;

    private WindowManagerGlobal() {
    }
    //单例模式,对外返回唯一实例
    @UnsupportedAppUsage
    public static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
    }
}

增加Window的手段(addView())

WindowManagerGlobal 的addView() 方法源码如下:

/**
 * path: /frameworks/base/core/java/android/view/WindowManagerGlobal.java
 */
    //存储所有Window对应的View
    @UnsupportedAppUsage
    private final ArrayList<View> mViews = new ArrayList<View>();

    //存储所有Window对应的ViewRootImpl
    @UnsupportedAppUsage
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();

    //存储所有Window所对应的布局参数
    @UnsupportedAppUsage
    private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();

    //存储正在被删除的View对象(调用removeView(),但删除操作还未完成的Window)
    private final ArraySet<View> mDyingViews = new ArraySet<View>();

    public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow, int userId) {
        /* 1.检查参数的合法性,若是子Window还需调整布局参数 */
        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;
            }
        }
        /* 第一步end */
        /********************************************************/

        /*2.创建ViewRootImpl,并将View加入到列表中*/
        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);
            /*第二步end*/
            /********************************************************/

            /*3.通过ViewRootImpl来更新界面并完成对Window的添加*/
            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
            /*第三步end*/
            /********************************************************/
        }
    }

WindowManagerGlobal 的addView() 方法主要做了这几件事:

  • 检查参数的合法性,若是子Window还需调整布局参数

  • 创建ViewRootImpl,并将View加入到列表中(列表的类型和作用已标注在源码中)

  • 通过ViewRootImpl来更新界面并完成对Window的添加
    这一步是通过ViewRootImpl的setView()来实现的,setView()内部通过requestLayout()来完成异步刷新请求(在requestLayout()方法中,scheduleTraversals()是View绘制的入口),之后借助WindowSession的addToDisplayAsUser()方法来完成Window的添加,源码如下:

    /**
    * path: /frameworks/base/core/java/android/view/ViewRootImpl.java
    */
      /**
       * We have one child
       */
      public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
          setView(view, attrs, panelParentView, UserHandle.myUserId());
      }
    
      /**
       * We have one child
       */
      public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
          synchronized (this) {
              if (mView == null) {
                  mView = view;
    
                  mAttachInfo.mDisplayState = mDisplay.getState();
                  mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
    
                  mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
                  mFallbackEventHandler.setView(view);
                  mWindowAttributes.copyFrom(attrs);
                  if (mWindowAttributes.packageName == null) {
                      mWindowAttributes.packageName = mBasePackageName;
                  }
                  mWindowAttributes.privateFlags |=
                          WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
    
                  attrs = mWindowAttributes;
                  setTag();
    
                  if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
                          & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
                          && (attrs.flags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0) {
                      Slog.d(mTag, "setView: FLAG_KEEP_SCREEN_ON changed from true to false!");
                  }
                  // Keep track of the actual window flags supplied by the client.
                  mClientWindowLayoutFlags = attrs.flags;
    
                  setAccessibilityFocus(null, null);
    
                  if (view instanceof RootViewSurfaceTaker) {
                      mSurfaceHolderCallback =
                              ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
                      if (mSurfaceHolderCallback != null) {
                          mSurfaceHolder = new TakenSurfaceHolder();
                          mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
                          mSurfaceHolder.addCallback(mSurfaceHolderCallback);
                      }
                  }
    
                  // Compute surface insets required to draw at specified Z value.
                  // TODO: Use real shadow insets for a constant max Z.
                  if (!attrs.hasManualSurfaceInsets) {
                      attrs.setSurfaceInsets(view, false /*manual*/, true /*preservePrevious*/);
                  }
    
                  CompatibilityInfo compatibilityInfo = mDisplay.getDisplayAdjustments().getCompatibilityInfo();
                  mTranslator = compatibilityInfo.getTranslator();
    
                  // If the application owns the surface, don't enable hardware acceleration
                  if (mSurfaceHolder == null) {
                      // While this is supposed to enable only, it can effectively disable
                      // the acceleration too.
                      enableHardwareAcceleration(attrs);
                      final boolean useMTRenderer = MT_RENDERER_AVAILABLE
                              && mAttachInfo.mThreadedRenderer != null;
                      if (mUseMTRenderer != useMTRenderer) {
                          // Shouldn't be resizing, as it's done only in window setup,
                          // but end just in case.
                          endDragResizing();
                          mUseMTRenderer = useMTRenderer;
                      }
                  }
    
                  boolean restore = false;
                  if (mTranslator != null) {
                      mSurface.setCompatibilityTranslator(mTranslator);
                      restore = true;
                      attrs.backup();
                      mTranslator.translateWindowLayout(attrs);
                  }
                  if (DEBUG_LAYOUT) Log.d(mTag, "WindowLayout in setView:" + attrs);
    
                  if (!compatibilityInfo.supportsScreen()) {
                      attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
                      mLastInCompatMode = true;
                  }
    
                  mSoftInputMode = attrs.softInputMode;
                  mWindowAttributesChanged = true;
                  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();
                  /********************************************************/
    
                  InputChannel inputChannel = null;
                  if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                      inputChannel = new InputChannel();
                  }
                  mForceDecorViewVisibility = (mWindowAttributes.privateFlags & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
    
                  /*通过WindowSession来添加Window*/
                  /********************************************************/     
                  try {
                      mOrigWindowType = mWindowAttributes.type;
                      mAttachInfo.mRecomputeGlobalAttributes = true;
                      collectViewAttributes();
                      adjustLayoutParamsForCompatibility(mWindowAttributes);
                      res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                              getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                              mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                              mAttachInfo.mDisplayCutout, inputChannel,
                              mTempInsets, mTempControls);
                      setFrame(mTmpFrame);
                  } catch (RemoteException e) {
                      mAdded = false;
                      mView = null;
                      mAttachInfo.mRootView = null;
                      inputChannel = null;
                      mFallbackEventHandler.setView(null);
                      unscheduleTraversals();
                      setAccessibilityFocus(null, null);
                      throw new RuntimeException("Adding window failed", e);
                  } finally {
                      if (restore) {
                          attrs.restore();
                      }
                  }
                  /********************************************************/
    
                  if (mTranslator != null) {
                      mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
                  }
                  mPendingDisplayCutout.set(mAttachInfo.mDisplayCutout);
                  mAttachInfo.mAlwaysConsumeSystemBars =
                          (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0;
                  mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars;
                  mInsetsController.onStateChanged(mTempInsets);
                  mInsetsController.onControlsChanged(mTempControls);
                  if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
                  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?");
                          case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
                              throw new WindowManager.BadTokenException(
                                      "Unable to add window -- token " + attrs.token
                                      + " is not for an application");
                          case WindowManagerGlobal.ADD_APP_EXITING:
                              throw new WindowManager.BadTokenException(
                                      "Unable to add window -- app for token " + attrs.token
                                      + " is exiting");
                          case WindowManagerGlobal.ADD_DUPLICATE_ADD:
                              throw new WindowManager.BadTokenException(
                                      "Unable to add window -- window " + mWindow
                                      + " has already been added");
                          case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
                              // Silently ignore -- we would have just removed it
                              // right away, anyway.
                              return;
                          case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
                              throw new WindowManager.BadTokenException("Unable to add window "
                                      + mWindow + " -- another window of type "
                                      + mWindowAttributes.type + " already exists");
                          case WindowManagerGlobal.ADD_PERMISSION_DENIED:
                              throw new WindowManager.BadTokenException("Unable to add window "
                                      + mWindow + " -- permission denied for window type "
                                      + mWindowAttributes.type);
                          case WindowManagerGlobal.ADD_INVALID_DISPLAY:
                              throw new WindowManager.InvalidDisplayException("Unable to add window "
                                      + mWindow + " -- the specified display can not be found");
                          case WindowManagerGlobal.ADD_INVALID_TYPE:
                              throw new WindowManager.InvalidDisplayException("Unable to add window "
                                      + mWindow + " -- the specified window type "
                                      + mWindowAttributes.type + " is not valid");
                          case WindowManagerGlobal.ADD_INVALID_USER:
                              throw new WindowManager.BadTokenException("Unable to add Window "
                                      + mWindow + " -- requested userId is not valid");
                      }
                      throw new RuntimeException(
                              "Unable to add window -- unknown error code " + res);
                  }
    
                  if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) {
                      mUseBLASTAdapter = true;
                  }
                  if ((res & WindowManagerGlobal.ADD_FLAG_USE_TRIPLE_BUFFERING) != 0) {
                      mEnableTripleBuffering = true;
                  }
    
                  if (view instanceof RootViewSurfaceTaker) {
                      mInputQueueCallback =
                          ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
                  }
                  if (inputChannel != null) {
                      if (mInputQueueCallback != null) {
                          mInputQueue = new InputQueue();
                          mInputQueueCallback.onInputQueueCreated(mInputQueue);
                      }
                      mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                              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;
    
                  if (mView instanceof RootViewSurfaceTaker) {
                      PendingInsetsController pendingInsetsController =
                              ((RootViewSurfaceTaker) mView).providePendingInsetsController();
                      if (pendingInsetsController != null) {
                          pendingInsetsController.replayAndAttach(mInsetsController);
                      }
                  }
              }
          }
      } 
    
      @Override
      public void requestLayout() {
          if (!mHandlingLayoutInLayoutRequest) {
              checkThread();
              mLayoutRequested = true;
              /*View绘制的入口*/
              /********************************************************/
              scheduleTraversals();
              /********************************************************/
          }
      }
    
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
      void scheduleTraversals() {
          if (!mTraversalScheduled) {
              mTraversalScheduled = true;
              mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
              mChoreographer.postCallback(
                      Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
              notifyRendererOfFramePending();
              pokeDrawLockIfNeeded();
          }
      }

    mWindowSession的类型是IWindowSession,这是一个Binder对象,真正的实现是Session(添加Window过程是一次IPC调用),相关源码如下:

    /**
    * path: /frameworks/base/services/core/java/com/android/server/wm/Session.java
    */
    class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
    
      @Override
      public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
              int viewVisibility, int displayId, int userId, Rect outFrame,
              Rect outContentInsets, Rect outStableInsets,
              DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
              InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
          return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                  outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
                  outInsetsState, outActiveControls, userId);
      }
    

}

Session内部通过WindowManagerService(WMS),来实现Window的添加,在WindowManagerService内部,会为每个应用保留一个单独的Session,具体是如何添加的,可以到WindowManagerService源码看看,这里就不明说了(源码路径: /frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)
## 干掉Window的手段(removeView())
Window的删除过程与添加过程相同,都是通过WimdowManagerImpl后,再利用WindowManagerGlobal来实现,WindowManagerGlobal的removeView()方法源码如下:
```java
/**
 * path: /frameworks/base/core/java/android/view/WindowManagerGlobal.java
 */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            //查找待删除View的索引
            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 int findViewLocked(View view, boolean required) {
        final int index = mViews.indexOf(view);
        if (required && index < 0) {
            throw new IllegalArgumentException("View=" + view + " not attached to window manager");
        }
        return index;
    }

    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (root != null) {
            root.getImeFocusController().onWindowDismissed();
        }
        //具体的删除操作
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

removeView()先通过findViewLocked()找到待删除的View的索引,再调用removeViewLocked()执行删除操作;在removeViewLocked()中通过ViewRootImpl(和添加的过程一样)提供的 die() 来完成删除操作,源码如下:

/**
 * path: /frameworks/base/core/java/android/view/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) {

        //判断是否是异步删除,若为同步删除,直接执行doDie()方法
        // 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;
        }

        //若为异步删除则发送MSG_DIE的消息
        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;
    }

    void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                //真正删除View的逻辑
                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, null /* postDrawTransaction */);
                            }
                        } catch (RemoteException e) {
                        }
                    }

                    destroySurface();
                }
            }

            mAdded = false;
        }
        //刷新WindowManagerGlobal中的数据,将关联的三类对象从列表中去除
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

    void dispatchDetachedFromWindow() {
        //垃圾回收相关工作,调用Session的remove()删除Window
        // Make sure we free-up insets resources if view never received onWindowFocusLost()
        // because of a die-signal
        mInsetsController.onWindowFocusLost();
        mFirstInputStage.onDetachedFromWindow();
        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);

        mInsetsController.cancelExistingAnimations();

        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;

        destroySurface();

        if (mInputQueueCallback != null && mInputQueue != null) {
            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
            mInputQueue.dispose();
            mInputQueueCallback = null;
            mInputQueue = null;
        }
        try {
            //调用Session的remove()方法
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }
        // Dispose receiver would dispose client InputChannel, too. That could send out a socket
        // broken event, so we need to unregister the server InputChannel when removing window to
        // prevent server side receive the event and prompt error.
        if (mInputEventReceiver != null) {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }

        mDisplayManager.unregisterDisplayListener(mDisplayListener);

        unscheduleTraversals();
    }

die()方法内部做了简单的判断,如果是异步删除,则发送一个MSG_DIE的消息,ViewRootImpl的Handler会处理该消息并调用 doDie(),若是同步删除则会直接调用 doDie() 方法。
在 doDie() 方法中,主要做了几件事:

  • 调用 dispatchDetachedFromWindow() 方法执行真正意义上View删除操作
  • 调用 WindowManagerGlobal 的 doRemoveView() 方法刷新数据,将当前Window关联的三类对象从列表中删除(mRoot,mParams,mDyingViews)

dispatchDetachedFromWindow() 方法中主要做了三件事:

  • 垃圾回收相关工作(清除数据,清除回调等等)
  • 调用View的 dispatchDetachedFromWindow() 方法进行资源回收的工作
  • 调用Session的remove()方法删除Window,最终会通过IPC的方式调用到WMS的 removeWindow() 方法

WindowManagerGlobal 的 doRemoveView() 方法源码如下:

/**
 * path: /frameworks/base/core/java/android/view/WindowManagerGlobal.java
 */
    void doRemoveView(ViewRootImpl root) {
        boolean allViewsRemoved;
        synchronized (mLock) {
            final int index = mRoots.indexOf(root);
            if (index >= 0) {
                mRoots.remove(index);
                mParams.remove(index);
                final View view = mViews.remove(index);
                mDyingViews.remove(view);
            }
            allViewsRemoved = mRoots.isEmpty();
        }
        if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
            doTrimForeground();
        }

        // If we don't have any views anymore in our process, we no longer need the
        // InsetsAnimationThread to save some resources.
        if (allViewsRemoved) {
            InsetsAnimationThread.release();
        }
    }

更新Window的手段(updateViewLayout())

Window的更新过程与删除过程相同,都是通过WimdowManagerImpl后,再利用WindowManagerGlobal来实现,WindowManagerGlobal的 updateViewLayout() 方法源码如下:

/**
 * path: /frameworks/base/core/java/android/view/WindowManagerGlobal.java
 */
    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            //更新View的LayoutParams,直接删除后添加
            mParams.remove(index);
            mParams.add(index, wparams);

            //更新ViewRootImpl的LayoutParams
            root.setLayoutParams(wparams, false);
        }
    }

updateViewLayout()方法做了几件事:

  • 更新View的LayoutParams
  • 更新ViewRootImpl的LayoutParams

ViewRootImpl的setLayoutParams()源码如下:

/**
 * path: /frameworks/base/core/java/android/view/ViewRootImpl.java
 */
    void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
        synchronized (this) {
            final int oldInsetLeft = mWindowAttributes.surfaceInsets.left;
            final int oldInsetTop = mWindowAttributes.surfaceInsets.top;
            final int oldInsetRight = mWindowAttributes.surfaceInsets.right;
            final int oldInsetBottom = mWindowAttributes.surfaceInsets.bottom;
            final int oldSoftInputMode = mWindowAttributes.softInputMode;
            final boolean oldHasManualSurfaceInsets = mWindowAttributes.hasManualSurfaceInsets;

            if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
                    & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
                    && (attrs.flags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0) {
                Slog.d(mTag, "setLayoutParams: FLAG_KEEP_SCREEN_ON from true to false!");
            }

            // Keep track of the actual window flags supplied by the client.
            mClientWindowLayoutFlags = attrs.flags;

            // Preserve compatible window flag if exists.
            final int compatibleWindowFlag = mWindowAttributes.privateFlags
                    & WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;

            // Transfer over system UI visibility values as they carry current state.
            attrs.systemUiVisibility = mWindowAttributes.systemUiVisibility;
            attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;

            final int changes = mWindowAttributes.copyFrom(attrs);
            if ((changes & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
                // Recompute system ui visibility.
                mAttachInfo.mRecomputeGlobalAttributes = true;
            }
            if ((changes & WindowManager.LayoutParams.LAYOUT_CHANGED) != 0) {
                // Request to update light center.
                mAttachInfo.mNeedsUpdateLightCenter = true;
            }
            if (mWindowAttributes.packageName == null) {
                mWindowAttributes.packageName = mBasePackageName;
            }
            mWindowAttributes.privateFlags |= compatibleWindowFlag;

            mWindowAttributes.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;

            if (mWindowAttributes.preservePreviousSurfaceInsets) {
                // Restore old surface insets.
                mWindowAttributes.surfaceInsets.set(oldInsetLeft, oldInsetTop, oldInsetRight, oldInsetBottom);
                mWindowAttributes.hasManualSurfaceInsets = oldHasManualSurfaceInsets;
            } else if (mWindowAttributes.surfaceInsets.left != oldInsetLeft
                    || mWindowAttributes.surfaceInsets.top != oldInsetTop
                    || mWindowAttributes.surfaceInsets.right != oldInsetRight
                    || mWindowAttributes.surfaceInsets.bottom != oldInsetBottom) {
                mNeedsRendererSetup = true;
            }

            applyKeepScreenOnFlag(mWindowAttributes);

            if (newView) {
                mSoftInputMode = attrs.softInputMode;
                requestLayout();
            }

            // Don't lose the mode we last auto-computed.
            if ((attrs.softInputMode & SOFT_INPUT_MASK_ADJUST) == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
                mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode & ~SOFT_INPUT_MASK_ADJUST) | (oldSoftInputMode & SOFT_INPUT_MASK_ADJUST);
            }

            if (mWindowAttributes.softInputMode != oldSoftInputMode) {
                requestFitSystemWindows();
            }

            mWindowAttributesChanged = true;
            scheduleTraversals();
        }
    }

setLayoutParams()方法调用到scheduleTraversals()对View重新布局

Window是如何诞生的

View是Android视图的呈现方式,但View不能单独存在,必须依附在Window上,有视图的地方就有Window。Android中可以提供视图的有Activity,Dialog,Toast等等,接下来捋捋Window在他们那边是如何诞生的

Window如何在Activity中诞生

要了解Window在Activity中是如何诞生的,需要了解Activity的启动流程。Activity的启动流程很复杂 (之后另写一篇文章专门讲) ,最终调用到ActivityThread的 performLaunchActivity() 方法完成最终的启动流程,在这个方法内部通过类加载器创建Activity的实例对象,并调用 attach()方法关联运行过程中所依赖的一系列上下文环境变量,相关源码如下:

/**
 * path: /frameworks/base/core/java/android/app/ActivityThread.java
 */
   /**  Core implementation of activity launch. */
   private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       ActivityInfo aInfo = r.activityInfo;
       ...
       ContextImpl appContext = createBaseContextForActivity(r);
       Activity activity = null;
       try {
           java.lang.ClassLoader cl = appContext.getClassLoader();
           activity = mInstrumentation.newActivity(
                   cl, component.getClassName(), r.intent);
           StrictMode.incrementExpectedActivityCount(activity.getClass());
           r.intent.setExtrasClassLoader(cl);
           r.intent.prepareToEnterProcess();
           if (r.state != null) {
               r.state.setClassLoader(cl);
           }
       } catch (Exception e) {
          ...
       }

       try {
           ...
           if (activity != null) {
               ...
               activity.attach(appContext, this, getInstrumentation(), r.token,
                       r.ident, app, r.intent, r.activityInfo, title, r.parent,
                       r.embeddedID, r.lastNonConfigurationInstances, config,
                       r.referrer, r.voiceInteractor, window, r.configCallback,
                       r.assistToken);

               ...
           }
           ...

       } catch (SuperNotCalledException e) {
           throw e;

       } catch (Exception e) {
           if (!mInstrumentation.onException(activity, e)) {
               throw new RuntimeException(
                   "Unable to start activity " + component
                   + ": " + e.toString(), e);
           }
       }

       return activity;
   }

在Activity的attach()方法中,系统会创建Activity所属的Window对象并设置回调接口,相关源码如下:

/**
 * path: /frameworks/base/core/java/android/app/Activity.java
 */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    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, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        //直接创建PhoneWindow对象
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        //设置回调接口,当Window收到外界状态改变就会回调Activity的方法
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        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;
        mAssistToken = assistToken;
        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);
        mWindow.setPreferMinimalPostProcessing(
                (info.flags & ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING) != 0);

        setAutofillOptions(application.getAutofillOptions());
        setContentCaptureOptions(application.getContentCaptureOptions());
    }

Activity中的Window已经创建好了,Activity的视图是通过 setContentView() 方法来设置的,看一下源码:

/**
 * path: /frameworks/base/core/java/android/app/Activity.java
 */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

Activity将具体的实现交给了PhoneWindow来处理,则跳转到PhoneWindow的setContentView()方法看一下:

/**
 * path: /frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
 */
    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            //1. 创建DecorView
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //2. 将View添加到DecorView的mContentParent中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        //3. 回调Activity的onContentChanged()方法
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //创建DecorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            //加载具体的布局文件
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeFrameworkOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            if (decorContentParent != null) {
                mDecorContentParent = decorContentParent;
                mDecorContentParent.setWindowCallback(getCallback());
                if (mDecorContentParent.getTitle() == null) {
                    mDecorContentParent.setWindowTitle(mTitle);
                }

                final int localFeatures = getLocalFeatures();
                for (int i = 0; i < FEATURE_MAX; i++) {
                    if ((localFeatures & (1 << i)) != 0) {
                        mDecorContentParent.initFeature(i);
                    }
                }

                mDecorContentParent.setUiOptions(mUiOptions);

                if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
                        (mIconRes != 0 && !mDecorContentParent.hasIcon())) {
                    mDecorContentParent.setIcon(mIconRes);
                } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
                        mIconRes == 0 && !mDecorContentParent.hasIcon()) {
                    mDecorContentParent.setIcon(
                            getContext().getPackageManager().getDefaultActivityIcon());
                    mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
                }
                if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
                        (mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
                    mDecorContentParent.setLogo(mLogoRes);
                }

                // Invalidate if the panel menu hasn't been created before this.
                // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
                // being called in the middle of onCreate or similar.
                // A pending invalidation will typically be resolved before the posted message
                // would run normally in order to satisfy instance state restoration.
                PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
                if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) {
                    invalidatePanelMenu(FEATURE_ACTION_BAR);
                }
            } else {
                mTitleView = findViewById(R.id.title);
                if (mTitleView != null) {
                    if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                        final View titleContainer = findViewById(R.id.title_container);
                        if (titleContainer != null) {
                            titleContainer.setVisibility(View.GONE);
                        } else {
                            mTitleView.setVisibility(View.GONE);
                        }
                        mContentParent.setForeground(null);
                    } else {
                        mTitleView.setText(mTitle);
                    }
                }
            }

            if (mDecor.getBackground() == null && mBackgroundFallbackDrawable != null) {
                mDecor.setBackgroundFallback(mBackgroundFallbackDrawable);
            }

            // Only inflate or create a new TransitionManager if the caller hasn't
            // already set a custom one.
            if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
                if (mTransitionManager == null) {
                    final int transitionRes = getWindowStyle().getResourceId(
                            R.styleable.Window_windowContentTransitionManager,
                            0);
                    if (transitionRes != 0) {
                        final TransitionInflater inflater = TransitionInflater.from(getContext());
                        mTransitionManager = inflater.inflateTransitionManager(transitionRes,
                                mContentParent);
                    } else {
                        mTransitionManager = new TransitionManager();
                    }
                }

                mEnterTransition = getTransition(mEnterTransition, null,
                        R.styleable.Window_windowEnterTransition);
                mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
                        R.styleable.Window_windowReturnTransition);
                mExitTransition = getTransition(mExitTransition, null,
                        R.styleable.Window_windowExitTransition);
                mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
                        R.styleable.Window_windowReenterTransition);
                mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
                        R.styleable.Window_windowSharedElementEnterTransition);
                mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
                        USE_DEFAULT_TRANSITION,
                        R.styleable.Window_windowSharedElementReturnTransition);
                mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
                        R.styleable.Window_windowSharedElementExitTransition);
                mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
                        USE_DEFAULT_TRANSITION,
                        R.styleable.Window_windowSharedElementReenterTransition);
                if (mAllowEnterTransitionOverlap == null) {
                    mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
                            R.styleable.Window_windowAllowEnterTransitionOverlap, true);
                }
                if (mAllowReturnTransitionOverlap == null) {
                    mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
                            R.styleable.Window_windowAllowReturnTransitionOverlap, true);
                }
                if (mBackgroundFadeDurationMillis < 0) {
                    mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
                            R.styleable.Window_windowTransitionBackgroundFadeDuration,
                            DEFAULT_BACKGROUND_FADE_DURATION_MS);
                }
                if (mSharedElementsUseOverlay == null) {
                    mSharedElementsUseOverlay = getWindowStyle().getBoolean(
                            R.styleable.Window_windowSharedElementsUseOverlay, true);
                }
            }
        }
    }

PhoneWindow的setContentView主要做了几件事:

  1. 判断DecorView是否存在,不存在则创建,并加载具体的布局到DecorView中
  2. 将View添加到DecorView的mContentParent中(mLayoutInflater.inflate(layoutResID, mContentParent))
  3. 回调Activity的onContentChanged()方法通知Activity视图已经发生改变

通过三个步骤,DecorView已经创建并初始化完毕,此时还没被WindowManager识别,用户此时也看不到这个视图,Acticity启动流程中会调用 ActivityThread 的 handleResumeActivity() 方法,在这个方法中,会调用makeVisible()方法,使DecorView真正完成添加和显示,此时用户才能看到视图。相关源码如下:

/**
 * path: /frameworks/base/core/java/android/app/ActivityThread.java
 */
    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        // 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
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        if (r == null) {
            // We didn't actually resume the activity, so skipping any follow-up actions.
            return;
        }
        if (mActivitiesToBeDestroyed.containsKey(token)) {
            // Although the activity is resumed, it is going to be destroyed. So the following
            // UI operations are unnecessary and also prevents exception because its token may
            // be gone that window manager cannot recognize it. All necessary cleanup actions
            // performed below will be done while handling destruction.
            return;
        }

        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 = ActivityTaskManager.getService().willActivityBeVisible(
                        a.getActivityToken());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        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 (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }

            // 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, false /* force */);

        // 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) {
                performConfigurationChangedForActivity(r, r.newConfig);
                if (DEBUG_CONFIGURATION) {
                    Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig "
                            + r.activity.mCurrentConfig);
                }
                r.newConfig = null;
            }
            if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward);
            ViewRootImpl impl = r.window.getDecorView().getViewRootImpl();
            WindowManager.LayoutParams l = impl != null
                    ? impl.mWindowAttributes : 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();
            }
        }

        r.nextIdle = mNewActivities;
        mNewActivities = r;
        if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
        Looper.myQueue().addIdleHandler(new Idler());
    }

makeVisible()源码:

/**
 * path: /frameworks/base/core/java/android/app/Activity.java
 */
    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

Window如何在Dialog中诞生

Dialog中创建Window过程和Activity类似,主要分为以下几个步骤:

  1. 创建Window

    /**
    * path: /frameworks/base/core/java/android/app/Dialog.java
    */
     @UnsupportedAppUsage
     final Window mWindow;
    
     Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
         if (createContextThemeWrapper) {
             if (themeResId == Resources.ID_NULL) {
                 final TypedValue outValue = new TypedValue();
                 context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                 themeResId = outValue.resourceId;
             }
             mContext = new ContextThemeWrapper(context, themeResId);
         } else {
             mContext = context;
         }
    
         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    
         //此处与Activity一致
         final Window w = new PhoneWindow(mContext);
         mWindow = w;
         w.setCallback(this);
         w.setOnWindowDismissedCallback(this);
         w.setOnWindowSwipeDismissedCallback(() -> {
             if (mCancelable) {
                 cancel();
             }
         });
         w.setWindowManager(mWindowManager, null, null);
         w.setGravity(Gravity.CENTER);
    
         mListenersHandler = new ListenersHandler(this);
     }
  2. 初始化DecorView并将Dialog的视图加入到DecorView中,与Activity类似

    /**
    * path: /frameworks/base/core/java/android/app/Dialog.java
    */
      public void setContentView(@LayoutRes int layoutResID) {
          mWindow.setContentView(layoutResID);
      }
  3. 将DecorView添加到Window中并显示(show()方***执行这操作)

    /**
    * path: /frameworks/base/core/java/android/app/Dialog.java
    */
    /**
    * Start the dialog and display it on screen.  The window is placed in the
    * application layer and opaque.  Note that you should not override this
    * method to do initialization when the dialog is shown, instead implement
    * that in {@link #onStart}.
    */
    public void show() {
     if (mShowing) {
         if (mDecor != null) {
             if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                 mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
             }
             mDecor.setVisibility(View.VISIBLE);
         }
         return;
     }
    
     mCanceled = false;
    
     if (!mCreated) {
         dispatchOnCreate(null);
     } else {
         // Fill the DecorView in on any configuration changes that
         // may have occured while it was removed from the WindowManager.
         final Configuration config = mContext.getResources().getConfiguration();
         mWindow.getDecorView().dispatchConfigurationChanged(config);
     }
    
     onStart();
     mDecor = mWindow.getDecorView();
    
     if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
         final ApplicationInfo info = mContext.getApplicationInfo();
         mWindow.setDefaultIcon(info.icon);
         mWindow.setDefaultLogo(info.logo);
         mActionBar = new WindowDecorActionBar(this);
     }
    
     WindowManager.LayoutParams l = mWindow.getAttributes();
     boolean restoreSoftInputMode = false;
     if ((l.softInputMode
             & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
         l.softInputMode |=
                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
         restoreSoftInputMode = true;
     }
    
     mWindowManager.addView(mDecor, l);
     if (restoreSoftInputMode) {
         l.softInputMode &=
                 ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
     }
    
     mShowing = true;
    
     sendShowMessage();
    }

    从上面的过程来看Dialog和Activity创建window过程很相似,几乎没什么区别

    Window如何在Toast中诞生

    Toast也是基于Window来实现,但它的工作过程与Dialog和Activity不同。接下来分析一下Toast是如何创建Window的
    Toast具有定时取消的功能,其内部采用了Handler来实现。在Toast中有两种IPC过程:

  • Toast访问NotificationManagerService
  • NotificationManagerService回调Toast中的TN接口

Toast提供了show()cancel() 方法来显示和隐藏Toast,内部是一个IPC过程,源码如下:

/**
 * path:/frameworks/base/core/java/android/widget/Toast.java
 */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    final TN mTN;

    /**
     * Show the view for the specified duration.
     */
    public void show() {
        if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
            checkState(mNextView != null || mText != null, "You must either set a text or a view");
        } else {
            if (mNextView == null) {
                throw new RuntimeException("setView must have been called");
            }
        }

        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;
        final int displayId = mContext.getDisplayId();

        try {
            if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
                if (mNextView != null) {
                    // It's a custom toast
                    service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
                } else {
                    // It's a text toast
                    ITransientNotificationCallback callback =
                            new CallbackBinder(mCallbacks, mHandler);
                    service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback);
                }
            } else {
                service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
            }
        } catch (RemoteException e) {
            // Empty
        }
    }

    /**
     * Close the view if it's showing, or don't show it if it isn't showing yet.
     * You do not normally have to call this.  Normally view will disappear on its own
     * after the appropriate duration.
     */
    public void cancel() {
        if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM) && mNextView == null) {
            try {
                getService().cancelToast(mContext.getOpPackageName(), mToken);
            } catch (RemoteException e) {
                // Empty
            }
        } else {
            mTN.cancel();
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    static private INotificationManager getService() {
        if (sService != null) {
            return sService;
        }
        sService = INotificationManager.Stub.asInterface(
                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
        return sService;
    }

显示和隐藏Toast都是通过NotificationManagerService(简称NMS)来实现的。NMS运行在系统进程中,需要通过IPC来调用。Toast中有个TN类,这个类属于Binder类,在Toast和NMS通信过程中,当NMS处理Toast显示或者隐藏请求时,会跨进程调用TN类中的方法。由于TN运行在Binder线程池中,需要通过Handler来切换到当前线程(指发送Toast请求所在线程),Handler需要Looper才能完成线程切换,所以Toast不能在没有Looper的线程中弹出

Toast的显示过程

由上面的代码可知,Toast调用NMS的enqueueToast() 方法来显示,源码如下:

/**
 * path:/frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
 */
    final ArrayList<ToastRecord> mToastQueue = new ArrayList<>();

    @VisibleForTesting
    final IBinder mService = new INotificationManager.Stub() {
        // Toasts
        // ============================================================================

        @Override
        public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration,
                int displayId, @Nullable ITransientNotificationCallback callback) {
            enqueueToast(pkg, token, text, null, duration, displayId, callback);
        }

        @Override
        public void enqueueToast(String pkg, IBinder token, ITransientNotification callback,
                int duration, int displayId) {
            enqueueToast(pkg, token, null, callback, duration, displayId, null);
        }

        private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text,
                @Nullable ITransientNotification callback, int duration, int displayId,
                @Nullable ITransientNotificationCallback textCallback) {
            if (DBG) {
                Slog.i(TAG, "enqueueToast pkg=" + pkg + " token=" + token
                        + " duration=" + duration + " displayId=" + displayId);
            }

            if (pkg == null || (text == null && callback == null)
                    || (text != null && callback != null) || token == null) {
                Slog.e(TAG, "Not enqueuing toast. pkg=" + pkg + " text=" + text + " callback="
                        + " token=" + token);
                return;
            }

            final int callingUid = Binder.getCallingUid();
            final UserHandle callingUser = Binder.getCallingUserHandle();
            final boolean isSystemToast = isCallerSystemOrPhone()
                    || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
            final boolean isPackageSuspended = isPackagePaused(pkg);
            final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg,
                    callingUid);

            final boolean appIsForeground;
            long callingIdentity = Binder.clearCallingIdentity();
            try {
                appIsForeground = mActivityManager.getUidImportance(callingUid)
                        == IMPORTANCE_FOREGROUND;
            } finally {
                Binder.restoreCallingIdentity(callingIdentity);
            }

            if (ENABLE_BLOCKED_TOASTS && !isSystemToast && ((notificationsDisabledForPackage
                    && !appIsForeground) || isPackageSuspended)) {
                Slog.e(TAG, "Suppressing toast from package " + pkg
                        + (isPackageSuspended ? " due to package suspended."
                        : " by user request."));
                return;
            }

            boolean isAppRenderedToast = (callback != null);
            if (isAppRenderedToast && !isSystemToast && !isPackageInForegroundForToast(pkg,
                    callingUid)) {
                boolean block;
                long id = Binder.clearCallingIdentity();
                try {
                    // CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK is gated on targetSdk, so block will be
                    // false for apps with targetSdk < R. For apps with targetSdk R+, text toasts
                    // are not app-rendered, so isAppRenderedToast == true means it's a custom
                    // toast.
                    block = mPlatformCompat.isChangeEnabledByPackageName(
                            CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK, pkg,
                            callingUser.getIdentifier());
                } catch (RemoteException e) {
                    // Shouldn't happen have since it's a local local
                    Slog.e(TAG, "Unexpected exception while checking block background custom toasts"
                            + " change", e);
                    block = false;
                } finally {
                    Binder.restoreCallingIdentity(id);
                }
                if (block) {
                    Slog.w(TAG, "Blocking custom toast from package " + pkg
                            + " due to package not in the foreground");
                    return;
                }
            }

            synchronized (mToastQueue) {
                int callingPid = Binder.getCallingPid();
                long callingId = Binder.clearCallingIdentity();
                try {
                    ToastRecord record;
                    int index = indexOfToastLocked(pkg, token);
                    // If it's already in the queue, we update it in place, we don't
                    // move it to the end of the queue.
                    if (index >= 0) {
                        record = mToastQueue.get(index);
                        record.update(duration);
                    } else {
                        // Limit the number of toasts that any given package can enqueue.
                        // Prevents DOS attacks and deals with leaks.
                        int count = 0;
                        final int N = mToastQueue.size();
                        for (int i = 0; i < N; i++) {
                            final ToastRecord r = mToastQueue.get(i);
                            if (r.pkg.equals(pkg)) {
                                count++;
                                //判断应用的ToastRecord是否超过50,预防DOS攻击
                                if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                                    Slog.e(TAG, "Package has already posted " + count
                                            + " toasts. Not showing more. Package=" + pkg);
                                    return;
                                }
                            }
                        }

                        Binder windowToken = new Binder();
                        mWindowManagerInternal.addWindowToken(windowToken, TYPE_TOAST, displayId);
                        //封装Toast请求
                        record = getToastRecord(callingUid, callingPid, pkg, token, text, callback,
                                duration, windowToken, displayId, textCallback);
                        //添加到mToastQueue中
                        mToastQueue.add(record);
                        index = mToastQueue.size() - 1;
                        keepProcessAliveForToastIfNeededLocked(callingPid);
                    }
                    // If it's at index 0, it's the current toast.  It doesn't matter if it's
                    // new or just been updated, show it.
                    // If the callback fails, this will remove it from the list, so don't
                    // assume that it's valid after this.
                    if (index == 0) {
                        //显示当前的Toast
                        showNextToastLocked();
                    }
                } finally {
                    Binder.restoreCallingIdentity(callingId);
                }
            }
        }
        ...
    }
    //封装Toast请求
    private ToastRecord getToastRecord(int uid, int pid, String packageName, IBinder token,
            @Nullable CharSequence text, @Nullable ITransientNotification callback, int duration,
            Binder windowToken, int displayId,
            @Nullable ITransientNotificationCallback textCallback) {
        if (callback == null) {
            return new TextToastRecord(this, mStatusBar, uid, pid, packageName, token, text,
                    duration, windowToken, displayId, textCallback);
        } else {
            return new CustomToastRecord(this, uid, pid, packageName, token, callback, duration,
                    windowToken, displayId);
        }
    }

    //显示当前的Toast
    @GuardedBy("mToastQueue")
    void showNextToastLocked() {
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            if (record.show()) {
                //发送延时消息
                scheduleDurationReachedLocked(record);
                return;
            }
            int index = mToastQueue.indexOf(record);
            if (index >= 0) {
                mToastQueue.remove(index);
            }
            record = (mToastQueue.size() > 0) ? mToastQueue.get(0) : null;
        }
    }
    private WorkerHandler mHandler;

    //发送延时消息
    @GuardedBy("mToastQueue")
    private void scheduleDurationReachedLocked(ToastRecord r) {
        mHandler.removeCallbacksAndMessages(r);
        Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
        int delay = r.getDuration() == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
        // Accessibility users may need longer timeout duration. This api compares original delay
        // with user's preference and return longer one. It returns original delay if there's no
        // preference.
        delay = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
                AccessibilityManager.FLAG_CONTENT_TEXT);
        mHandler.sendMessageDelayed(m, delay);
    }

    protected class WorkerHandler extends Handler{
        ...
        @Override
        public void handleMessage(Message msg)
        {
            switch (msg.what){
                case MESSAGE_DURATION_REACHED:
                    handleDurationReached((ToastRecord) msg.obj);
                    break;
                ...
            }
        }
    }

    //隐藏Toast
    private void handleDurationReached(ToastRecord record) {
        if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " token=" + record.token);
        synchronized (mToastQueue) {
            int index = indexOfToastLocked(record.pkg, record.token);
            if (index >= 0) {
                cancelToastLocked(index);
            }
        }
    }

enqueueToast() 做了这几件事:

  • 将Toast请求封装为ToastRecord对象并添加到mToastQueue队列中
  • 判断应用的ToastRecord是否超过50,预防DOS攻击
  • 调用showNextToastLocked() 显示当前的Toast
  • 在Toast显示之后发送一个延时消息,具体延时取决于Toast的时长,延迟相应的时间之后,调用cancelToastLocked() 方法来隐藏Toast,并将其从mToastQueue中移除
  • 如果mToastQueue中还有其他Toast,继续显示其他Toast

Toast显示是由ToastRecord的show() 方法来实现,ToastRecords是抽象类,从源码来看,真正的实现是CustomToastRecord,CustomToastRecord的源码如下:

/**
 * path:/frameworks/base/services/core/java/com/android/server/notification/toast/CustomToastRecord.java
 */

 /**
  * Represents a custom toast, a toast whose view is provided by the app.
  */
 public class CustomToastRecord extends ToastRecord {
     private static final String TAG = NotificationManagerService.TAG;

     public final ITransientNotification callback;

     public CustomToastRecord(NotificationManagerService notificationManager, int uid, int pid,
             String packageName, IBinder token, ITransientNotification callback, int duration,
             Binder windowToken, int displayId) {
         super(notificationManager, uid, pid, packageName, token, duration, windowToken, displayId);
         this.callback = checkNotNull(callback);
     }

     @Override
     public boolean show() {
         if (DBG) {
             Slog.d(TAG, "Show pkg=" + pkg + " callback=" + callback);
         }
         try {
             callback.show(windowToken);
             return true;
         } catch (RemoteException e) {
             Slog.w(TAG, "Object died trying to show custom toast " + token + " in package "
                     + pkg);
             mNotificationManager.keepProcessAliveForToastIfNeeded(pid);
             return false;
         }
     }
     ...
 }

CustomToastRecord的show() 方法调用callback的show() 方法,在封装Toast时,这个callback实际上就是Toast中的TN对象,最终调用TN中的方***运行在发起Toast请求的应用的Binder线程池中

Toast的显示效果是通过Toast的TN类的show() 方法来实现的(NMS是控制Toast显示的,显示样式由Toast决定),接下来看一下具体实现(这个方法运行在Binder线程池中):

/**
 * path:/frameworks/base/core/java/android/widget/Toast.java
 */

private static class TN extends ITransientNotification.Stub {
    /**
     * schedule handleShow into the right thread
     */
    @Override
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    public void show(IBinder windowToken) {
       if (localLOGV) Log.v(TAG, "SHOW: " + this);
       mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
    }

    final Handler mHandler;
    TN(Context context, String packageName, Binder token, List<Callback> callbacks,
            @Nullable Looper looper) {
        ...
        mHandler = new Handler(looper, null) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case SHOW: {
                        IBinder token = (IBinder) msg.obj;
                        handleShow(token);
                        break;
                    }
                    case HIDE: {
                        handleHide();
                        // Don't do this in handleHide() because it is also invoked by
                        // handleShow()
                        mNextView = null;
                        break;
                    }
                    case CANCEL: {
                        handleHide();
                        // Don't do this in handleHide() because it is also invoked by
                        // handleShow()
                        mNextView = null;
                        try {
                            getService().cancelToast(mPackageName, mToken);
                        } catch (RemoteException e) {
                        }
                        break;
                    }
                }
            }
        };
    }

    private final ToastPresenter mPresenter;

    public void handleShow(IBinder windowToken) {
        if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                + " mNextView=" + mNextView);
        // If a cancel/hide is pending - no need to show - at this point
        // the window token is already invalid and no need to do any work.
        if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
            return;
        }
        if (mView != mNextView) {
            // remove the old view if necessary
            handleHide();
            mView = mNextView;
            mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
                    mHorizontalMargin, mVerticalMargin,
                    new CallbackBinder(getCallbacks(), mHandler));
        }
    }
}

最终show() 调用ToastPresenter的show() 方法,这个方法是真正执行Toast显示的方法,源码如下:

/**
 * path:/frameworks/base/core/java/android/widget/ToastPresenter.java
 */

public class ToastPresenter {
    /**
     * Shows the toast in {@code view} with the parameters passed and callback {@code callback}.
     */
    public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
            int xOffset, int yOffset, float horizontalMargin, float verticalMargin,
            @Nullable ITransientNotificationCallback callback) {
        checkState(mView == null, "Only one toast at a time is allowed, call hide() first.");
        mView = view;
        mToken = token;

        adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
                horizontalMargin, verticalMargin);
        if (mView.getParent() != null) {
            mWindowManager.removeView(mView);
        }
        try {
            //添加视图
            mWindowManager.addView(mView, mParams);
        } catch (WindowManager.BadTokenException e) {
            // Since the notification manager service cancels the token right after it notifies us
            // to cancel the toast there is an inherent race and we may attempt to add a window
            // after the token has been invalidated. Let us hedge against that.
            Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
            return;
        }
        trySendAccessibilityEvent(mView, mPackageName);
        if (callback != null) {
            try {
                callback.onToastShown();
            } catch (RemoteException e) {
                Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e);
            }
        }
    }
}

Toast的隐藏过程

Toast的隐藏与显示的执行过程一样,在MNS中调用ToastRecord的hide() 方法来实现,具体直接看源码的执行过程:

/**
 * path:/frameworks/base/core/java/android/widget/Toast.java
 */
public void cancel() {
    if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)
            && mNextView == null) {
        try {
            getService().cancelToast(mContext.getOpPackageName(), mToken);
        } catch (RemoteException e) {
            // Empty
        }
    } else {
        mTN.cancel();
    }
}

/**
 * path:/frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
 */
@VisibleForTesting
final IBinder mService = new INotificationManager.Stub() {
    ...
    @Override
    public void cancelToast(String pkg, IBinder token) {
        Slog.i(TAG, "cancelToast pkg=" + pkg + " token=" + token);

        if (pkg == null || token == null) {
            Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " token=" + token);
            return;
        }

        synchronized (mToastQueue) {
            long callingId = Binder.clearCallingIdentity();
            try {
                int index = indexOfToastLocked(pkg, token);
                if (index >= 0) {
                    cancelToastLocked(index);
                } else {
                    Slog.w(TAG, "Toast already cancelled. pkg=" + pkg
                            + " token=" + token);
                }
            } finally {
                Binder.restoreCallingIdentity(callingId);
            }
        }
    }
}
@GuardedBy("mToastQueue")
void cancelToastLocked(int index) {
    ToastRecord record = mToastQueue.get(index);
    record.hide();

    ToastRecord lastToast = mToastQueue.remove(index);

    mWindowManagerInternal.removeWindowToken(lastToast.windowToken, false /* removeWindows */,
            lastToast.displayId);
    // We passed 'false' for 'removeWindows' so that the client has time to stop
    // rendering (as hide above is a one-way message), otherwise we could crash
    // a client which was actively using a surface made from the token. However
    // we need to schedule a timeout to make sure the token is eventually killed
    // one way or another.
    scheduleKillTokenTimeout(lastToast);

    keepProcessAliveForToastIfNeededLocked(record.pid);
    if (mToastQueue.size() > 0) {
        // Show the next one. If the callback fails, this will remove
        // it from the list, so don't assume that the list hasn't changed
        // after this point.
        showNextToastLocked();
    }
}

/**
 * path:/frameworks/base/services/core/java/com/android/server/notification/toast/CustomToastRecord.java
 */
@Override
public void hide() {
    try {
        callback.hide();
    } catch (RemoteException e) {
        Slog.w(TAG, "Object died trying to hide custom toast " + token + " in package "
                + pkg);

    }
}

/**
 * path:/frameworks/base/core/java/android/widget/Toast.java
 */
//通过TN的hide()方法最终会调用到handleHide()方法
private static class TN extends ITransientNotification.Stub {
    ...
    @UnsupportedAppUsage
    public void handleHide() {
        if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
        if (mView != null) {
            checkState(mView == mPresenter.getView(),
                    "Trying to hide toast view different than the last one displayed");
            mPresenter.hide(new CallbackBinder(getCallbacks(), mHandler));
            mView = null;
        }
    }
}

/**
 * path:/frameworks/base/core/java/android/widget/ToastPresenter.java
 */
public class ToastPresenter {
    public void hide(@Nullable ITransientNotificationCallback callback) {
        checkState(mView != null, "No toast to hide.");

        if (mView.getParent() != null) {
            mWindowManager.removeViewImmediate(mView);
        }
        try {
            mNotificationManager.finishToken(mPackageName, mToken);
        } catch (RemoteException e) {
            Log.w(TAG, "Error finishing toast window token from package " + mPackageName, e);
        }
        if (callback != null) {
            try {
                callback.onToastHidden();
            } catch (RemoteException e) {
                Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e);
            }
        }
        mView = null;
        mToken = null;
    }
}

总结

Window在Toast中创建简直是山路十八弯,从Toast出发,流转到NMS进行一系列操作,最后流转回Toast,对Window的操作最终给了ToastPresenter来执行