Window 基本概念

  • 抽象类,具体实现类PhoneWindow
  • 通过 WindowManager 可以创建,具体实现是 WindowManagerService 中
  • WindowManager和 WindowManagerService 的交互是 IPC 过程
  • Android 所有的视图都是通过 Window 来呈现的,不管是 Activity , Dialog ,还是 Toast ,他们的视图都是附件在 Window 上的,Window是 View 的直接管理者
  • setContentView()的底层也是通过 Window 来完成的

Window和WindowManager

WindowManager.LayoutParams中的 flags 和type

Flags 参数

表示 window 的属性,可以控制 Window 的显示特性,主要常用的有一下

  • FLAG_NOT_FOCUSABLE

    表示 Window 不需要获取焦点,也不需要接受各种输入事件,同时启动 FLAG_NOT_TOUCH_MODAL ,事件最终传递给下层的焦点的Window

  • FLAG_NOT_TOUCH_MODAL

    此模式下,系统会将当前的 Window 区域以外的点击事件传递给底层的 Window ,当前 Window 区域以内的单击事件则自己处理,这个标记很重要,一般来说都需要开启此标记,否则其他 Window 将无法收到单击事件

  • FLAG_SHOW_WHEN_LOCKED

    让 Window 显示在锁屏界面上

Type参数

Window 的类型,分为三种

  • 应用Window

    层级范围1~99 , 对应一个Activity

  • 子Window

    层级范围 1000~1999, 不能单独存在,需要附属特定的的 Window 上,常见的一些 Dialog 就是一个子Window

  • 系统Window

    层级范围 2000~2999,需要声明权限才能创建的 Window ,比如 Toast 和系统状态栏

Window 是分层的,层级大的会覆盖层级小的,层级的范围对应这WindowManager.LayoutParams的 type 参数

WindowManager

  1. 继承ViewManager
  2. 常用的方法 addView() , updateViewLayout() ,removeView()
  3. 真正实现是WindowManagerImpl

Window的内部机制

  • 每一个 Window 对应一个 View 和ViewRootImpl
  • Window和 View 通过 ViewRootImpl 建立联系
  • Window并不是实际存在的,是以 View 的形式存在的
  • View才是 Window 存在的实体,所以对 Window 的添加,删除,更新都是针对 View 。

Window 的添加过程

其实就是通过 WindowManagerImpl 是添加一个 View , 那么我们就看看 WindowManagerImpl 的 addView() 方法吧。

 // WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    android.util.SeempLog.record_vg_layout(383,params);
    applyDefaultToken(params);
    mGlobal.addView(view, params , mContext.getDisplay(), mParentWindow);
}

WindowManagerImpl 也没做啥操作,只是简单的调用了 mGlobal 的 addView() 方法。那么这个 mGlobal 是啥玩意呢

  1. mGlobal 是 WindowManagerGlobal 的对象,
  2. WindowManagerGlobal以工厂形式向外提供自己的实例, WindowManager 中有这样一段代码
     private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    

    这是典型的桥接模式,将所有的的操作委托 WindowManagerGlobal 来实现

那就看看 WindowManagerGlobal 中的 addView() 干啥了吧。

public void addView(View view, ViewGroup.LayoutParams params, Display display , Window parentWindow) {
    // 1. 检查参数的合法性
    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;
    //如果有设置父窗口,会通过 adjustLayoutParamsForSubWindow 来调整 params 。
    if (parentWindow != null) {
      //调整布局参数,并设置token
      parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
      。。。
    }
    。。。
    //创建 ViewRootImpl ,并且将 view 与之绑定
    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);
    mViews.add(view);//将当前 view 添加到 mViews 集合中
    mRoots.add(root);//将当前 ViewRootImpl 添加到 mRoots 集合中
    mParams.add(wparams);//将当前 window 的 params 添加到 mParams 集合中

    //通过 ViewRootImpl 的 setView 方法,完成 view 的绘制流程,并添加到 window 上。
    root.setView(view, wparams , panelParentView);
    。。。
  }

通过代码我们可知,主要做了以下四件事:

  1. 检查参数是否合法
  2. 如果是子 View ,调整布局参数
  3. 创建 ViewRootImpl ,并将 View 添加到列表中。从这里也能看出,一个 View ,对应一个 ViewRootImpl ,对应一个 LayoutParams 。 这里面有几个集合,需要关注一下
    /* 存储所有 window 对应的View*/
    private final ArrayList<View> mViews = new ArrayList<View>();
    /*存储所有 Window 对应的ViewRootImpl*/
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    /*存储所有 Window 对应的布局参数*/
    private final ArrayList<WindowManager.LayoutParams> mParams =
             new ArrayList<WindowManager.LayoutParams>();
    /*存储那些正在被删除的 View 对象,或者说已经调用了 removeView 方法,但是删除操作还未完成的的 Window 对象*/
    private final ArraySet<View> mDyingViews = new ArraySet<View>();
    
  4. 通过 ViewRootImpl 来更新并完成 Window 的添加过程

那么接下来我们看看 怎么通过 ViewRootImpl 的 setView() 完成 View 的添加工作

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
  ...
  requestLayout();//View的绘制流程
  ...
  res = mWindowSession.addToDisplay(mWindow, mSeq , mWindowAttributes , getHostVisibility()
                                  , mDisplay.getDisplayId(), mAttachInfo.mContentInsets
                                  , mAttachInfo.mStableInsets, mInputChannel);
  ...
}

主要工作

  1. 执行 View 的绘制流程 。
  2. 通过 WindowSession 来最终完成 Window 的添加过程

在之前文章 View 的绘制 - Draw 流程, invalidate 的流程 以及 requestLayout 流程 中讲过。

setView()-> requestLayout()->scheduleTraversals()。
scheduleTraversals() 才是 View 绘制的入口。这里就不再细说

主要说说 WindowSession 的 addToDisplay()

我们先了解一下 mWindowSession 。

  1. mWindowSession 类型 IWindowSession ,是一个 Binder 对象,
  2. 在 ViewRootImpl 创建的时候,会创建这个 mWindowSession 对象。用来与 WMS 建立连接
// ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
  mWindowSession = WindowManagerGlobal.getWindowSession();
}

//WindowManagerGlobal.java
public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
      if (sWindowSession == null) {
        try {
          //创建 InputMethodManager 实例
          InputMethodManager imm = InputMethodManager.getInstance();
          //获取WMS
          IWindowManager windowManager = getWindowManagerService();
          //与 WMS 建立一个Session
          sWindowSession = windowManager.openSession(new IWindowSessionCallback.Stub() {
            @Override
            public void onAnimatorScaleChanged(float scale) {
              ValueAnimator.setDurationScale(scale);
            }
          }, imm.getClient(), imm.getInputContext());
        } catch (RemoteException e) {
          Log.e(TAG, "Failed to open window session", e);
        }
      }
      return sWindowSession;
    }
  }

// WindowManagerService.java
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client ,
        IInputContext inputContext) {
    if (client == null) throw new IllegalArgumentException("null client");
    if (inputContext == null) throw new IllegalArgumentException("null inputContext");
    Session session = new Session(this, callback , client , inputContext);
    return session;
}

//Session.java
public Session(WindowManagerService service, IWindowSessionCallback callback
                , IInputMethodClient client , IInputContext inputContext) {
    mService = service;
    ...
}

在 WMS 的 openSession() 中,创建一个 Session ,并且把 WMS 实例保存到 mService 中。并且返回这个 Session 对象。

也就是说 mWindowSession 正真的实现类是 Session 。

那么我们就看看 Session 中的 addToDisplay() 做了啥吧

//Session.java
@Override
public int addToDisplay(IWindow window, int seq , WindowManager.LayoutParams attrs,
 int viewVisibility , int displayId , Rect outContentInsets , Rect outStableInsets ,
 Rect outOutsets , InputChannel outInputChannel) {
    //通过 WindowManagerService 来实现 Window 的添加
    return mService.addWindow(this, window , seq , attrs , viewVisibility , displayId ,
 outContentInsets , outStableInsets , outOutsets , outInputChannel);
}

这个 mService 前面说了,就是 WMS ,调用 WMS 的 addWindow() 来实现 Window 的添加。这也是一个 IPC 调用。至于 WMS 怎么 addWindow() 了,下次再分析吧。

整个流程图如下 添加图片

Window 的删除过程

同 addView() 过程一样,桥接到 WindowManagerGlobal 中,执行 removeView() 方法实现的

在 WindowManager 提供两种删除接口:

  • removeView() 异步删除
  • removeViewImmdiate()同步删除,一般不需要此方法删除 Window ,以免发生意外

但是不管哪种,最后都执行到了 WindowManagerGlobal 的 removeView() 中,使用变量 immediate 表示是否为同步删除

//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 的索引
        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);
    }
}

逻辑很简单,首先通过 findViewLocked() 来查找带删除的 View 的索引,然后调用 removeViewLockde() 删除。查找的逻辑就不说了。直接看删除吧

//WindowManagerGlobal.java
private void removeViewLocked(int index, boolean immediate) {
  ViewRootImpl root = mRoots.get(index);
  View view = root.getView();
  if (view != null) {
      InputMethodManager imm = InputMethodManager.getInstance();
      if (imm != null) {
          imm.windowDismissed(mViews.get(index).getWindowToken());
      }
  }
  //通过 ViewRootImpl 来完成删除操作
  boolean deferred = root.die(immediate);
  if (view != null) {
      view.assignParent(null);
      if (deferred) {
          mDyingViews.add(view);
      }
  }
}

同样是通过 ViewRootImpl 来实现删除的。那么我们就看看 ViewRootImpl 的 die() 方法把

boolean die(boolean immediate) {
    //如果是立即删除的话,直接执行doDie()
    if (immediate && !mIsInTraversal) {
        doDie();
        return false;
    }

    if (!mIsDrawing) {
        destroyHardwareRenderer();
    } else {
        Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                "  window=" + this + ", title=" + mWindowAttributes.getTitle());
    }
    mHandler.sendEmptyMessage(MSG_DIE);
    return true;
}

die() 方法只发送一个请求删除的消息后就返回了。这个时候 View 并没有删除所以最后把这个要删除的 View 添加到 mDyingView 中 最终执行 doDie 方法,

void doDie() {
    ...
    if (mAdded) {
        // 在 doDie 中,会通过 dispatchDetachedFromWindow ,通知 View 树,窗口已经移除了
        dispatchDetachedFromWindow();
    }
    ...
    //刷新数据,包括 mRoots , mParams ,以及 mDyingViews ,需要将当前 Window 所关联的对象从列表中删除
    WindowManagerGlobal.getInstance().doRemoveView(this);
}

在 doDie() 中真正删除的逻辑在 dispatchDetachedFromWindow() 方法的内部实现

删除成功后,刷新数据,包括 mRoots , mParams ,以及 mDyingViews ,需要将当前 Window 所关联的对象从列表中移除

那么我们就看看这个 dispatchDetachedFromWindow() 吧

void dispatchDetachedFromWindow() {
    if (mView != null && mView.mAttachInfo != null) {
        mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
        //内部会调用 onDetachedFromWindow 和 onDetachedFromWindowInternal ,
        //当 View 从 Window 移除的时候,会执行 onDetachedFromWindow ,
        //可以在内部做一些资源回收的工作,例如终止动画,线程停止等
        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 {
        //通过 Session 的 remove 方法删除 Window ,同样是一个 IPC 过程,最终调用 WMS 的 removeWindow 方法
        mWindowSession.remove(mWindow);
    } catch (RemoteException e) {
    }
    。。。
}

看到没,也是一个 IPC 过程。通过 Session 调用 remove() ,最终执行 WMS 的 removeWindow() 来完成删除操作的。

整个流程图如下 添加图片

Window的更新

同样桥接到 WindowManagerGlobal 中的 updateViewLayout()

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 的LayoutParams
    view.setLayoutParams(wparams);

    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        mParams.remove(index);
        mParams.add(index, wparams);
        //更新 ViewRootImpl 的LayoutParams
        root.setLayoutParams(wparams, false);
    }
}
  1. 检查参数合法性
  2. 更新 ViewRootImpl 的 LayoutParams ,通过 setLayoutParams() 实现,内部会执行 scheduleTraversals() 对 ViewRootImpl 重新布局,包括测量,布局,重绘,整个流程入下
    setLayoutParams()->scheduleTraversals()->切换到主线程 mTraversalRunnable run()
    -> doTraversal()-> performTraversals()->relayoutWindow()
    ->mWindowSession.relayout()->Session.relayout->WMS.relayoutWindow()
    

    通过 WindowSession 更新 Window 的视图,这个过程最终交个 WMS 的 relayoutWindow() 来实现


搬运地址:

Android 开发艺术探索