尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从一个窗口覆盖问题出发,理解 Android Window 的层级与权限

从一个窗口覆盖问题出发,理解 Android Window 的层级与权限 一、引子一个“似懂非懂”的起点描述最初的问题遇到窗口问题想搞清楚 Display/Window/Activity 的关系。首先通过dumpsys window containers可以看到 Android 的层级树最外层是一个 Display 0Display 中有孩子节点如Leaf:36:36#1 HideDisplayCutout:32:35#0 WindowedMagnification:然后在WindowedMagnification:中多层递进之下来到它的孩子辈#0 DefaultTaskDisplayArea。这里是管理 Task 的然后 Task 是可以嵌套的(这不是重点)在 Task 中通过 ActivityRecord 来管理一个个 Activity。Android 并不是简单地维护一个 Window 列表而是维护了一棵 WindowContainer 树。shennong:/ $ dumpsys window containersROOT typeundefined modefullscreen override-modeundefined requested-bounds[0,0][0,0] bounds[0,0][1080,1920]#0 Display 0name内置屏幕 typeundefined modefullscreen override-modefullscreen requested-bounds[0,0][1080,1920] bounds[0,0][1080,1920]#2 Leaf:36:36typeundefined modefullscreen override-modeundefined requested-bounds[0,0][0,0] bounds[0,0][1080,1920]...#0 WindowToken{e0bd122 android.os.BinderProxydad5eed} typeundefined modefullscreen override-modeundefined requested-bounds[0,0][0,0] bounds[0,0][1080,1920]#0 70c97b3 ScreenDecorOverlay typeundefined modefullscreen override-modeundefined requested-bounds[0,0][0,0] bounds[0,0][1080,1920]#1 HideDisplayCutout:32:35typeundefined modefullscreen override-modeundefined requested-bounds[0,0][0,0] bounds[0,0][1080,1920]...#0 WindowedMagnification:0:31 typeundefined modefullscreen override-modeundefined requested-bounds[0,0][0,0] bounds[0,0][1080,1920]#1 Leaf:3:14typeundefined modefullscreen override-modeundefined requested-bounds[0,0][0,0] bounds[0,0][1080,1920]#0 WindowToken{4b4dee7 android.os.BinderProxy9c397e9} typeundefined modefullscreen override-modeundefined requested-bounds[0,0][0,0] bounds[0,0][1080,1920]#0 cb2fe01 ShellDropTarget typeundefined modefullscreen override-modeundefined requested-bounds[0,0][0,0] bounds[0,0][1080,1920]#0 DefaultTaskDisplayAreatypeundefined modefullscreen override-modeundefined requested-bounds[0,0][0,0] bounds[0,0][1080,1920]#3 Task1typehome modefullscreen override-modeundefined requested-bounds[0,0][0,0] bounds[0,0][1080,1920]#0 Task2typehome modefullscreen override-modeundefined requested-bounds[0,0][0,0] bounds[0,0][1080,1920]#0 ActivityRecord{99abbcd u0 app.lawnchair/.LawnchairLauncher t2} typehome modefullscreen override-modeundefined requested-bounds[0,0][0,0] bounds[0,0][1080,1920]...#0 Task5typeundefined modemulti-window override-modemulti-window requested-bounds[0,0][0,0] bounds[0,0][1080,1920]此dumpsys window container代码在WindowManagerService.java中。大致调用链路如下这里mRoot是一个根节点frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) ... else if (containers.equals(cmd))) { mRoot.dumpChildrenNames(pw, ); // ← 打印整棵窗口树 pw.println( ); mRoot.forAllWindows(w - {pw.println(w);}, true); // ← 再打印所有 WindowState }如果想进一步理清上面节点的关系可以去浏览一下这个博主的文章。二、窗口层级的理论模型从 WMS 的 WindowContainer 树来看可以先用下面这个简化模型理解应用窗口和系统窗口的组织方式因为不同 Android 版本、不同 Window 类型的实际组织关系会有差异。这里提到一个基类 WindowContainer 是一个泛型很多类通过继承它来管理孩子节点。如果从 WMS 管理具体 Window 的角度来看可以先把WindowState理解为 WMS 对一个 Window 的服务端表示那在窗口中的一个个组件如 layout、text、button 又属于上面层级中的哪一个部分呢UI 中的Layout、TextView、Button等属于 View 层级它们由ViewRootImpl作为 View 树的根节点参与管理。ViewRootImpl是应用侧 View 系统与 WMS 之间的重要桥梁。因此Button、TextView等 View 并不是 WMS 直接管理的 Window它们存在于某个 Window 内部的 View Tree 中。可以参考博主文章。三、Android 中窗口是怎么显示的这里引入一个层级的关系也就是一个窗口除去 x、y 轴的平面实际上是一个个窗口的累加这个一个个窗口就是 z 轴。Window Type 可以理解为 WMS 划分窗口层级的重要依据之一。不同 Type 位于不同的层级区间但窗口最终的相对层级并不是简单地由 Type 数值大小决定还会受到 WindowContainer 层级、Token 以及系统策略等因素影响。这里可以通过dumpsys window windows输出解读重点看mAttrs里的ty字段。这里的ty字段后面的NAVIGATION_BAR_PANEL字段就是一个 Type。Type 类型定义在frameworks/base/core/java/android/view/WindowManager.java中。可以看到显示如下也就是说这个窗口的 type 是 2024。Window Type是 WMS 判断窗口层级的重要依据之一。不同 Type 被划分到不同的窗口层级区间WMS 会结合 Type、WindowContainer 层级、Token 以及系统策略等因素确定窗口的最终层级。/** * Start of system-specific window types. These are not normally * created by applications. */ public static final int FIRST_SYSTEM_WINDOW 2000; /** Window type: Navigation bar panel (when navigation bar is distinct from status bar) In multiuser systems shows on all users windows. hide */ public static final int TYPE_NAVIGATION_BAR_PANEL FIRST_SYSTEM_WINDOW 24;四、回头再看几个关键类这些问题属于进一步阅读 WMS 源码时比较容易遇到的概念本文先不展开只保留几个入口后续可以单独分析也建议直接参考下面引用的博主的文章。RootWindowContainer 为什么是根继承链ConfigurationContainer → WindowContainer → RootWindowContainerDisplayContent 的真实孩子和各个 Android 版本有关这里可以参考这个博主的一系列文章自己对照源码梳理。ActivityRecord extends WindowToken 意味着什么一个 Activity 为什么有多个 WindowStateDialog/PopupWindow 都是独立 WindowState到这里可以先得到一个结论Window 能不能显示在另一个 Window 上面不是单纯由应用层 View 决定的。View 属于某个 Window而 Window 本身由 WMS 管理。WMS 会结合 Window Type、WindowContainer 层级、Token 和权限等因素决定窗口是否能够被创建以及处于什么层级。五、窗口类型type的设计哲学源码在frameworks/base/core/java/android/view/WindowManager.java中三大区间应用窗口1~99子窗口1000~1999系统窗口2000~2999/** * End of types of system windows. */ public static final int LAST_SYSTEM_WINDOW 2999;那如果一个应用希望创建一个能够显示在其他普通应用之上的窗口需要满足什么条件对于一些系统级、高层级窗口调用方还需要具备相应的系统权限。WMS 在addWindow()过程中会根据窗口 Type、调用者权限、Token 等条件进行检查权限不足时会直接拒绝窗口创建。一个应用想创建一个能够显示在其他应用之上的窗口需要同时满足窗口类型和系统权限/策略的要求。例如普通应用的悬浮窗口可以使用 TYPE_APPLICATION_OVERLAY但仍然受到系统权限和 WindowManager 策略限制。而某些系统级窗口则需要更高的系统权限。一个应用层大概这样实现WindowManager.LayoutParams params new WindowManager.LayoutParams( WRAP_CONTENT, WRAP_CONTENT, TYPE_APPLICATION_OVERLAY, FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); wm.addView(view, params);那这个wm.addView()后续在 Android 中又走什么样的链路呢大致如下不同 Window Type 对调用者的权限要求并不完全相同。例如对于特殊的 Rounded Corner Overlay 窗口WMS 会额外检查调用者是否具有INTERNAL_SYSTEM_WINDOW权限。权限不满足时窗口添加会被拒绝。App: WindowManager.addView() → ViewRootImpl.setView() → Session.addToDisplay() [IPC] (windowmanagerservice) → WMS.addWindow() ├─ 检查窗口添加权限 │ └─ 根据 Window Type、调用者权限以及特殊窗口属性进行检查 │ ├─ 获取 DisplayContent │ └─ 确定窗口属于哪个 Display │ ├─ 查找 / 创建 WindowToken │ ├─ 创建 WindowState │ └─ 加入 WindowContainer 层级 │ └─ 返回添加结果App: WM.addView() → 后续 relayout() → WMS.relayoutWindow() → createSurfaceControl() → SurfaceFlinger.createLayer() → App 绘制 → BufferQueue → SF 合成显示到这里从一个应用调用WindowManager.addView()开始窗口如何进入 WMS、如何参与 Window 层级管理以及最终如何进入 SurfaceFlinger 完成显示整个链路就串起来了。参考文章https://juejin.cn/post/7338808893529423883
返回列表