源码探索系列13---Window的PhoneWindow与WindowManager

本文详细探讨了Android中Window、PhoneWindow和WindowManager三者的关系。Window是一个抽象类,PhoneWindow是其具体实现,而WindowManager负责管理Window。文章从WindowManager的addView和removeView方法入手,揭示了添加和移除View的内部流程,涉及到ViewRootImpl、InputMethodManager以及AMS的角色。接着,讨论了PhoneWindow如何根据Activity的需求初始化DecorView并设置内容视图。通过本文,读者能深入了解Android界面显示的底层机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于Window,PhoneWindow和WindowManager三者的关系是:

Window是一个抽象类,他的具体实现是PhoneWindow
我们通过WindowManager来管理Window。

我们的所有的界面,例如Activity,Toast,Dialog等都是靠Window来呈现,因此他是View的管理者咯。

很久前用过他的一个功能就是拿来做消息提醒,当有用户发来特定消息时候,就跑出来一个小悬浮窗来提示用户。现在看得多的就是各种管家关于内存的提示!这真的很让人纠结的安卓机啊!特别是某米的手机,当年发现他不支持这个功能!搞到得单独为他开发一个别的方式….

好了,说这么多,我们该从哪里开始说起呢?

起航

API:23

我们先挑WindowManager 开始吧。

public interface WindowManager extends ViewManager 

我们的WiddowManger是一个接口,不是实际干活的人,看下他继承的ViewManager

public interface ViewManager{ 
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}

定义了我们使用到的三个操作。增加,更新,移除。
那么背后到底谁在干活呢,是WindowManagerImpl
不过很有意思的是,他也是一个壳,实际干活的都交给了WindowManagerGlobal去干了。

public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    @Override
    public void removeView(View view) {
         mGlobal.removeView(view, false);
    }

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }
    ...
}

addView

我们继续跟下,看在addView背后做了什么

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {

     ... 

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {

        ...

        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }
    // do this last because it fires off messages to start doing things
    try {
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        ...
    }
}

private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
        new ArrayList<WindowManager.LayoutParams>();

我们看到他主要是创建了新的ViewRootImpl,然后将View,ViewRootImpl和params保存起来,最后将View添加进去ViewRootImpl里面去。这里有种感觉,removeView做的就可能和上面的相反,从数组移除,然后调用root.removeView()的类似函数去移除。这是猜测,我们先留着,看完我们的主线

我们来看下这个setView里面做了什么

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = 
<think>我们正在讨论Android中DecorView的工作机制及其在Activity生命周期中的加载流程。用户要求结合源码进行解释,特别是DecorView如何管理View的位置和大小以及如何参ViewTree的三大流程(测量、布局、绘制)。 根据提供的引用,我们可以梳理出以下关键点: 1. DecorView是ViewTree的根视图(引用[1])。 2. PhoneWindow持有DecorView(引用[1])。 3. ViewRootImpl是连接WindowManager和DecorView的纽带,负责三大流程(引用[2])。 4. 将DecorView添加到Window时,会创建ViewRootImpl并建立关联(引用[2])。 5. WindowManager.addView()将DecorView和WindowManager关联,并最终触发三大流程(引用[3])。 6. Activity不负责视图控制,而是通过Window(引用[4])。 7. 布局的加载是通过递归解析XML文件完成的(引用[5])。 现在,我们将结合源码详细分析DecorView的创建、加载以及参三大流程的过程。 ### 1. DecorView的创建 在Activity的创建过程中,会调用`setContentView()`方法,该方法最终会调用PhoneWindow的`setContentView()`方法。在PhoneWindow中,会创建DecorView。 **源码分析(基于Android SDK 30):** ```java // PhoneWindow.java @Override public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); // 初始化DecorView } else { mContentParent.removeAllViews(); } mLayoutInflater.inflate(layoutResID, mContentParent); // 将Activity的布局添加到mContentParent } ``` 在`installDecor()`方法中: ```java private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); // 创建DecorView ... } if (mContentParent == null) { mContentParent = generateLayout(mDecor); // 生成布局,并设置ContentView的父容器 ... } } ``` `generateDecor()`方法: ```java protected DecorView generateDecor() { return new DecorView(getContext(), -1); } ``` ### 2. 将布局加载到DecorView 在`generateLayout()`方法中,会根据主题等设置选择不同的窗口布局,然后通过`mDecor.onResourcesLoaded()`加载布局。 ```java protected ViewGroup generateLayout(DecorView decor) { // 根据主题选择布局文件 int layoutResource = R.layout.screen_title; // 例如 mDecor.startChanging(); mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); // 加载布局 ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT); // 获取content区域 ... return contentParent; } ``` 在`DecorView`的`onResourcesLoaded()`方法中,会将布局文件加载到DecorView中,并设置到mContentRoot(即整个DecorView的根布局)中。 ### 3. 将Activity的布局添加到content区域 在`setContentView()`方法中,通过`mLayoutInflater.inflate(layoutResID, mContentParent)`将Activity的布局文件添加到`mContentParent`(即content区域)中。这样,Activity的布局就成为了DecorView中content区域的子视图。 ### 4. ViewRootImpl的创建关联 在Activity的`onResume()`方法之后,ActivityThread会调用`handleResumeActivity()`方法,其中会调用Activity的`makeVisible()`方法,将DecorView添加到WindowManager中。 **源码分析(ActivityThread.handleResumeActivity()):** ```java @Override public void handleResumeActivity(...) { ... final Activity a = r.activity; if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; ... wm.addView(decor, l); // 将DecorView添加到WindowManager } } ``` `WindowManager`的实现是`WindowManagerImpl`,其`addView()`方法会调用到`WindowManagerGlobal`的`addView()`方法: ```java // WindowManagerGlobal.java public void addView(View view, ViewGroup.LayoutParams params, ...) { ... ViewRootImpl root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); root.setView(view, wparams, panelParentView); // 将DecorView设置给ViewRootImpl } ``` 在`ViewRootImpl`的`setView()`方法中,会调用`requestLayout()`来开始视图的三大流程。 ### 5. 三大流程的触发 在`ViewRootImpl.setView()`中: ```java public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ... requestLayout(); // 请求布局 ... } ``` `requestLayout()`方法: ```java @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); // 安排遍历,即开始三大流程 } } ``` `scheduleTraversals()`方法会向主线程消息队列发送一个消息,最终在下一个VSync信号到来时执行`performTraversals()`方法。`performTraversals()`方法包含了测量(measure)、布局(layout)和绘制(draw)三大流程。 ### 6. DecorView如何管理View的位置和大小 在`performTraversals()`方法中: ```java private void performTraversals() { ... // 测量 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... // 布局 performLayout(lp, desiredWindowWidth, desiredWindowHeight); ... // 绘制 performDraw(); } ``` - **测量(measure)**:从DecorView开始,递归调用每个View的`measure()`方法,确定每个View的尺寸。 - **布局(layout)**:从DecorView开始,递归调用每个View的`layout()`方法,确定每个View的位置。 - **绘制(draw)**:从DecorView开始,递归调用每个View的`draw()`方法,将内容绘制到屏幕上。 DecorView作为根视图,是整个ViewTree的起点。在测量过程中,DecorView会根据窗口的尺寸和自身的LayoutParams计算出自己的测量规格,然后传递给子视图(即content区域)。同样,在布局过程中,DecorView会确定content区域的位置和大小,然后content区域再负责布局其子视图。 ### 总结流程 1. **创建DecorView**:在Activity的`setContentView()`中,PhoneWindow创建DecorView。 2. **加载窗口布局**:根据主题选择预定义的窗口布局,加载到DecorView中,并找到content区域(id为android.R.id.content)。 3. **加载Activity布局**:将Activity的布局文件加载到content区域中。 4. **关联WindowManager**:在Activity的`onResume()`之后,将DecorView添加到WindowManager,同时创建ViewRootImpl并建立关联。 5. **触发三大流程**:ViewRootImpl的`requestLayout()`会安排一次遍历,最终执行`performTraversals()`,开始测量、布局和绘制。 这样,DecorView作为整个视图树的根,通过ViewRootImplWindowManager交互,并负责管理其子视图的位置和大小,以及参整个ViewTree的三大流程。 [^1]: 创建DecorView,作为整个ViewTree的RootView [^2]: ViewRootImpl是连接WindowManager和DecorView的纽带 [^3]: WindowManager.addView()将DecorView添加到窗口,并最终触发三大流程 [^4]: Activity通过Window控制视图 [^5]: 布局加载通过递归解析XML完成 §§相关问题§§ 1. DecorView和ViewRootImpl之间的关系是什么? 2. 在Activity的生命周期中,DecorView是在哪个阶段被添加到窗口的? 3. View的三大流程(测量、布局、绘制)是如何从DecorView开始的? 4. 如何自定义DecorView?有什么注意事项? 5. 为什么说DecorView是ViewTree的根视图?</think>### Android Activity生命周期中DecorView的创建加载流程详解 #### 1. **DecorView的创建初始化** 在Activity启动时,系统通过`PhoneWindow`创建`DecorView`作为整个视图树的根容器: ```java // PhoneWindow.java public void installDecor() { if (mDecor == null) { mDecor = generateDecor(); // 创建DecorView实例 mDecor.setWindow(this); } if (mContentParent == null) { mContentParent = generateLayout(mDecor); // 关联布局 } } ``` - **关键步骤**: - `generateDecor()` 实例化`DecorView`(继承`FrameLayout`)[^1] - `generateLayout()` 根据主题加载预定义布局(如`R.layout.screen_simple`),将`android.R.id.content`作为Activity布局的父容器[^1][^4] #### 2. **布局加载挂载** 通过`LayoutInflater`递归解析XML布局: ```java // LayoutInflater.java void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs) { while (parser.next() != XmlPullParser.END_TAG) { final View view = createViewFromTag(parent, name, context, attrs); // 创建View final ViewGroup viewGroup = (ViewGroup) parent; viewGroup.addView(view); // 添加到父容器 rInflateChildren(parser, view, attrs); // 递归加载子View[^5] } } ``` - **流程说明**: - Activity的`setContentView(R.layout.activity_main)`触发布局加载 - 解析后的View树挂载到`DecorView`的`content`区域[^1] - 最终形成完整的ViewTree:`DecorView → 系统布局(如ActionBar) → content → Activity布局` #### 3. **关联WindowViewRootImpl** 在`onResume()`阶段完成关键绑定: ```java // ActivityThread.java public void handleResumeActivity(...) { ViewManager wm = activity.getWindowManager(); wm.addView(decorView, params); // 关联WindowManager[^3] } ``` - **核心对象**: - `ViewRootImpl` 作为`WindowManager`和`DecorView`的纽带[^2] - `WindowManager.addView()` 触发`ViewRootImpl`创建,建立`DecorView``WindowManager`的关联[^3] #### 4. **三大流程的触发执行** `ViewRootImpl`驱动整个ViewTree的测量、布局、绘制: ```java // ViewRootImpl.java public void performTraversals() { performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // 1.测量 performLayout(lp, desiredWindowWidth, desiredWindowHeight); // 2.布局 performDraw(); // 3.绘制 } ``` - **测量流程**: - 从`DecorView`开始递归调用`measure()` - 计算规则:`DecorView`尺寸 = 屏幕尺寸 - 状态栏/导航栏 - **布局流程**: - `DecorView.layout(0, 0, width, height)` 确定自身位置 - 递归调用子View的`layout()`传递位置参数 - **绘制流程**: - `DecorView.draw(canvas)` 先绘制自身背景 - 递归调用子View的`draw()`实现视图渲染 #### 5. **位置尺寸管理机制 - **坐标系转换**: - `DecorView`的坐标系原点`(0,0)`位于屏幕左上角(不包括状态栏) - 子View通过`getLocationOnScreen()`获取绝对坐标 - **尺寸约束**: - `DecorView`接受`WindowManager.LayoutParams`的尺寸规则 - 通过`ViewRootImpl`向WMS(WindowManagerService)申请窗口尺寸[^3] #### 6. **Activity生命周期的联动 | 生命周期阶段 | DecorView相关操作 | |--------------|------------------------------------------| | onCreate() | 创建PhoneWindow和DecorView,加载布局 | | onStart() | DecorView完成attach但不可见 | | onResume() | ViewRootImpl关联DecorView,触发首次绘制 | | onPause() | 暂停绘制,但DecorView仍存在 | | onDestroy() | WindowManager.removeView()销毁DecorView | ### 关键源码总结 1. **创建根源**:`PhoneWindow.generateDecor()` 2. **布局挂载**:`mContentParent.addView(activity_main)` 3. **流程驱动**:`ViewRootImpl.performTraversals()` 4. **尺寸协商**:`ViewRootImpl.relayoutWindow()`WMS通信[^3] DecorView作为Android视图系统的根容器,通过`ViewRootImpl`协调`WindowManager`和`ViewTree`,最终实现测量→布局→绘制的完整渲染流水线[^2][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值