【Android】动态操作 Window 的背后机制

Window 和 WindowManager

视频通话程序和音乐播放程序能看到的电话或音乐悬浮窗本质都是 Window,我们初始化空项目,应用启动时显示的画面就是 Window,系统状态栏和导航栏则也是 Window,所以单屏幕是可以对应多 Window 的,首先来看动态添加 Window 的过程

java 复制代码
 Button button = new Button(this);
 WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
         LayoutParams.WRAP_CONTENT, 
         LayoutParams.WRAP_CONTENT, 
         0, 0, 
         PixelFormat.TRANSPARENT
 );
 lp.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
         | LayoutParams.FLAG_NOT_FOCUSABLE
         | LayoutParams.FLAG_SHOW_WHEN_LOCKED
 lp.gravity = Gravity.LEFT | Gravity.TOP;
 lp.x = 100;
 lp.y = 100;
 getWindowManager().addView(button, lp);

Window 是承载 View 的工具,所以添加 Window 的方法往往要伴随控件的加入,WindowManager 的 addView 方法会在参数设置的屏幕位置创建 Window,接着把 View 添加进 Window。

注意参数有 flags 和 type 两种属性,它们分别表示 Window 的显示特征和 Window 类型。常用的显示特征在代码块中有所提到:

  1. FLAG_NOT_FOCUSABLE:表示 Window 不需要获取焦点,也就意味着 Window 不需要也无法接收任何事件,事件会传给 z-order 更低且具有焦点的 Window,激活该标记会同时启用 FLAG_NOT_TOUCH_MODAL;
  2. FLAG_NOT_TOUCH_MODAL:添加的 Window 本身在高 z 区域,会默认拦截即使是 Window 外部区域的事件,该标志会将外部区域的事件分发到低 z 值 Window,若在 Window 内部则由自己处理;
  3. FLAG_SHOW_WHEN_LOCKED:表示允许 Window 显示在锁屏界面,常用于闹铃或电话功能。

type 即 Window 的类型:应用 Window、派生 Window 和系统 Window,应用 Window 与 Activity 实例对应;派生 Window 不能单独存在,需要依附在特定的父 Window;系统 Window 要在声明权限后才能创建,Toast 和系统状态栏都是系统 Window。

前面提到的 z-order 就是 Window 的分层策略,层级大的 Window 会覆盖在层级小的 Window 上面,不同的 Window 类型具有不同的层级 z 值范围,应用 Window 的层级范围为 1-99、派生 Window 的层级范围为 1000-1999、系统 Window 的层级范围为 2000-2999。层级值对应 type 参数,显然系统窗口的层级更大。

Window 本身是抽象类,其实现为 PhoneWindow。WindowManagerServer 存在于系统进程中,使用多进程管理当前显示屏幕的所有 Window。WindowManager 用于向屏幕添加 Window,其有 addView、updateViewLayout 和 removeView 等方法。Window 和 View 通过 ViewRootImpl 建立联系,向屏幕添加 Window 时我们也能发现,View 才是 Window 存在的实体,实际使用中无法直接访问 Window。

Window 的内部机制

addView

Window 的添加过程由 WindowManager 接口的实现类 WindowManagerImpl 实现,其直接转交给 WindowManagerGlobal 处理,removeView 和 updateViewLayout 也是相同的逻辑。

java 复制代码
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
    mGlobal.addView(view, params, mDisplay, mParentWindow);
}

@Override
public void removeView(View view) {
    mGlobal.removeView(view, false);
}

@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    mGlobal.updateViewLayout(view, params);
}

WindowManagerGlobal 的 addView 方法分有 3 步:

  1. 检查参数合法性,若是派生 Window 则根据父 Window 调整布局参数,类似 View 在 ViewGroup 内部的自适应处理;

  2. 创建 ViewRootImpl 并将 View 添加到列表,值得注意的是 WindowManagerGlobal 有若干成员列表,其分别是

    1. mViews:存储所有 Window 对应的 View;
    2. mRoots:存储所有 Window 对应的 ViewRootImpl;
    3. mParams:存储所有 Window 对应的布局参数;
    4. mDyingViews:存储正在被删除的 View 实例,可以认作已经调用 removeView 方法但删除操作还未完成的 Window 对象。
  3. 借助 ViewRootImpl 的 setView 方法更新界面并完成 Window 的添加过程。setView 会通过 requestLayout 完成异步刷新请求。

交由 ViewRootImpl 调用 requestLayout 方法后,mWindowSession 执行 Window 的添加过程,mWindowSession 是 Binder 对象,真正的实现类是 Session,最后会由 WindowManagerService 完成 Window 的添加。

removeView

Window 的删除过程也通过 WindowManagerImpl 后接着交由 WindowManagerGlobal 实现,其会做同步块查索引后再调用 removeViewLocked 方法

java 复制代码
/* WindowManagerImpl */
public void removeView(View view, boolean immediate) {
    ...
    
    synchronized (mLock) {
        int index = findViewLocked(view, true);
        View curView = mRoots.get(index).getView();
        removeViewLocked(index, immediate);
        if (curView == view) {
            return;
        }
        
        ...
    }
}

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 (defered) {
            mDyingViews.add(view);
        }
    }
}

removeView 是异步删除,removeViewLocked 方法的处理主要有收起输入法、调用 ViewRootImpl.die 方法和将 View 添加到 mDyingViews 列表。die 方法内部只有简单判断,如果是异步删除则发送 MSG_DIE 消息,ViewRootImpl 的 Handler 会处理此消息并调用 toDie 方法。

最后由 dispatchDetachedFromWindow 方法执行真正的 View 删除逻辑,主要做有 4 件事:

  1. 垃圾回收工作,如清理数据和移除回调等;
  2. 通过 Session 的 remove 方法删除 Window;
  3. 调用 View 的 dispatchDetachedFromWindow 方法,内部会回调可供重写的 onDetachedFromWindow 和 onDetachedFromWindowInternal 方法;
  4. 调用 WindowManagerGlobal 的 doRemoveView 方法刷新数据,清除在列表的记录。

updateViewLayout

Window 的更新过程比较简单,还是由 WindowManagerGlobal 处理,它会更新 View 和 ViewRootImpl 的布局参数。ViewRootImpl 内部会通过 scheduleTraversals 方法对 View 重新布局,还会通过 WindowSession 更新 Window 视图,该过程同样是 IPC 过程,由 WindowManagerService 的 relayoutWindow 实现。

相关推荐
用户90443816324602 小时前
从40亿设备漏洞到AI浏览器:藏在浏览器底层的3个“隐形”原理
前端·javascript·浏览器
小二李2 小时前
第12章 koa框架重构篇 - Koa框架项目重构
java·前端·重构
cike_y2 小时前
JavaBean&MVC三层架构
java·架构·mvc·javaweb·java开发
鸡吃丸子2 小时前
React Native入门详解
开发语言·前端·javascript·react native·react.js
漂亮的小碎步丶2 小时前
【启】Java中高级开发51天闭关冲刺计划(聚焦运营商/ToB领域)
java·开发语言
华锋20222 小时前
2025.12首次体验 arkui-x 跨平台开发库
android
qq_428723242 小时前
英语歌10个月之前启蒙磨耳朵
前端
Hao_Harrision2 小时前
50天50个小项目 (React19 + Tailwindcss V4) ✨ | DrinkWater(喝水记录组件)
前端·react.js·typescript·vite7·tailwildcss
SadSunset2 小时前
(19)Bean的循环依赖问题
java·开发语言·前端