DecorView添加到Window和直接用WindowManger添加View的差异?

要理解这个问题,我们可以把 Android 系统比作一个 "公寓大楼",用生活化的故事结合源码细节来讲透其中的差异。

先明确几个核心角色(类比)

  • Window(窗户) :每个 App 都像一套房子,Window 就是房子里的 "窗户"------ 是视图展示的载体,所有 View 都必须依附于 Window 才能显示。
  • WindowManager(物业管理员) :负责管理所有窗户的 "物业",决定窗户的位置、大小、层级,以及添加 / 移除窗户的规则。
  • DecorView(主窗帘) :Activity 的 "主窗帘",是整个界面的根视图,包含标题栏、内容区等标准结构。
  • MyView(自定义小挂饰) :我们自己做的小挂饰,想直接挂在窗户上。

故事开始:两种 "挂东西" 的方式

第一种:Activity 的 DecorView 添加到 Window(正规入住流程)

当你买了一套房(启动 Activity),开发商(Android 系统)会给你一套标准配置:

  1. 交房前的准备(Activity 启动流程)

    当 Activity 执行到onCreate时,会调用setContentView,这时候系统会偷偷做几件事:

    • 创建PhoneWindow(Window 的具体实现,相当于给你家装了个标准窗户)。
    • PhoneWindow会创建DecorView(主窗帘),并在里面预留出一个 "内容区"(R.id.content),你在setContentView中传的布局就会被放到这个区域。
      源码片段(PhoneWindow.java):
    java 复制代码
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor(); // 创建DecorView
        }
        // 将布局添加到DecorView的内容区
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
  2. 正式挂主窗帘(DecorView 添加到 Window)

    当 Activity 执行到onResume时,系统会通过ActivityThreadhandleResumeActivity方法,让 Window 把 DecorView 交给物业(WindowManager):

    源码片段(ActivityThread.java):

    java 复制代码
    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        // 拿到Activity的Window
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        final Activity a = r.activity;
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView(); // 获取DecorView
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager(); // 拿到WindowManager
            // 配置Window参数(类型为应用窗口,层级等)
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; // 应用窗口类型
            // 关键:通过WindowManager添加DecorView
            wm.addView(decor, l);
        }
    }

    这一步就像告诉物业:"我家正式入住,麻烦把主窗帘挂上"。物业知道这是 "住户的主窗帘",会给它分配 "应用窗口" 的身份(TYPE_BASE_APPLICATION),享受正规住户的待遇(比如可以和状态栏、输入法交互)。

第二种:直接用 WindowManager 添加 MyView(自己偷偷挂东西)

如果你不想用开发商给的主窗帘,自己做了个小挂饰(MyView),想直接挂在窗户上,流程是这样的:

  1. 找物业申请(获取 WindowManager)

    直接通过getSystemService(WINDOW_SERVICE)拿到物业的联系方式:

    java 复制代码
    WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
  2. 告诉物业挂哪里(配置 LayoutParams)

    你需要自己定义挂饰的位置、大小、层级,比如:

    java 复制代码
    WindowManager.LayoutParams params = new WindowManager.LayoutParams(
        200, 200, // 宽高
        WindowManager.LayoutParams.TYPE_APPLICATION, // 窗口类型(可选多种)
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, // 不获取焦点
        PixelFormat.TRANSLUCENT
    );
    params.x = 100; // x坐标
    params.y = 200; // y坐标
  3. 直接挂上(调用 addView)

    最后让物业把挂饰挂上:

    java 复制代码
    wm.addView(myView, params);

核心差异:两种方式的本质区别

1. "身份" 不同(Window 类型与权限)

  • DecorView :属于TYPE_BASE_APPLICATION(应用窗口),是系统默认给 Activity 的 "正规身份",不需要额外权限,且层级适中(在系统窗口之下,子窗口之上)。

  • 直接添加的 MyView:可以选择多种身份(窗口类型):

    • 若选TYPE_APPLICATION(应用子窗口):必须依附于某个应用窗口(比如 Activity 的 Window),层级比主窗口高。
    • 若选TYPE_STATUS_BAR(系统窗口):层级很高(能覆盖状态栏),但需要申请SYSTEM_ALERT_WINDOW权限(如悬浮窗)。

    就像:DecorView 是 "住户的正规窗帘",而 MyView 可以是 "挂在窗帘前的小挂饰"(子窗口),甚至是 "挂在楼道里的广告牌"(系统窗口,需要特殊许可)。

2. 与 Activity 的 "绑定关系" 不同

  • DecorView :和 Activity 是 "强绑定" 的。Activity 销毁时(onDestroy),系统会自动调用WindowManager.removeViewImmediate(decor),把 DecorView 移除。就像住户搬走,物业会自动拆掉主窗帘。

    源码片段(ActivityThread.java):

    java 复制代码
    private void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
             boolean getNonConfigInstance, String reason) {
         ActivityClientRecord r = mActivities.get(token);
         // 移除DecorView
         if (r.window != null && r.mDecor != null) {
             r.mDecor.setVisibility(View.INVISIBLE);
             r.window.clearContentView();
         }
         // ...
     }
  • 直接添加的 MyView :和 Activity 是 "弱绑定" 的。如果 Activity 销毁时你忘了调用wm.removeView(myView),这个 View 会变成 "僵尸视图"(内存泄漏),即使 Activity 没了,它还会留在屏幕上。就像你搬走了,但自己挂的小挂饰没带走,一直留在窗户上。

3. 系统服务的 "特殊照顾" 不同

  • DecorView:作为 Activity 的主视图,会被系统自动纳入 "生命周期管理":

    • 当 Activity 暂停(onPause),DecorView 会被设置为INVISIBLE
    • 系统会自动处理它与输入法的交互(比如输入框弹出时调整布局)。
    • 会响应系统事件(如屏幕旋转、状态栏下拉)。
  • 直接添加的 MyView :这些 "特殊照顾" 都需要自己处理。比如想让 MyView 在 Activity 暂停时隐藏,必须手动在onPause中调用myView.setVisibility(View.INVISIBLE);想和输入法交互,必须手动配置softInputMode参数。

4. 视图树的 "组织结构" 不同

  • DecorView :是一个标准的 "完整视图树",包含标题栏(ActionBar)、内容区(contentParent)等,系统会自动给它添加很多 "辅助功能"(如事件传递、焦点管理)。

    简化的 DecorView 结构:

    plaintext

    复制代码
    DecorView
    ├─ LinearLayout(垂直方向)
    │  ├─ ActionBar(标题栏)
    │  └─ ContentParent(内容区,放我们setContentView的布局)
  • 直接添加的 MyView:是一个 "独立的小视图",没有这些标准结构,所有功能(如点击事件、布局规则)都需要自己实现。就像主窗帘有完整的轨道和挂钩系统,而自己挂的小挂饰可能只是用一根绳子拴着。

总结:用一句话区分

  • DecorView 添加:是 "系统正规流程",适合作为 Activity 的主界面,享受系统完整服务,与 Activity 生命周期强绑定。

  • 直接用 WindowManager 添加 MyView:是 "灵活自定义方式",适合做悬浮窗、弹窗等独立视图,但需要自己管理生命周期和系统交互。

就像:前者是 "精装修房子,拎包入住",后者是 "毛坯房自己装修,自由但麻烦"。

相关推荐
前行的小黑炭4 分钟前
Android Flow的其他使用:stateIn和冷流(普通Flow)
android·kotlin
tran_sient16 分钟前
【Android】制造一个ANR并进行简单分析
android·anr
前行的小黑炭1 小时前
Android Flow:你真的了解?在工作当中的运用~~通过光照例子来解释一下..
android·kotlin
tangweiguo030519871 小时前
极致效率:用 Copilot 加速你的 Android 开发
android·copilot
阿华的代码王国1 小时前
【Android】Room数据库的使用
android·数据库·room
lichong9511 小时前
【混合开发】Android+Webview+VUE播放视频之视频解析工具mediainfo-Macos
android·macos·架构·vue·音视频·api·postman
翻滚丷大头鱼8 小时前
android View详解—View的刷新流程源码解析
android
zhangphil8 小时前
Android adb shell命令分析应用内存占用
android·adb
漠缠9 小时前
Android AI客户端开发(语音与大模型部署)面试题大全
android·人工智能
Lei活在当下10 小时前
一个基础问题:关于SDK初始化时机的选择
android