深入分析 Android 系统中的Window 机制

470 阅读9分钟

结合 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 机制的核心功能:

  1. 窗口创建与管理: 创建窗口并设置属性(如大小、位置、层级)。
  2. 视图绘制: 将 View 树渲染到窗口的 Surface。
  3. 事件分发: 将输入事件分发到目标窗口。
  4. 层级管理: 维护窗口的 Z-order,确保正确叠放。

二、Window 机制的底层原理

以下按 Window 的生命周期和功能,结合源码详细讲解。

1. 窗口的创建与初始化

核心组件

  • WindowManagerService (WMS):
    • 运行在 System Server 进程,管理所有窗口的状态和层级。
    • 提供 IWindowManager Binder 接口,供应用进程调用。
  • WindowManager:
    • 应用进程的接口,代理 WMS 的功能(如 addViewremoveView)。
    • 实现类为 WindowManagerImpl,通过 IWindowSession 与 WMS 通信。
  • ViewRootImpl:
    • 每个窗口的根节点,管理 View 树的绘制和事件处理。
    • 通过 IWindow Binder 接口与 WMS 通信。

创建流程

以 Activity 窗口为例,剖析窗口创建:

  1. Activity 启动:

    • ActivityThread 调用 performLaunchActivity,创建 Activity 实例。
    • Activity 调用 setContentView,触发窗口创建。
  2. Window 初始化:

    • Activity 创建 PhoneWindowWindow 的实现类),管理窗口的 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); // 加载布局
      }
      
  3. 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
      }
      
  4. 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;
      }
      
  5. Surface 分配:

    • WMS 通过 SurfaceFlinger 创建 Surface,绑定到窗口的 SurfaceControl
    • 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_PANELTYPE_TOAST(1000~1999)。
    • 系统窗口:TYPE_STATUS_BARTYPE_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 树的测量、布局和绘制。
  • 绘制流程:
    1. 测量(Measure): 计算 View 的大小。
    2. 布局(Layout): 确定 View 的位置。
    3. 绘制(Draw): 将 View 渲染到 Canvas(绑定到 Surface)。

绘制流程

  1. 请求绘制:

    • 调用 ViewRootImpl.requestLayoutinvalidate 触发绘制。
    • 源码(ViewRootImpl.java):
      public void requestLayout() {
          scheduleTraversals(); // 调度绘制
      }
      
      void scheduleTraversals() {
          if (!mTraversalScheduled) {
              mTraversalScheduled = true;
              mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
          }
      }
      
  2. 执行绘制:

    • 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); // 提交绘制
      }
      
  3. 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 或不可见,将无法接收事件。

解决方案

  1. 强制请求焦点:
    • 使用 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();
          }
      }
      
  2. 调整窗口类型:
    • 确保窗口类型(如 TYPE_APPLICATION) 适合当前场景,避免被系统窗口覆盖。
  3. 监听焦点变化:
    • 使用 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; // 适配屏幕宽度
        }
        // 其他计算
    }
    

解决方案

  1. 动态调整布局:
    • 使用 ConstraintLayoutdp 单位,确保布局自适应。
    • 示例:
      <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>
      
  2. 处理配置变化:
    • AndroidManifest.xml 处理屏幕大小变化:
      <activity
          android:name=".MainActivity"
          android:configChanges="screenSize|orientation" />
      
      @Override
      public void onConfigurationChanged(Configuration newConfig) {
          super.onConfigurationChanged(newConfig);
          // 动态调整布局
          setContentView(R.layout.activity_main);
      }
      
  3. 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),导致 WindowStateInputChannel 未释放。
    • Activity 销毁后,WMS 仍持有窗口引用。
  • 源码分析:
    // WindowManagerService.java
    void removeWindow(WindowState win) {
        mWindowMap.remove(win.mClient.asBinder());
        win.destroySurface(); // 销毁 Surface
        win.mInputChannel.dispose(); // 释放 InputChannel
    }
    

解决方案

  1. 确保窗口移除:
    • 在 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;
              }
          }
      }
      
  2. 使用弱引用:
    • 对于自定义窗口,使用弱引用避免内存泄漏:
      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;
              }
          }
      }
      

四、最佳实践与优化

  1. 窗口类型选择:

    • 根据需求选择合适的窗口类型(如 TYPE_APPLICATIONTYPE_TOAST),避免不必要的系统权限。
    • 系统窗口(如 TYPE_APPLICATION_OVERLAY)需声明 SYSTEM_ALERT_WINDOW 权限。
  2. 焦点管理:

    • 确保窗口具有焦点,设置 FLAG_NOT_FOCUSABLEFLAG_NOT_TOUCH_MODAL 避免干扰其他窗口。
    • 动态监听焦点变化,及时恢复焦点。
  3. 屏幕适配:

    • 使用自适应布局(ConstraintLayoutdpsp),支持多分辨率和屏幕方向。
    • 处理配置变化,动态加载资源。
  4. 资源清理:

    • 在 Activity 或窗口销毁时,调用 removeViewdismiss 释放资源。
    • 使用弱引用或 LifecycleObserver 管理窗口引用。
  5. 性能优化:

    • 减少 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 渲染和事件分发的核心,由 WindowManagerServiceViewRootImplWindowState 协同工作:

  • WMS 管理窗口的创建、层级和焦点,分配 Surface 和 InputChannel。
  • ViewRootImpl 协调 View 树的绘制和事件分发。
  • SurfaceFlinger 合成窗口内容,显示到屏幕。

常见问题(如焦点丢失、屏幕适配、窗口泄漏)源于 WMS 的焦点管理、窗口属性配置和资源清理不当。通过合理设置 LayoutParams、动态适配布局、及时移除窗口,可以优化 Window 行为。