【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 实现。

相关推荐
知兀11 小时前
【MybatisPlus】后端用枚举类,数据库用tinyint,存在枚举类型转换
java
StockTV11 小时前
印度股票实时数据 NSE和BSE的实时行情、K 线及指数数据
java·开发语言·spring boot·python
User_芊芊君子11 小时前
【OpenAI 把 AI 玩明白了】:自主推理 + 动态知识图谱,这 4 个技术突破要颠覆行业
java·人工智能·知识图谱
c++之路12 小时前
C++20概述
java·开发语言·c++20
儿歌八万首12 小时前
Jetpack Compose 实战:实现一个动态平滑折线图
android·折线图·compose
Championship.23.2412 小时前
Linux Top 命令族深度解析与实战指南
java·linux·服务器·top·linux调试
橘子海全栈攻城狮12 小时前
【最新源码】养老院系统管理A013
java·spring boot·后端·web安全·微信小程序
逻辑驱动的ken12 小时前
Java高频面试考点18
java·开发语言·数据库·算法·面试·职场和发展·哈希算法
冷雨夜中漫步13 小时前
Claude Code源码分析——Claude Code Agent Loop 详细设计文档
java·开发语言·人工智能·ai
直奔標竿13 小时前
Java开发者AI转型第二十六课!Spring AI 个人知识库实战(五)——联网搜索增强实战
java·开发语言·人工智能·spring boot·后端·spring