要理解这个问题,我们可以把 Android 系统比作一个 "公寓大楼",用生活化的故事结合源码细节来讲透其中的差异。
先明确几个核心角色(类比)
- Window(窗户) :每个 App 都像一套房子,Window 就是房子里的 "窗户"------ 是视图展示的载体,所有 View 都必须依附于 Window 才能显示。
- WindowManager(物业管理员) :负责管理所有窗户的 "物业",决定窗户的位置、大小、层级,以及添加 / 移除窗户的规则。
- DecorView(主窗帘) :Activity 的 "主窗帘",是整个界面的根视图,包含标题栏、内容区等标准结构。
- MyView(自定义小挂饰) :我们自己做的小挂饰,想直接挂在窗户上。
故事开始:两种 "挂东西" 的方式
第一种:Activity 的 DecorView 添加到 Window(正规入住流程)
当你买了一套房(启动 Activity),开发商(Android 系统)会给你一套标准配置:
-
交房前的准备(Activity 启动流程)
当 Activity 执行到
onCreate
时,会调用setContentView
,这时候系统会偷偷做几件事:- 创建
PhoneWindow
(Window 的具体实现,相当于给你家装了个标准窗户)。 PhoneWindow
会创建DecorView
(主窗帘),并在里面预留出一个 "内容区"(R.id.content
),你在setContentView
中传的布局就会被放到这个区域。
源码片段(PhoneWindow.java):
javapublic void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); // 创建DecorView } // 将布局添加到DecorView的内容区 mLayoutInflater.inflate(layoutResID, mContentParent); }
- 创建
-
正式挂主窗帘(DecorView 添加到 Window)
当 Activity 执行到
onResume
时,系统会通过ActivityThread
的handleResumeActivity
方法,让 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),想直接挂在窗户上,流程是这样的:
-
找物业申请(获取 WindowManager)
直接通过
getSystemService(WINDOW_SERVICE)
拿到物业的联系方式:javaWindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
-
告诉物业挂哪里(配置 LayoutParams)
你需要自己定义挂饰的位置、大小、层级,比如:
javaWindowManager.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坐标
-
直接挂上(调用 addView)
最后让物业把挂饰挂上:
javawm.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):
javaprivate 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
。 - 系统会自动处理它与输入法的交互(比如输入框弹出时调整布局)。
- 会响应系统事件(如屏幕旋转、状态栏下拉)。
- 当 Activity 暂停(
-
直接添加的 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:是 "灵活自定义方式",适合做悬浮窗、弹窗等独立视图,但需要自己管理生命周期和系统交互。
就像:前者是 "精装修房子,拎包入住",后者是 "毛坯房自己装修,自由但麻烦"。