aosp新增窗口层级 Type 完整实现方案(有源码)-wms需求和面试题

一、背景需求

经常在公司里面会有添加自定义层级的一个窗口需求,比如公司里面要求你创建一个窗口属于系统的最顶层,或者创建一个窗口位于App之上,StatusBar之下等这样添窗口需求。

也是很多同学在framework系统开发窗口显示高级工程师的面试真题。

在 AOSP 14 窗口层级体系中新增两种窗口类型:

窗口类型 Type 常量值 定位 目标 Policy Layer
TYPE_SYSTEM_TOP 2042 最顶层,完全独立,不隶属于任何现有 Feature 分组 37
TYPE_ABOVE_APPLICATION 2043 应用窗口之上、StatusBar 之下 9

那么这些需求到底应该如何实现呢?

二、AOSP 窗口层级体系原理

这块其实在马哥的wms课程的中已经讲解。

  • WindowManagerPolicy.getWindowLayerFromTypeLw(): 将每个 Window Type 映射为一个 policy layer(小整数)
  • WindowManagerPolicy.getMaxWindowLayer(): 返回最大 layer 号,DisplayArea 为 0~max 每层分配一个 Leaf

三、代码修改详情(4 个文件)

3.1 WindowManager.java --- 窗口类型定义

core/java/android/view/WindowManager.java

新增两个窗口类型常量:

java 复制代码
// --- 插入到 TYPE_STATUS_BAR_ADDITIONAL 之后 ---

/**
 * Window type: a window that displays at the very top of the window hierarchy,
 * above all other system windows (just below the rounded corner overlay).
 * This window type is positioned at the max layer of the current window hierarchy + 1.
 * In multiuser systems shows only on the owning user's screen.
 * @hide
 */
public static final int TYPE_SYSTEM_TOP = FIRST_SYSTEM_WINDOW + 42;  // 2042

/**
 * Window type: a window that displays between the application layer (desktop)
 * and the StatusBar. This window type is positioned at a specified layer,
 * e.g., layer 9, which sits above TYPE_SYSTEM_ALERT and below TYPE_STATUS_BAR.
 * In multiuser systems shows only on the owning user's screen.
 * @hide
 */
public static final int TYPE_ABOVE_APPLICATION = FIRST_SYSTEM_WINDOW + 43;  // 2043

注册到 @WindowType IntDef:

diff 复制代码
              TYPE_STATUS_BAR_ADDITIONAL,
+             TYPE_SYSTEM_TOP,
+             TYPE_ABOVE_APPLICATION
      })

注册到 isSystemAlertWindowType()

diff 复制代码
              case TYPE_VOLUME_OVERLAY:
+             case TYPE_SYSTEM_TOP:
+             case TYPE_ABOVE_APPLICATION:
                  return true;

3.2 WindowManagerPolicy.java --- Policy Layer 映射

services/core/java/com/android/server/policy/WindowManagerPolicy.java

添加 import:

diff 复制代码
+ import static android.view.WindowManager.LayoutParams.TYPE_ABOVE_APPLICATION;
+ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_TOP;

Layer 映射:

diff 复制代码
              case TYPE_POINTER:
                  return  35;
+             case TYPE_SYSTEM_TOP:
+                 return  37;
+             case TYPE_ABOVE_APPLICATION:
+                 return  9;

Bump getMaxWindowLayer()(圆角覆盖层独占 38):

diff 复制代码
      default int getMaxWindowLayer() {
-         return 37;
+         return 38;
      }

3.3 DisplayAreaPolicy.java --- Feature 层级修改

services/core/java/com/android/server/wm/DisplayAreaPolicy.java

添加 import:

diff 复制代码
+ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_TOP;

HideDisplayCutout 排除 TYPE_SYSTEM_TOP:

diff 复制代码
  .all()
  .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL, TYPE_STATUS_BAR,
-         TYPE_NOTIFICATION_SHADE)
+         TYPE_NOTIFICATION_SHADE, TYPE_SYSTEM_TOP)

3.4 PhoneWindowManager.java --- 权限豁免

services/core/java/com/android/server/policy/PhoneWindowManager.java

checkAddPermission() 中添加权限豁免,让新 type 只需 SYSTEM_ALERT_WINDOW(而不是 INTERNAL_SYSTEM_WINDOW 签名权限):

diff 复制代码
- if (appInfo == null || (type != TYPE_APPLICATION_OVERLAY
-         && appInfo.targetSdkVersion >= O)) {
+ if (appInfo == null || ((type != TYPE_APPLICATION_OVERLAY && type != TYPE_SYSTEM_TOP
+         && type != TYPE_ABOVE_APPLICATION) && appInfo.targetSdkVersion >= O)) {

四、修改后的 DisplayArea 层级结构(来自实际 dumpsys)

最顶层的窗口层级结构树的情况

bash 复制代码
ROOT type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
  #1 Display 0 name="Built-in Screen" type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][1440,2960] bounds=[0,0][1440,2960]
   #2 Leaf:37:38 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #2 WindowToken{598e4e1 type=2024 android.os.BinderProxy@7d7e48} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 1a350c7 ScreenDecorOverlayBottom type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #1 WindowToken{eb81f18 type=2024 android.os.BinderProxy@7fef2fb} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 26ebd71 ScreenDecorOverlay type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #0 WindowToken{f6a3469 type=2042 android.os.BinderProxy@d5bb6f0} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 e667eee DemoTopOverlay type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]

在app上面的层级结构树情况

bash 复制代码
#0 HideDisplayCutout:0:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 OneHanded:0:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
      #1 ImePlaceholder:13:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
       #0 ImeContainer type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #0 WindowToken{94294b type=2011 android.os.Binder@a06ca1a} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #0 daed721 InputMethod type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
      #0 FullscreenMagnification:0:12 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
       #2 Leaf:3:12 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #1 WindowToken{6dc97ed type=2038 android.os.BinderProxy@37a6a55} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #0 e252696 ShellDropTarget type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #0 WindowToken{7f7ef38 type=2043 android.os.BinderProxy@9596f9b} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #0 cc41b11 DemoMiddleOverlay type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
       #1 DefaultTaskDisplayArea type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #3 Task=1 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #0 Task=29 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
          #0 ActivityRecord{97232e6 u0 com.android.launcher3/.uioverrides.QuickstepLauncher t29} type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
           #0 1722cc4 com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]

关键点 :TYPE_SYSTEM_TOP (layer 37) 和圆角覆盖层 (layer 38) 合并到同一个 Leaf:37:38 中。这是因为 layer 37 被 3 个 Feature exclude 后,与 layer 38(max 层,同样不在任何 Feature 中)相邻,DisplayAreaPolicyBuilder 算法会自动合并连续的无 Feature 层为单个 Leaf。

五、 Demo App 关键点

Demo 使用原始 int 值

由于 @hide 类型无法在 SDK 中引用,demo 代码使用原始 int 值:

java 复制代码
private static final int TYPE_SYSTEM_TOP = 2042;
private static final int TYPE_ABOVE_APPLICATION = 2043;

添加窗口

java 复制代码
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
        WindowManager.LayoutParams.MATCH_PARENT,
        200,  // height
        type,
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
        PixelFormat.TRANSLUCENT
);
params.gravity = Gravity.TOP;
params.y = yOffset;
windowManager.addView(overlayView, params);

Broadcast 触发

java 复制代码
BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if ("com.example.testae.ADD_TOP".equals(action)) {
            addTopOverlay();
        } else if ("com.example.testae.ADD_MIDDLE".equals(action)) {
            addMiddleOverlay();
        }
    }
};
registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);

运行效果

可以看到两个窗口都位于App之上

最顶层窗口看看是不是在最顶层,覆盖了通知锁屏栏,但是了一个窗口确实位于通知栏下面:

原文地址:

https://mp.weixin.qq.com/s/Uqy8J7MC7OmvGQxXhLd9zA

相关推荐
AI_Auto2 小时前
【智能制造】- APS系列|14 生产计划三层架构:长期、中期、短期
架构·制造
morning_judger3 小时前
Agent系列(一) - Agent系统分层架构
人工智能·架构
跨境数据猎手3 小时前
Superbuy淘宝代购集运系统架构拆解,复刻方案参考
爬虫·架构·系统架构
Rain5094 小时前
mini-cc 的 MCP 协议:给 AI 装个 USB-C 接口
c语言·开发语言·前端·人工智能·架构·node.js·ai编程
雨辰AI5 小时前
SpringBoot3 整合达梦 DM9 超详细入门实战|从零搭建可直接上线
数据库·微服务·架构·政务
辰海Coding5 小时前
MiniSpring框架学习-分解 Dispatcher
java·学习·spring·架构
wb043072016 小时前
厨房装监控——从阿明餐厅的“出餐慢“投诉,看可观测性的三大支柱
架构
喵个咪7 小时前
选择第三方IAM还是自建权限体系?中小型后台系统权限架构决策指南
后端·架构·go
ting94520007 小时前
Ava 2.0 技术架构与核心能力深度解析:自主式 AI BDR 的全链路技术实现
人工智能·架构