结合 Android 源码(基于 AOSP Android 12),详细剖析 Window 的创建、管理、层级、事件分发和绘制原理。同时分析常见问题(如窗口焦点丢失、屏幕适配问题)及其底层原因,提供优化方案和代码示例。
一、Window 机制概述
在 Android 系统中,Window 是显示内容的抽象概念,负责管理 UI 渲染和输入事件的分发。Window 机制涉及以下核心组件:
- WindowManagerService (WMS): 系统服务,运行在 System Server 进程,负责窗口的创建、层级管理和事件分发。
- ViewRootImpl: 应用进程中 Window 的管理实体,协调 View 树的绘制和事件处理。
- WindowState: WMS 内部表示窗口的对象,记录窗口的状态和属性。
- InputChannel: 用于窗口与 InputManagerService (IMS) 的事件通信通道。
Window 的类型包括:
- 应用窗口: 由 Activity 创建,显示应用 UI(如 Z-order 0~99)。
- 子窗口: 依附于其他窗口(如 Dialog、PopupWindow,Z-order 1000~1999)。
- 系统窗口: 由系统创建(如状态栏、导航栏、Toast,Z-order 2000~2999)。
Window 机制的核心功能:
- 窗口创建与管理: 创建窗口并设置属性(如大小、位置、层级)。
- 视图绘制: 将 View 树渲染到窗口的 Surface。
- 事件分发: 将输入事件分发到目标窗口。
- 层级管理: 维护窗口的 Z-order,确保正确叠放。
二、Window 机制的底层原理
以下按 Window 的生命周期和功能,结合源码详细讲解。
1. 窗口的创建与初始化
核心组件
- WindowManagerService (WMS):
- 运行在 System Server 进程,管理所有窗口的状态和层级。
- 提供
IWindowManagerBinder 接口,供应用进程调用。
- WindowManager:
- 应用进程的接口,代理 WMS 的功能(如
addView、removeView)。 - 实现类为
WindowManagerImpl,通过IWindowSession与 WMS 通信。
- 应用进程的接口,代理 WMS 的功能(如
- ViewRootImpl:
- 每个窗口的根节点,管理 View 树的绘制和事件处理。
- 通过
IWindowBinder 接口与 WMS 通信。
创建流程
以 Activity 窗口为例,剖析窗口创建:
-
Activity 启动:
ActivityThread调用performLaunchActivity,创建 Activity 实例。- Activity 调用
setContentView,触发窗口创建。
-
Window 初始化:
- Activity 创建
PhoneWindow(Window的实现类),管理窗口的 DecorView。 setContentView将布局添加到 DecorView 的内容区域。- 源码(
Activity.java):public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); // 调用 PhoneWindow initWindowDecorActionBar(); }// PhoneWindow.java @Override public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); // 创建 DecorView } mLayoutInflater.inflate(layoutResID, mContentParent); // 加载布局 }
- Activity 创建
-
ViewRootImpl 创建:
- Activity 调用
ActivityThread.handleResumeActivity,触发窗口绑定。 WindowManagerImpl创建ViewRootImpl,并调用setView将 DecorView 绑定到窗口。- 源码(
ActivityThread.java):public void handleResumeActivity(IBinder token, boolean finalStateRequest) { ActivityClientRecord r = performResumeActivity(token, finalStateRequest); if (r.window == null) { WindowManager wm = r.activity.getWindowManager(); wm.addView(r.activity.mDecor, r.activity.getWindow().getAttributes()); } }// ViewRootImpl.java public void setView(View view, WindowManager.LayoutParams attrs) { mView = view; // 设置 DecorView requestLayout(); // 触发绘制 mWindowSession.addToDisplay(mWindow, mWindowAttributes); // 调用 WMS }
- Activity 调用
-
WMS 添加窗口:
- WMS 接收
IWindowSession.addToDisplay请求,创建WindowState。 - WMS 为窗口分配 Surface(通过 SurfaceFlinger)和
InputChannel(通过 IMS)。 - 源码(
WindowManagerService.java):public int addWindow(Session session, IWindow client, WindowManager.LayoutParams attrs) { WindowState win = new WindowState(this, session, client, attrs); // 创建 WindowState mWindowMap.put(client.asBinder(), win); // 添加到窗口映射 win.attach(); // 分配 Surface 和 InputChannel return win.mAttrs.type; }
- WMS 接收
-
Surface 分配:
- WMS 通过 SurfaceFlinger 创建 Surface,绑定到窗口的
SurfaceControl。 - Surface 是一个硬件缓冲区,用于渲染窗口内容。
- WMS 通过 SurfaceFlinger 创建 Surface,绑定到窗口的
WindowState 的作用
- WindowState 是 WMS 内部表示窗口的实体,包含:
- 窗口属性(
WindowManager.LayoutParams):大小、位置、类型、Z-order。 - Surface 信息:渲染目标。
- InputChannel:事件通信通道。
- 焦点状态:是否接收输入事件。
- 窗口属性(
- WMS 通过
WindowMap(HashMap)管理所有 WindowState。
2. 窗口的层级管理(Z-order)
原理
- WMS 根据窗口类型和
WindowManager.LayoutParams.type确定 Z-order。 - Z-order 决定了窗口的叠放顺序,值越大越靠前(靠近用户)。
- 窗口类型:
- 应用窗口:
TYPE_APPLICATION(0~99)。 - 子窗口:
TYPE_APPLICATION_PANEL、TYPE_TOAST(1000~1999)。 - 系统窗口:
TYPE_STATUS_BAR、TYPE_NAVIGATION_BAR(2000~2999)。
- 应用窗口:
源码分析
- WMS 在添加窗口时,根据类型分配 Z-order:
// WindowManagerService.java private int findWindowOffset(Session session, WindowManager.LayoutParams attrs) { int type = attrs.type; if (type >= FIRST_SYSTEM_WINDOW && type <= LAST_SYSTEM_WINDOW) { return computeSystemWindowZ(type); // 系统窗口 } else if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) { return computeSubWindowZ(type); // 子窗口 } else { return computeAppWindowZ(); // 应用窗口 } }
焦点管理
- WMS 维护当前焦点窗口(
mCurrentFocus),决定哪个窗口接收输入事件。 - 焦点切换由 WMS 根据用户操作(如点击)或系统事件(如屏幕旋转)触发。
- 源码:
// WindowManagerService.java void assignWindowFocus(WindowState newFocus) { if (mCurrentFocus != newFocus) { if (mCurrentFocus != null) { mCurrentFocus.reportFocusChanged(false); // 通知失去焦点 } mCurrentFocus = newFocus; if (newFocus != null) { newFocus.reportFocusChanged(true); // 通知获得焦点 } } }
3. 窗口的视图绘制
原理
- 窗口的内容由 View 树渲染到 Surface。
- ViewRootImpl 负责协调 View 树的测量、布局和绘制。
- 绘制流程:
- 测量(Measure): 计算 View 的大小。
- 布局(Layout): 确定 View 的位置。
- 绘制(Draw): 将 View 渲染到 Canvas(绑定到 Surface)。
绘制流程
-
请求绘制:
- 调用
ViewRootImpl.requestLayout或invalidate触发绘制。 - 源码(
ViewRootImpl.java):public void requestLayout() { scheduleTraversals(); // 调度绘制 } void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } }
- 调用
-
执行绘制:
ViewRootImpl.performTraversals执行测量、布局和绘制。- 源码:
private void performTraversals() { performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); performLayout(lp, desiredWindowWidth, desiredWindowHeight); performDraw(); } private void performDraw() { Surface surface = mSurface; Canvas canvas = surface.lockCanvas(null); // 获取 Canvas mView.draw(canvas); // 绘制 View 树 surface.unlockCanvasAndPost(canvas); // 提交绘制 }
-
SurfaceFlinger 合成:
- SurfaceFlinger 将窗口的 Surface 内容合成为最终画面,显示到屏幕。
- 源码(
SurfaceFlinger.cpp):void SurfaceFlinger::commitTransactions() { for (const auto& layer : mLayers) { layer->commitBuffer(); // 提交 Surface 缓冲区 } mHwc->present(); // 显示到屏幕 }
4. 窗口的事件分发
原理
- 窗口通过
InputChannel接收 InputManagerService (IMS) 分发的事件。 - ViewRootImpl 将事件分发到 View 树(参考上一问题中的事件分发机制)。
- 焦点窗口优先接收事件,非焦点窗口可能忽略事件。
源码分析
-
ViewRootImpl 接收事件:
// ViewRootImpl.java private final class WindowInputEventReceiver extends InputEventReceiver { @Override public void onInputEvent(InputEvent event) { enqueueInputEvent(event); // 加入事件队列 } } void enqueueInputEvent(InputEvent event) { QueueInputEvent queuedInputEvent = new QueueInputEvent(event); mPendingInputEventQueue.add(queuedInputEvent); doProcessInputEvents(); // 处理事件 } -
WMS 分发事件到焦点窗口:
// WindowManagerService.java void dispatchInputEvent(InputEvent event) { WindowState focus = mCurrentFocus; if (focus != null && focus.mInputChannel != null) { focus.mInputChannel.sendEvent(event); // 发送事件 } }
三、常见问题与底层分析
1. 窗口焦点丢失
问题本质
- 原因:
- 新窗口(如 Dialog、系统窗口)抢占焦点,导致当前窗口失去输入能力。
- WMS 的焦点管理逻辑基于 Z-order 和窗口类型,系统窗口优先级更高。
- 源码分析:
如果窗口设置了// WindowManagerService.java boolean canReceiveInput(WindowState win) { return (win.mAttrs.flags & FLAG_NOT_FOCUSABLE) == 0 && win.isVisible(); }FLAG_NOT_FOCUSABLE或不可见,将无法接收事件。
解决方案
- 强制请求焦点:
- 使用
requestFocus或设置FLAG_NOT_FOCUSABLE为 false。 - 示例:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); findViewById(R.id.root_view).requestFocus(); } }
- 使用
- 调整窗口类型:
- 确保窗口类型(如
TYPE_APPLICATION) 适合当前场景,避免被系统窗口覆盖。
- 确保窗口类型(如
- 监听焦点变化:
- 使用
ViewTreeObserver检测焦点变化,动态调整。view.getViewTreeObserver().addOnWindowFocusChangeListener(hasFocus -> { if (!hasFocus) { view.requestFocus(); } });
- 使用
2. 屏幕适配问题
问题本质
- 原因:
- 窗口大小和屏幕分辨率不匹配,导致布局异常(如拉伸、裁剪)。
- WMS 根据
WindowManager.LayoutParams和设备配置(Configuration)计算窗口大小。
- 源码分析:
// WindowState.java void computeFrameLw() { Rect frame = mAttrs.frame; if (mAttrs.width == LayoutParams.MATCH_PARENT) { frame.right = mDisplayFrame.right; // 适配屏幕宽度 } // 其他计算 }
解决方案
- 动态调整布局:
- 使用
ConstraintLayout或dp单位,确保布局自适应。 - 示例:
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" android:text="Adaptive Text" /> </androidx.constraintlayout.widget.ConstraintLayout>
- 使用
- 处理配置变化:
- 在
AndroidManifest.xml处理屏幕大小变化:<activity android:name=".MainActivity" android:configChanges="screenSize|orientation" />@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // 动态调整布局 setContentView(R.layout.activity_main); }
- 在
- WindowManager 参数调整:
- 设置合适的
LayoutParams:WindowManager.LayoutParams params = getWindow().getAttributes(); params.width = WindowManager.LayoutParams.MATCH_PARENT; params.height = WindowManager.LayoutParams.WRAP_CONTENT; getWindow().setAttributes(params);
- 设置合适的
3. 窗口泄漏
问题本质
- 原因:
- 窗口未正确移除(如 Dialog 未 dismiss),导致
WindowState和InputChannel未释放。 - Activity 销毁后,WMS 仍持有窗口引用。
- 窗口未正确移除(如 Dialog 未 dismiss),导致
- 源码分析:
// WindowManagerService.java void removeWindow(WindowState win) { mWindowMap.remove(win.mClient.asBinder()); win.destroySurface(); // 销毁 Surface win.mInputChannel.dispose(); // 释放 InputChannel }
解决方案
- 确保窗口移除:
- 在 Activity 的
onDestroy移除窗口。 - 示例:
public class MainActivity extends AppCompatActivity { private Dialog dialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); dialog = new Dialog(this); dialog.show(); } @Override protected void onDestroy() { super.onDestroy(); if (dialog != null && dialog.isShowing()) { dialog.dismiss(); dialog = null; } } }
- 在 Activity 的
- 使用弱引用:
- 对于自定义窗口,使用弱引用避免内存泄漏:
public class CustomWindow { private WeakReference<Context> contextRef; private WindowManager windowManager; private View windowView; public CustomWindow(Context context) { contextRef = new WeakReference<>(context); windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); } public void show() { Context context = contextRef.get(); if (context != null) { windowView = new View(context); WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); windowManager.addView(windowView, params); } } public void dismiss() { if (windowView != null) { windowManager.removeView(windowView); windowView = null; } } }
- 对于自定义窗口,使用弱引用避免内存泄漏:
四、最佳实践与优化
-
窗口类型选择:
- 根据需求选择合适的窗口类型(如
TYPE_APPLICATION、TYPE_TOAST),避免不必要的系统权限。 - 系统窗口(如
TYPE_APPLICATION_OVERLAY)需声明SYSTEM_ALERT_WINDOW权限。
- 根据需求选择合适的窗口类型(如
-
焦点管理:
- 确保窗口具有焦点,设置
FLAG_NOT_FOCUSABLE或FLAG_NOT_TOUCH_MODAL避免干扰其他窗口。 - 动态监听焦点变化,及时恢复焦点。
- 确保窗口具有焦点,设置
-
屏幕适配:
- 使用自适应布局(
ConstraintLayout、dp、sp),支持多分辨率和屏幕方向。 - 处理配置变化,动态加载资源。
- 使用自适应布局(
-
资源清理:
- 在 Activity 或窗口销毁时,调用
removeView或dismiss释放资源。 - 使用弱引用或 LifecycleObserver 管理窗口引用。
- 在 Activity 或窗口销毁时,调用
-
性能优化:
- 减少 View 树复杂度,降低绘制开销。
- 避免频繁调用
requestLayout,使用invalidate局部重绘。
五、代码示例(完整实现)
以下是一个自定义悬浮窗口的示例,展示窗口创建、事件处理和屏幕适配:
import android.content.Context;
import android.graphics.PixelFormat;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
import java.lang.ref.WeakReference;
public class FloatingWindow {
private WeakReference<Context> contextRef;
private WindowManager windowManager;
private View windowView;
private WindowManager.LayoutParams params;
private float lastX, lastY;
public FloatingWindow(Context context) {
contextRef = new WeakReference<>(context);
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
initWindow();
}
private void initWindow() {
Context context = contextRef.get();
if (context == null) return;
// 创建窗口视图
windowView = new TextView(context);
((TextView) windowView).setText("Floating Window");
windowView.setBackgroundColor(0x80FF0000); // 半透明红色背景
// 设置窗口参数
params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT);
// 自适应屏幕位置
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
params.x = metrics.widthPixels / 4;
params.y = metrics.heightPixels / 4;
// 处理触摸事件(拖动窗口)
windowView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = event.getRawX();
lastY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float dx = event.getRawX() - lastX;
float dy = event.getRawY() - lastY;
params.x += dx;
params.y += dy;
windowManager.updateViewLayout(windowView, params);
lastX = event.getRawX();
lastY = event.getRawY();
break;
}
return true;
}
});
}
public void show() {
Context context = contextRef.get();
if (context != null && windowView != null && windowView.getParent() == null) {
windowManager.addView(windowView, params);
}
}
public void dismiss() {
if (windowView != null && windowView.getParent() != null) {
windowManager.removeView(windowView);
windowView = null;
}
}
}
使用示例:
public class MainActivity extends AppCompatActivity {
private FloatingWindow floatingWindow;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 检查权限(Android 6.0+ 需要 SYSTEM_ALERT_WINDOW)
if (Settings.canDrawOverlays(this)) {
floatingWindow = new FloatingWindow(this);
floatingWindow.show();
} else {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(intent);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (floatingWindow != null) {
floatingWindow.dismiss();
}
}
}
六、总结
Android 的 Window 机制是 UI 渲染和事件分发的核心,由 WindowManagerService、ViewRootImpl 和 WindowState 协同工作:
- WMS 管理窗口的创建、层级和焦点,分配 Surface 和 InputChannel。
- ViewRootImpl 协调 View 树的绘制和事件分发。
- SurfaceFlinger 合成窗口内容,显示到屏幕。
常见问题(如焦点丢失、屏幕适配、窗口泄漏)源于 WMS 的焦点管理、窗口属性配置和资源清理不当。通过合理设置 LayoutParams、动态适配布局、及时移除窗口,可以优化 Window 行为。