前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >竟如此简单,一文看懂DecorView的一生

竟如此简单,一文看懂DecorView的一生

作者头像
Rouse
发布2024-04-11 15:09:48
4970
发布2024-04-11 15:09:48
举报
文章被收录于专栏:Android补给站

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

简介

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是不对开发者公开的,但它在视图渲染和事件处理过程中起着关键作用。

创建流程

DecorView的创建通常在Activity的生命周期的onCreate方法中开始,具体是通过调用setContentView方法触发的。

当Activity的setContentView方法被调用时,背后的LayoutInflater就开始发挥作用。这个方法接受一个布局资源ID,然后LayoutInflater负责找到对应的布局文件,解析它,并根据文件中的定义构建出一个完整的View树。这个View树随后被设置为Activity的内容视图,实质上是被添加到Activity所关联的Window的DecorView中。

类似于我们直接使用LayoutInflater加载获取到View是一样的。

代码语言:javascript
复制
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 的一个子类,它负责管理应用程序窗口的外观和行为。

代码语言:javascript
复制
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(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(mWindowControllerCallback);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    ....
}

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

初始化DecorView

当Activity启动时,在onCreate方法中通常会调用setContentView方法来设置Activity的用户界面布局。

代码语言:javascript
复制
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
}

这段代码中,setContentViewActivity类中的一个方法,它接受一个布局资源ID,用于指定Activity的布局。

在Activity的setContentView方法内部,会进行以下几个关键步骤:

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

Window的setContentView方法内部,进一步调用了PhoneWindowsetContentView实现。在这个方法中,会创建或找到DecorView,然后将解析的视图树添加到DecorView中。

代码语言:javascript
复制
@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中。

代码语言:javascript
复制
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()方法中

代码语言:javascript
复制
@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之后。

绘制

一旦DecorView被创建并设置内容,ViewRootImpl就负责将DecorView附加到窗口。ViewRootImpl是一个系统内部使用的类,它连接窗口管理器(WindowManager)和DecorView,处理布局、绘制和事件分发。

在上面将DecorView添加到WindowManager中时,内部是交由WindowManagerGlobaladdView处理,在该方法中会创建ViewRootImpl对象。

代码语言:javascript
复制
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()方法

代码语言:javascript
复制
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. 等待刷新,触发后续的绘制流程

总结

最后,总结一下,整个流程主要可以归纳为四步:

  1. Activityattach()方法里面先创建PhoneWindow并获取WindowManager
  2. ActivityonCreate()方法里调用setContentView()会通过调用用PhoneWindowinstallDecor()来创建DecorView
  3. ActivityonResume()方法之后,也就是handleResumeActivity()方法中,会把DecorView添加到WindowMangaer中,并与wms建立双向通信。最终交个ViewRootImpl进行后续的绘制流程。
  4. ViewRootImple中,验证触发线程,并等到屏幕刷新信号来了,会调用到ViewRootImplperformTraversals()来进行后续的绘制。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-03-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android补给站 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
    • 与Window的关系
      • 与Activity的关系
        • 与ViewRootImpl的关系
        • 创建流程
          • 创建PhoneWindow
            • 初始化DecorView
              • 将DecorView添加到WindowManager中
              • 绘制
              • 总结
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档