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

相关推荐
ycvv5 小时前
手机启动失败后的数据提取评估:系统状态、加密条件与文件验收
服务器·数据库·智能手机
ESDWAN6 小时前
SD-WAN 企业选型与落地指南:从链路测试到网络架构设计
运维·网络·架构
DianSan_ERP6 小时前
多平台订单自动下载与回传的技术实现:从消息推送到状态闭环引言
java·linux·服务器·前端·网络·架构·自动化
恋猫de小郭6 小时前
Shopify 回应为什么从 RN 回到原生,为什么不用 KMP ?
android·前端·flutter
梦帮科技7 小时前
量子张量网络破局大模型:从矩阵乘积态 (MPS) 到张量列 (TT-SVD) 低秩收缩全推导
网络·数据结构·数据库·线性代数·矩阵·架构·模拟退火算法
霸道流氓气质8 小时前
LLM 应用限流与熔断机制完全指南:从多层防护架构到Java生产级弹性实战
java·开发语言·架构
fundroid8 小时前
Android AI 开发,真正拉开差距的是工程闭环
android·ai·大模型·agent
脚踏实地,坚持不懈!8 小时前
Android 面试题全解(结合 AOSP 源码 · 截至 Android 17 / API 37)
android
Jmyd01238 小时前
元宇宙虚拟校史馆技术方案解析:一个引擎+两大平台架构拆解
架构·三维数字化
ESDWAN9 小时前
外贸企业网络专线选型与落地实战指南
网络·架构