查看原文
其他

一文看懂DecorView的一生

鸿洋
2024-08-30

The following article is from Android补给站 Author Rouse

DecorView是Android应用程序中所有视图的根视图。它是框架用来管理和显示应用程序界面的核心组件之一。理解DecorView的创建流程对于理解Android视图系统的运作方式至关重要。

1简介


DecorView的主要角色是作为顶层容器,承载着应用的视图结构。当在应用中使用setContentView方法加载布局时,实际上是将这个布局作为子视图添加到DecorView中。因此,DecorView定义了应用界面的边界,所有的视图都在这个边界内进行绘制和事件分发。
下面我们来说一下,DecorView与Window、Activity和ViewRootImpl之间的关系,这能够更好地帮助我们理解应用的视图层次结构。

与Window的关系

Window是Android中的一个抽象概念,代表着屏幕上的一块区域,可以用来显示视图。每个Activity都会被赋予一个Window,而这个Window则负责承载DecorView。简单来说,Window是一个显示DecorView的容器。在Android中,Window和View通过WindowManager服务来管理,WindowManager负责将Window(及其包含的DecorView)放置到屏幕上的正确位置。

与Activity的关系

Activity是Android应用中的一个基本组件,负责创建用户界面。每个Activity都会有一个与之关联的Window,而这个Window则承载着DecorView。在Activity的生命周期中,当调用setContentView方法时,系统就会开始构建视图层次结构,将指定的布局文件加载到当前Activity的Window所关联的DecorView中。

与ViewRootImpl的关系

ViewRootImpl是Android UI系统的内部机制,作为桥梁连接Window和DecorView。它负责初始化视图层次结构的根,处理布局、绘制、事件分发等。当一个Activity的视图被设置或者窗口发生变化时,ViewRootImpl确保DecorView得到更新和重新绘制。ViewRootImpl是不对开发者公开的,但它在视图渲染和事件处理过程中起着关键作用。

2创建流程


DecorView的创建通常在Activity的生命周期的onCreate方法中开始,具体是通过调用setContentView方法触发的。
当Activity的setContentView方法被调用时,背后的LayoutInflater就开始发挥作用。这个方法接受一个布局资源ID,然后LayoutInflater负责找到对应的布局文件,解析它,并根据文件中的定义构建出一个完整的View树。这个View树随后被设置为Activity的内容视图,实质上是被添加到Activity所关联的Window的DecorView中。
类似于我们直接使用LayoutInflater加载获取到View是一样的。
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // 使用LayoutInflater加载布局
    val inflater = LayoutInflater.from(this)
    val view = inflater.inflate(R.layout.activity_main, null)
    setContentView(view)
}


所以,DecorView的创建之前,需要经过Activity的启动。

创建PhoneWindow

在 Activity 的 attach() 方法中,会创建一个 PhoneWindow 对象。PhoneWindow 是 Window 的一个子类,它负责管理应用程序窗口的外观和行为。
final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
        IBinder shareableActivityToken) {
    attachBaseContext(context);

    mFragments.attachHost(null /*parent*/);
    mActivityInfo = info;

    // 创建Window
    mWindow = new PhoneWindow(thiswindow, activityConfigCallback);
    mWindow.setWindowControllerCallback(mWindowControllerCallback);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    ....
}


在PhoneWindow中,会初始化DecorView,但它的触发逻辑是在调用setContentView的时候。

初始化DecorView

当Activity启动时,在onCreate方法中通常会调用setContentView方法来设置Activity的用户界面布局。
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
}


这段代码中,setContentView是Activity类中的一个方法,它接受一个布局资源ID,用于指定Activity的布局。
在Activity的setContentView方法内部,会进行以下几个关键步骤:
  1. 获取Window: 首先,setContentView通过getWindow()方法获取当前Activity的Window对象。Window对象代表了Android窗口管理系统中的一个窗口。
  2. 布局解析: 使用LayoutInflater解析指定的布局资源ID。这个过程会根据布局文件中的定义,创建出对应的View对象,并按照布局文件的层次结构组装这些对象,形成一个完整的视图树。
  3. 设置内容视图: 通过Window的setContentView方法,将解析好的视图树设置为Window的内容视图。这个视图树的根节点,就是我们所说的DecorView。
public void setContentView(int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}


Window的setContentView方法内部,进一步调用了PhoneWindow的setContentView实现。在这个方法中,会创建或找到DecorView,然后将解析的视图树添加到DecorView中。
@Override
public void setContentView(int layoutResID) {
    // 确保DecorView已经被创建
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}


installDecor方法负责初始化DecorView。如果DecorView还没有被创建,PhoneWindow会创建一个新的DecorView实例,并将其设置为窗口的根视图。接着,解析的视图树(即Activity的布局)被添加到DecorView中。
private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        ...
    } else {
        ...
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
        // 其他初始化代码...
    }
}


通过这个流程,DecorView被创建并作为Window的内容视图。它不仅包含了Activity的布局,还可能包含窗口级别的UI元素,如状态栏和导航栏。

将DecorView添加到WindowManager中

WindowManager 是系统服务,它负责管理应用程序窗口的显示。它提供了一些用于管理窗口显示的方法,例如添加、删除、更新窗口等。
在 Activity 的 onResume() 方法之后,会将 DecorView 添加到 WindowManager 中。这将导致 DecorView 显示在屏幕上。
触发点是在,ActivityThread中的handleResumeActivity()方法中。
@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        boolean isForward, boolean shouldSendCompatFakeFocus, String reason) 
{
    ...

    // 执行Activity onResume
    if (!performResumeActivity(r, finalStateRequest, reason)) {
        return;
    }

    ...

    if (r.window == null && !a.mFinished && willBeVisible) {
        // PhoneWindow
        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;
        ...

        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;

                // 添加到WindowManager中,并与wms建立双向通信
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
    } else if (!willBeVisible) {
        if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
        r.hideForNow = true;
    }

    ...
}


这就是为什么我们在onCreateonResume的时候不能直接拿到View的宽高的原因。因为DecorView添加是在onResume之后。

3绘制


一旦DecorView被创建并设置内容,ViewRootImpl就负责将DecorView附加到窗口。ViewRootImpl是一个系统内部使用的类,它连接窗口管理器(WindowManager)和DecorView,处理布局、绘制和事件分发。
在上面将DecorView添加到WindowManager中时,内部是交由WindowManagerGlobal的addView处理,在该方法中会创建ViewRootImpl对象。
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) 
{

    ...

    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
        ...

        // 创建ViewRootImpl
        if (windowlessSession == null) {
            root = new ViewRootImpl(view.getContext(), display);
        } else {
            root = new ViewRootImpl(view.getContext(), display,
                    windowlessSession, new WindowlessWindowLayout());
        }

        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        try {

            // 将DecorView交由ViewRootImpl,进行后续的绘制与事件分发等出来。
            root.setView(view, wparams, panelParentView, userId);
        } catch (RuntimeException e) {
            final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
            if (viewIndex >= 0) {
                removeViewLocked(viewIndex, true);
            }
            throw e;
        }
    }
}


在这里我们就能发现,DecorView的绘制是由ViewRootImpl触发的,而内部其实是调用了它的requestLayout()方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) 
{
    ...
    requestLayout()
    ...
}

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        // 主线程判断
        checkThread();
        mLayoutRequested = true;
        // 等待垂直刷新信号量的到来,触发分发绘制流程
        scheduleTraversals();
    }
}


requestLayout()方法中,做了经典的两件事情:
  1. 验证是否是在主线程触发。
  2. 等待刷新,触发后续的绘制流程。

4总结


最后,总结一下,整个流程主要可以归纳为四步:
  1. 在Activity的attach()方法里面先创建PhoneWindow并获取WindowManager。
  2. 在Activity的onCreate()方法里调用setContentView()会通过调用用PhoneWindow的installDecor()来创建DecorView。
  3. 在Activity的onResume()方法之后,也就是handleResumeActivity()方法中,会把DecorView添加到WindowMangaer中,并与wms建立双向通信。最终交个ViewRootImpl进行后续的绘制流程。
  4. 在ViewRootImple中,验证触发线程,并等到屏幕刷新信号来了,会调用到ViewRootImpl的performTraversals()来进行后续的绘制。


最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!


推荐阅读

LeakCanary 你真的了解么?看看这些高级用法
详解Android14 Activity 启动过程
不同版本上 Bitmap 内存分配与回收对比


扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!

继续滑动看下一个
鸿洋
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存