Android Window学习记录(一)理解window和windowmanager

公冶渝
2023-12-01

一、Window

什么是Window

在Android框架层意义上,每个应用界面都有一个应用级window。例如popupWindow、Toast、dialog、menu都是需要通过创建window来实现。它是一个窗口的概念,也是一个抽象类。

view树是window机制的操作单位,在android机制中,每一个view**树**对应一个windowview树是window的存在形式。例如,我们平时看到的应用界面、dialog、popupWindow以及悬浮窗,都是window的表现形式
注意,我们看到的不是window,而是view树不像windows系统有一个很明显的标志,Android中我们只能看到view树无法看到window,window是控制view树需要怎么显示的管理者。每个view树背后都有一个window。
通过View的事件分发机制我们也可以知道,单击事件由Window传递给DecorView,然后再由DecorView传递给的View,就连Activity的设置视图的方法setContentView底层也是通过Window来完成的。

小结

为什么view树是window机制的操作单位,而不是每个view呢?

  • 因为view树中每个view的显示次序是固定的,例如我们的Activity布局,每一个控件的显示都是已经安排好的,对于window机制来说,属于“不可再分割的view”。Activity里面就是DecorView。

什么是view树?

  • 例如你在布局中给Activity设置了一个布局xml,那么最顶层的布局如LinearLayout就是view树的根,他包含的所有view就都是该view树的节点,所以个view树就对应一个window。
    举几个具体的例子:
    • 我们在添加dialog的时候,需要给他设置view,那么这个view不属于antivity的布局内是通过WindowManager添加到屏幕上的不属于activity的view树,所以这个**dialog是一个独立的view树,所以他是一个window。**
    • popupWindow他也对应一个window,因为它也是通过windowManager添加上去的,不属于Activity的view树。
    • 当我们使用windowManager在屏幕上添加的任何view都不属于Activity的布局view树,即使是只添加一个button。

为什么需要Window

如果我们需要让一个悬浮窗在所有界面显示,如果是在一个应用还好,直接在Activity里面设置即可,但在多个应用的情况下,如小米悬浮窗,怎么确定两个不同应用的view显示次序?又例如我们需要弹出一个dialog来提示用户,怎么样可以让dialog永远处于最顶层呢,包括显示dialog期间应用弹出的如popupWindow必须显示在dialog的低下,但toast又必须显示在dialog上面。

很明显,我们的屏幕可以允许多个应用同时显示非常多的view,他们的显示次序或者说显示高度是不一样的,如果没有一个统一的管理者,那么每一家应用都想要显示在最顶层,那么屏幕上的view会非常乱。

同时,当我们点击屏幕时,这个触摸事件应该传给哪个view?很明显我们都知道应该传给最上层的view,但是接受事件的是屏幕,是另一个系统服务,他怎么知道触摸位置的最上层是哪个view呢?即时知道,他又怎么把这个事件准确地传给他呢?

小结

  • 我们创造window来管理view树之间不同的层级,并且作为屏幕和view树的中间层将触摸事件分发给view树。
  • Android中所有的view树都是通过Window来呈现的,不管是Activity、Dialog还是Toast,它们的视图实际上都是附加在Window上的,因此Window实际是View树的直接管理者

Window的相关属性

在了解window的操作流程之前,先补充一下window的相关属性。

window的type属性

Type参数对应着WindowManager.LayoutParamstype参数,表示Window的类型。

Window是分层的,每个Window都有对应的z-ordered,层级大的会覆盖在层级小的Window的上面,很显然系统Window的层级是最大的。window的**type属性就是Z-Order的值**,我们可以给**type属性赋值来决定window的高度**。这个属性就解决了前面我们讲到的window的显示次序问题。Z-Order越大,window越靠近用户,显示越高。
window根据显示高度范围不同有三种分类

  • 应用程序window:应用程序window一般位于最底层,Z-Order在1-99。应用类Window对应着一个Activity
  • window:子**window一般是显示在应用window**之上,Z-Order1000-1999。子Window不能单独存在,它需要附属在特定的父Window之中,比如常见的一些Dialog就是一个子Window。
  • 系统级window:系统Window是需要声明权限在能创建的Window,一般位于最层,不会被其他的window遮住,如Toast,Z-Order,在2000-2999。如果要弹出自定义系统级window需要动态申请权限。

系统为我们三类window都预设了静态常量。

应用级window
// 应用程序 Window 的开始值
public static final int FIRST_APPLICATION_WINDOW = 1;

// 应用程序 Window 的基础值
public static final int TYPE_BASE_APPLICATION = 1;

// 普通的应用程序
public static final int TYPE_APPLICATION = 2;

// 特殊的应用程序窗口,当程序可以显示 Window 之前使用这个 Window 来显示一些东西
public static final int TYPE_APPLICATION_STARTING = 3;

// TYPE_APPLICATION 的变体,在应用程序显示之前,WindowManager 会等待这个 Window 绘制完毕
public static final int TYPE_DRAWN_APPLICATION = 4;

// 应用程序 Window 的结束值
public static final int LAST_APPLICATION_WINDOW = 99;

子window
// 子 Window 类型的开始值
public static final int FIRST_SUB_WINDOW = 1000;

// 应用程序 Window 顶部的面板。这些 Window 出现在其附加 Window 的顶部。
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;

// 用于显示媒体(如视频)的 Window。这些 Window 出现在其附加 Window 的后面。
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;

// 应用程序 Window 顶部的子面板。这些 Window 出现在其附加 Window 和任何Window的顶部
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;

// 当前Window的布局和顶级Window布局相同时,不能作为子代的容器
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;

// 用显示媒体 Window 覆盖顶部的 Window, 这是系统隐藏的 API
public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;

// 子面板在应用程序Window的顶部,这些Window显示在其附加Window的顶部, 这是系统隐藏的 API
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;

// 子 Window 类型的结束值
public static final int LAST_SUB_WINDOW = 1999;

系统级window
// 系统Window类型的开始值
public static final int FIRST_SYSTEM_WINDOW = 2000;

// 系统状态栏,只能有一个状态栏,它被放置在屏幕的顶部,所有其他窗口都向下移动
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;

// 系统搜索窗口,只能有一个搜索栏,它被放置在屏幕的顶部
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;

// 已经从系统中被移除,可以使用 TYPE_KEYGUARD_DIALOG 代替
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;

// 系统对话框窗口
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;

// 锁屏时显示的对话框
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;

// 输入法窗口,位于普通 UI 之上,应用程序可重新布局以免被此窗口覆盖
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;

// 输入法对话框,显示于当前输入法窗口之上
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;

// 墙纸
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;

// 状态栏的滑动面板
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;

// 应用程序叠加窗口显示在所有窗口之上
public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;

// 系统Window类型的结束值
public static final int LAST_SYSTEM_WINDOW = 2999;

系统级windowtype有很多值,一般我们选用TYPE_SYSTEM_OVERLAY或者TYPE_SYSTEM_ERROR,如果采用TYPE_SYSTEM_ERROR,只需要为type参数指定这个层级即可:mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;
同时声明权限:<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
。否则会在创建时报错

Window的flags参数

Flags参数表示Window的属性,它有很多选项,各种情景下的显示逻辑(锁屏,游戏等)还有触控事件的处理逻辑。控制显示是他的很大部分功能,但并不是全部。下面看一下一些常用的flag,就知道flag的功能了

// 当 Window 可见时允许锁屏
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;

// Window 后面的内容都变暗
public static final int FLAG_DIM_BEHIND = 0x00000002;

// Window 不能获得输入焦点,即不接受任何**按键或按钮事件**,例如该 Window 上 有 EditView,
点击 EditView 是 不会弹出软键盘的
// Window 范围外的事件依旧为原窗口处理;例如点击该窗口外的view,
依然会有响应。另外只要设置了此Flag,都将会启用FLAG_NOT_TOUCH_MODAL
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;

// 设置了该 Flag,将 Window 之外的按键事件发送给后面的 Window 处理, 
而自己只会处理 Window 区域内的触摸事件
// Window 之外的 view 也是可以响应 touch 事件。
public static final int FLAG_NOT_TOUCH_MODAL  = 0x00000020;

// 设置了该Flag,表示该 Window 将不会接受任何 touch 事件,例如点击该 Window 不会有响应,只会传给下面有聚焦的窗口。
public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;

// 只要 Window 可见时屏幕就会一直亮着
public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;

// 允许 Window 占满整个屏幕
public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;

// 允许 Window 超过屏幕之外
public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;

// 全屏显示,隐藏所有的 Window 装饰,比如在游戏、播放器中的全屏显示
public static final int FLAG_FULLSCREEN      = 0x00000400;

// 表示比FLAG_FULLSCREEN低一级,会显示状态栏
public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;

// 当用户的脸贴近屏幕时(比如打电话),不会去响应此事件
public static final int FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000;

// 则当按键动作发生在 Window 之外时,将接收到一个MotionEvent.ACTION_OUTSIDE事件。
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;

@Deprecated
// 窗口可以在锁屏的 Window 之上显示, 使用 Activity#setShowWhenLocked(boolean) 方法代替
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;

// 表示负责绘制系统栏背景。如果设置,系统栏将以透明背景绘制,
// 此 Window 中的相应区域将填充 Window#getStatusBarColor()和 Window#getNavigationBarColor()中指定的颜色。
public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;

// 表示要求系统壁纸显示在该 Window 后面,Window 表面必须是半透明的,才能真正看到它背后的壁纸
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;

window的softInputMode属性

这一部分就是当软件盘弹起来的时候,window的处理逻辑,这在日常中也经常遇到,如:我们在微信聊天的时候,点击输入框,当软键盘弹起来的时候输入框也会被顶上去。如果你不想被顶上去,也可以设置为被软键盘覆盖。下面介绍一下常见的属性

// 没有指定状态,系统会选择一个合适的状态或者依赖于主题的配置
public static final int SOFT_INPUT_STATE_UNCHANGED = 1;

// 当用户进入该窗口时,隐藏软键盘
public static final int SOFT_INPUT_STATE_HIDDEN = 2;

// 当窗口获取焦点时,隐藏软键盘
public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;

// 当用户进入窗口时,显示软键盘
public static final int SOFT_INPUT_STATE_VISIBLE = 4;

// 当窗口获取焦点时,显示软键盘
public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;

// window会调整大小以适应软键盘窗口
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;

// 没有指定状态,系统会选择一个合适的状态或依赖于主题的设置
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;

// 当软键盘弹出时,窗口会调整大小,例如点击一个EditView,整个layout都将平移可见且处于软件盘的上方
// 同样的该模式不能与SOFT_INPUT_ADJUST_PAN结合使用;
// 如果窗口的布局参数标志包含FLAG_FULLSCREEN,则将忽略这个值,窗口不会调整大小,但会保持全屏。
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;

// 当软键盘弹出时,窗口不需要调整大小, 要确保输入焦点是可见的,
// 例如有两个EditView的输入框,一个为Ev1,一个为Ev2,当你点击Ev1想要输入数据时,当前的Ev1的输入框会移到软键盘上方
// 该模式不能与SOFT_INPUT_ADJUST_RESIZE结合使用
public static final int SOFT_INPUT_ADJUST_PAN = 0x20;

// 将不会调整大小,直接覆盖在window上
public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;

window的其他属性
上面的三个属性是window比较重要也是比较复杂 的三个,除此之外还有几个日常经常使用的属性:

  • x与y属性:指定window的位置
  • alpha:window的透明度
  • gravity:window在屏幕中的位置,使用的是Gravity类的常量
  • format:window的像素点格式,值定义在PixelFormat中

如何给window属性赋值

window属性的常量值大部分存储在WindowManager.LayoutParams类中,我们可以通过这个类来获得这些常量。当然还有Gravity类和PixelFormat类等。

一般情况下我们会通过以下方式来往屏幕中添加一个window:

// 在Activity中调用
WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
windParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
TextView view = new TextView(this);
getWindowManager.addview(view,windowParams);

我们可以直接给WindowManager.LayoutParams对象设置属性。

第二种赋值方法是直接给window赋值,如

getWindow().flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
除此之外,window的softInputMode属性比较特殊,他可以直接在AndroidManifest中指定,如下:

<activity android:windowSoftInputMode="adjustNothing" />

小结

window的重要属性有type、flags、softInputMode、gravity等,我们可以通过不同的方式给window属性赋值,等遇到需求再去寻找对应的常量即可。

Window和WindowManager的关系

  • 每个window都被添加到PhoneWindowwindow的触摸事件等都会汇集到PhoneWindow,然后再交给Activity处理,那么Activity就可以通过操作PhoneWindow来操作Window
  • PhoneWindow本身不是真正意义上的window,他更多可以认为是辅助Activity操作window工具类
  • windowManagerImpl并不是管理window的类,而是管理PhoneWindow的类。真正管理window的是WindowManagerService
  • PhoneWindow可以配合DecorView可以给其中的window按照一定的逻辑提供标准的UI策略
    PhoneWindow限制了不同的组件添加window的权限。

二、WindowManager

WindowManager是外界访问Window的入口Window的具体实现位于WindowManagerService中,WindowManagerWindow-ManagerService的交互是一个IPC过程。

WindowManager是一个接口,它的真正实现是WindowManagerImpl类。

WindowManager所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View。从WindowManager的定义也可以看出,它提供的三个接口方法都是针对View的,这也说明View才是Window存在的实体。这三个方法定义在ViewManager中,而WindowManager继承了ViewManager。

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的过程更像是在操作Window中的View。我们时常见到那种可以拖动的Window效果,只需要根据手指的位置来设定LayoutParams中的x和y的值即可改变Window的位置。首先给View设置onTouchListener:mFloatingButton.setOnTouchListener(this)。然后在onTouch方法中不断更新View的位置即可:

public boolean onTouch(View v, MotionEvent event) {
    int rawX = (int) event.getRawX();
    int rawY = (int) event.getRawY();
    switch (event.getAction()) {
    case MotionEvent.ACTION_MOVE: {
        mLayoutParams.x = rawX;
        mLayoutParams.y = rawY;
        mWindowManager.updateViewLayout(mFloatingButton, mLayoutParams);
        break;
    }
default:
        break;
    }

    return false;
}

可以发现,WindowManagerImpl并没有直接实现Window的三大操作,而是全部交给了WindowManagerGlobal来处理WindowManagerGlobal以工厂的形式向外提供自己的实例,在WindowManagerGlobal中有如下一段代码:private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance()WindowManagerImpl这种工作模式是典型的桥接模式,将所有的操作全部委托给WindowManagerGlobal来实现

Window的添加过程

window的添加过程就是通过WindowManagerImpl委托WindowManager-GlobaladdView方法来添加window的过程。

我们首先得有viewWindowManager.LayoutParams对象,才能去创建一个window。下面的代码演示了通过WindowManager添加Window的过程。

Button button = new Button(this);
WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
// 这里对windowParam进行初始化
windowParam.addFlags...
// 获得应用PhoneWindow的WindowManager对象进行添加window
getWindowManager.addView(button,windowParams);

WindowManager-GlobaladdView主要分为如下几步

  • 1.检查参数是否合法,如果是子Window那么还需要调整一些布局参数

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // 首先判断参数是否合法
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }
        
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        // **如果不是子window,会对其做参数的调整,**这个好理解,子window要跟随父window的特性。
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }
    
    		synchronized (mLock) {
            ...                              
            // 这里新建了一个viewRootImpl,并设置参数
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
    
            // 添加到windowManagerGlobal的三个重要list中,后面会讲到
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
    
            // 最后通过viewRootImpl来添加window
            try {
                root.setView(view, wparams, panelParentView);
            } 
            ...
        }  
    }
    
  • 2.创建ViewRootImpI并,并把viewviewRootImplparams三个对象添加到三个list中进行保存

    WindowManagerGlobal内部有如下几个列表比较重要:

    //mViews存储的是所有Window所对应的View
    private final ArrayList<View> mViews = new ArrayList<View>();
    //mRoots存储的是所有Window所对应的ViewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList <ViewRootImpl>();
    //mParams存储的是所有Window所对应的布局参数
    private final ArrayList<WindowManager.LayoutParams> mParams =
          new ArrayList<WindowManager.LayoutParams>();
    //mDyingViews则存储了那些正在被删除的View对象,或者说是那些已经调用removeView方法但是删除操作还未完成的Window对象。
    private final ArraySet<View> mDyingViews = new ArraySet<View>();
    
    每一个window所对应的这三个对象都会保存在这里,之后对window的一些操作就可以直接来这里取对象了。当window被删除的时候,这些对象也会被从list中移除。
    

    ViewRootImplview的最高层级,属于所有View的根,但它不是View,实现了viewParent接口,控件的测量、布局、绘制以及输入事件的派发处理都由ViewRootImpl触发,是view和windowManager的通信桥梁。viewRootImpl可以处理两边的对象,然后联结起来

  • 3.通过ViewRootImpI更新界面并完成Window的添加过程。
    这个步骤由ViewRootImpl的setView方法来完成View的绘制过程是由ViewRootImpl来完成的,这里当然也不例外。

    
    synchronized (mLock) {
        ...                              
        // 这里新建了一个viewRootImpl,并设置参数
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
    
        // 添加到windowManagerGlobal的三个重要list中,后面会讲到
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    
        // 最后通过viewRootImpl来添加window
        try {
            root.setView(view, wparams, panelParentView);
        } 
        ...
    }  
    

addView方法小结

  • 首先对参数的合法性进行检查
  • 然后判断该window是不是子window,如果是的话需要对window进行调整,这个好理解,子window要跟随父window的特性。
  • 接着新建viewRootImpl对象,并把viewviewRootImplparams三个对象添加到三个list中进行保存
  • 最后通过viewRootImpl来进行添加
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
				...
				// 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();
        ...
        try {
            mOrigWindowType = mWindowAttributes.type;
            mAttachInfo.mRecomputeGlobalAttributes = true;
            collectViewAttributes();
            // 这里调用了windowSession的方法,调用wms的方法,把**添加window的逻辑交给wms**
            res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                    getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                    mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                    mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                    mTempInsets);
            setFrame(mTmpFrame);
        } 
        ...
    }
}

viewRootImpl的逻辑很多,重要的就是调用了mWindowSession的方法调用了WindowManagerService的方法。mWindowSession是一个IWindowSession类型对象,IWindowSession是一个IBinder接口,他的具体实现类在WindowManagerService,本地的mWindowSession只是一个Binder对象,通过这个mWindowSession就可以直接调用WindowManagerService的方法进行跨进程通信
那这个mWindowSession是从哪里来的呢?我们到viewRootImpl的构造器方法中看一下:

public ViewRootImpl(Context context, Display display) {
	...
 	mWindowSession = WindowManagerGlobal.getWindowSession();
 	...
}

可以看到这个session对象是来自WindowManagerGlobal。再深入看一下:

public static IWindowSession getWindowSession() {
 synchronized (WindowManagerGlobal.class) {
     if (sWindowSession == null) {
         try {
             ...
             sWindowSession = windowManager.openSession(
                     new IWindowSessionCallback.Stub() {
                         ...
                     });
         } 
         ...
     }
     return sWindowSession;
 }
}

这熟悉的代码格式,可以看出来这个session是一个单例,也就是整个应用的所有viewRootImplwindowSession都是同一个,也就是一个应用只有一个windowSessionWindowManagerService服务于多个应用。WindowManagerService的对象单位是应用,他在内部给每个**应用的session**分配了一些数据结构如list,用于保存每个应用的window以及对应的viewRootImpl。当需要操作view的时候,通过session直接找到viewRootImpl就可以操作了。

后面的逻辑就交给WindowManagerService去处理了,WindowManagerService就会创建window,然后结合参数计算window的高度等等,最后使用viewRootImpl进行绘制。这后面的代码逻辑就不讲了,这是深入到WindowManagerService的内容。

最后做个总结:

window的添加过程是通过PhoneWindow对应的WindowManagerImpl来添加window,内部会调用WindowManagerGlobal来实现。WindowManagerGlobal会使用viewRootImpl来进行跨进程通信让WindowManagerService执行创建window的业务。每个应用都有一个windowSession,用于负责和WindowManagerService的通信,如ApplicationThread与AMS的通信。

在**setView内部会通过requestLayout来完成异步刷新请求**。在下面的代码中,scheduleTraversals实际是View绘制的入口:

public void requestLayout() {
    if (! mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();//View绘制的入口
    }
}

接着会通过WindowSession最终来完成Window的添加过程。在下面的代码中

try {
    mOrigWindowType = mWindowAttributes.type;
    mAttachInfo.mRecomputeGlobalAttributes = true;
    collectViewAttributes();
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
            getHostVisibility(), mDisplay.getDisplayId(),
            mAttachInfo.mContentInsets, mInputChannel);
} catch (RemoteException e) {
    mAdded = false;
    mView = null;
    mAttachInfo.mRootView = null;
    mInputChannel = null;
    mFallbackEventHandler.setView(null);
    unscheduleTraversals();
    setAccessibilityFocus(null, null);
    throw new RuntimeException("Adding window failed", e);
}

Session内部会通过WindowManagerService来实现Window的添加,代码如下所示。

public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams
attrs,
        int viewVisibility, int displayId, Rect outContentInsets,
        InputChannel outInputChannel) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility,
    displayId,
            outContentInsets, outInputChannel);
}

如此一来,Window的添加请求就交给WindowManagerService去处理了,在**Window-ManagerService内部会为每一个应用保留一个单独的Session**。

Window的删除过程

Window的删除过程和添加过程一样,都是先通过WindowManagerImpl,再通过WindowManagerGlobal来实现的。下面是WindowManagerGlobalremoveView的实现:

public void removeView(View view, boolean immediate) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }

    synchronized (mLock) {
        int index = findViewLocked(view, true);
        View curView = mRoots.get(index).getView();
        removeViewLocked(index, immediate);
        if (curView == view) {
            return;
        }

        throw new IllegalStateException("Calling with view " + view
                + " but the ViewAncestor is attached to " + curView);
    }
}

removeView的逻辑很清晰,首先通过findViewLocked来查找待删除的View的索引,这个查找过程就是建立的数组遍历,然后再调用removeViewLocked来做进一步的删除,如下所示。

    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();
          if (view ! = null) {
              InputMethodManager imm = InputMethodManager.getInstance();
              if (imm ! = null) {
                  imm.windowDismissed(mViews.get(index).getWindowToken());
              }
          }
          boolean deferred = root.die(immediate);
          if (view ! = null) {
              view.assignParent(null);
              if (deferred) {
                  mDyingViews.add(view);
              }
          }
      }

removeViewLocked是通过ViewRootImpl来完成删除操作的。在WindowManager中提供了两种删除接口removeViewremoveViewImmediate,它们分别表示异步删除和同步删除,其中一般来说不需要使用removeViewImmediate来删除Window以免发生意外的错误
异步删除**ViewRoot-Impldie方法来执行。在异步删除的情况下,die方法只是发送了一个请求删除的消息后就立刻返回了,这个时候View并没有完成删除操作,所以最后会将待删除的View添加到**mDyingViews中。
ViewRootImpl的die方法:

boolean die(boolean immediate) {
    // Make sure we do execute immediately if we are in the middle of a traversal
      or the damage
    // done by dispatchDetachedFromWindow will cause havoc on return.
    if (immediate && ! mIsInTraversal) {
        doDie();
        return false;
    }

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

在die方法内部只是做了简单的判断,如果是异步删除就发送一个MSG_DIE的消息ViewRootImpl中的Handler会处理此消息并调用doDie方法,如果是同步删除(立即删除),就不发消息直接调用doDie方法。在doDie内部会调用此ViewRootImpldispatchDetachedFromWindow方法,真正删除View的逻辑dispatchDetachedFromWindow方法的内部实现。
dispatchDetachedFromWindow方法主要做四件事:

  • (1)垃圾回收相关的工作,比如清除数据和消息、移除回调
  • (2)通过Sessionremove方法删除WindowmWindowSession.remove(mWindow),这同样是一个IPC过程,最终会调用WindowManagerServiceremoveWindow方法。
  • (3)调用View的dispatchDetachedFromWindow方法,在内部会调用View的onDetached-FromWindow()以及onDetachedFromWindowInternal()。当ViewWindow中移除时onDetachedFromWindow()也会被调用,可以在这个方法内部做一些资源回收的工作,比如终止动画、停止线程等。
  • (4)调用WindowManagerGlobaldoRemoveView方法刷新数据,包括mRoots、mParams以及mDyingViews,需要将当前Window所关联的这三类对象从列表中删除。

Window的更新过程

WindowManagerGlobalupdateViewLayout方法,如下所示。

    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.Layout-
          Params)params;

          view.setLayoutParams(wparams);

          synchronized (mLock) {
              int index = findViewLocked(view, true);
              ViewRootImpl root = mRoots.get(index);
              mParams.remove(index);
              mParams.add(index, wparams);
              root.setLayoutParams(wparams, false);
          }
      }

updateViewLayout方法中,首先它需要更新ViewLayoutParams并替换掉老的LayoutParams,接着再更新ViewRootImpl中的LayoutParams,这一步是通过ViewRootImplsetLayoutParams方法来实现的。在ViewRootImpl中会通过scheduleTraversals方法来对View重新布局,包括测量、布局、重绘这三个过程。除了View本身的重绘以外,ViewRootImpl还会通过WindowSession来更新Window的视图,这个过程最终是由WindowManagerServicerelayoutWindow()来具体实现的,它同样是一个IPC过程。

参考资料

Android开发艺术探索

https://blog.csdn.net/weixin_43766753/article/details/108350589

 类似资料: