Android 活动、窗、DecorView、ViewRoot

  1. 请讲述Activity、Window、DecorView以及ViewRoot之间的层级关系⭐⭐⭐⭐⭐

  1. DecorView什么时候可见?⭐⭐⭐⭐

目录

  • 1、Activity、Window、DecorView以及ViewRoot之间的层级关系

  • 1.1 Activity

  • 1.2 Window

  • 1.3 DecorView

  • 1.4 ViewRoot

  • 2、DecorView源码分析

  • 3、DecorView的显示

  • 4、ViewRoot

  • 5、总结

1、Activity、Window、DecorView以及ViewRoot之间的层级关系

图片可以最直观看出这几个模块之间的层次关系。

1.1 Activity

我们都知道可以Activity来创建可以触摸的视图界面,但其实Activity并不负责视图的控制。真正控制视图界面的是Window。每一个Activity包含一个Window。Activity主要负责视图界面的统筹,如添加、显示等,并通过一些特定的方法来和Window、View做交互。

1.2 Window

Window意味“窗口”,是视图界面真正的承载器。Window是一个接口类,实际的窗口是PhoneWindow类,该类内部有一个内部类DecorView。就可以通过创建DecorView来加载布局xml文件。

1.3 DecorView

DecorView是FrameLayout类的子类,通常认为DecorView是Android视图树的根节点,也就是最顶层的View。DecorView内部包含一个LinearLayout,在这个LinearLayout里面有上下三个部分,上面是个ViewStub,延迟加载的视图(应该是设置ActionBar,根据Theme设置),中间的是TitleView,根据Activity的主题设置的,有的布局就没有这个标题栏,最下面的是ContentViews,这是最重要的一部分,我们平时自定义的布局文件通过setContentView()方法加载,其实就是加载到这个ContentViews里面,成为其唯一的子View。Window 通过WindowManager将DecorView加载其中,并将DecorView交给ViewRoot,进行视图绘制以及其他交互。通过一个实际的DecorView文件加深理解:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <!-- Popout bar for action modes -->
    <ViewStub
        android:id="@+id/action_mode_bar_stub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inflatedId="@+id/action_mode_bar"
        android:layout="@layout/action_mode_bar"
        android:theme="?attr/actionBarTheme" />

    <FrameLayout
        style="?android:attr/windowTitleBackgroundStyle"
        android:layout_width="match_parent"
        android:layout_height="?android:attr/windowTitleSize">

        <TextView
            android:id="@android:id/title"
            style="?android:attr/windowTitleStyle"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical" />
    </FrameLayout>

    <FrameLayout
        android:id="@android:id/content"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foreground="?android:attr/windowContentOverlay"
        android:foregroundGravity="fill_horizontal|top" />
</LinearLayout>

1.4 ViewRoot

ViewRoot作用非常重大。所有View绘制、触摸事件分发、界面刷新等都是通过它来执行或传递的。ViewRoot对应ViewRootImpl类,它是连接WindowManagerService和DecorView的纽带

ViewRoot并不属于View树的一份子。从源码实现上来看,它既非View的子类,也非View的父类,但是,它实现了ViewParent接口,这让它可以作为View的名义上的父视图。RootView继承了Handler类,可以接收事件并分发,Android的所有触屏事件、按键事件、界面刷新等事件都是通过ViewRoot进行分发的。

2、DecorView源码分析

从上面的介绍可以看出DecorView的重要地位,这一节,通过阅读源码,进一步弄清楚:

  • Activity、Window、DecorView以及ViewRoot之间的层级嵌套关系;

  • DecorView的创建和显示;

为了读者可以更加完整的了解上面两个问题,我们从handleLaunchActivity()方法开始追溯,该方法是Activity启动的本地操作入口,完成整个Activity的启动流程。 以下源码来自Android9.0.0/frameworks/base/core/java/android/app/ActivityThread.java:

public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
    ...
    final Activity a = performLaunchActivity(r, customIntent); // 1

    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        reportSizeConfigurations(r);
        if (!r.activity.mFinished && pendingActions != null) {
            pendingActions.setOldState(r.state);
            pendingActions.setRestoreInstanceState(true);
            pendingActions.setCallOnPostCreate(true);
        }
    } 
    ...
    return a;
}

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            ...
 activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback); // 2
            ...
}

在Acitivy的创建流程有如下调用关系:

handleLaunchActivity()-> [注释1]performLaunchActivity()-> [注释2]activity.attach()

继续跟踪源码就到了activity.attach(),其源码在/frameworks/base/core/java/android/app/Activity.java:

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) {
    ...
    mWindow = new PhoneWindow(this, window, activityConfigCallback); // 3
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this); // 4
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    ...
    mWindow.setWindowManager( // 5
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    ...
}

在attach()方法需要重点介绍一下,在[注释3]生成PhoneWindow实例,通过[注释4]设置回调,向Activity分发触摸、点击等事件,通过[注释5]给mWindow设置WindowManage对象.其中[注释3]所创建的mWindow就在从我们非常熟悉的代码中使用到:

setContentView(R.layout.activity_main);

我们在Activity的onCreate()函数会调用setContentView()来设置当前使用的布局文件,其对应的源码在/frameworks/base/core/java/android/app/Activity.java:

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID); // 6
    initWindowDecorActionBar();
}

在Activity中通过[注释6]的getWindow()中可以获取到Window对象,也就是上面[注释3]创建的mWindow对象。因为上述attach()函数在Activity启动流程只调用一次,也仅创建一个PhoneWindow对象,即mWindow对象。因此说每一个Activity仅包含一个Window。此时,在Activity获取到PhoneWindow对象后,回到[注释6],就可以给PhoneWindow设置布局文件了。对应实现在/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java:

public void setContentView(int layoutResID) {
       if (mContentParent == null) { // 7
           installDecor(); // 8
       } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
           mContentParent.removeAllViews(); // 9
       }

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

上述有两个变量:

  • mContentParent:这个就是上述说的DecorView里的ContentViews,对应布局文件里的@android:id/content,本质是一个FrameLayout;

  • FEATURE_CONTENT_TRANSITIONS:具有FEATURE_CONTENT_TRANSITIONS特性则表示开启了Transition,而开启Transition就需要做相应的处理,在此不做讨论;

如果mContentParent为空,则在[注释8]installDecor()创建一个DecorView和mContentParent,不为空则在[注释9]清空其里面的View。并最后在[注释10]添加传入的布局文件ID,在[注释11]在有内容改变时做回调通知。

总结:[注释3]Activity会创建PhoneWindow对象,[注释8]PhoneWindow对象又会创建一个DecorView对象,DecorView创建过程中根据不同的主题,可能加载不同的布局格式,比如一些没有Title,一些没有导航栏等。[注释10]向DecorView的ContentViews添加Activity设置的布局文件。到此,视图就一层一层的嵌套上去了。

3、 DecorView的显示

有这么一个知识点,Activity的界面只有在其生命周期执行到onResume()后才对用户可见。下面通过源码查看其原因。Activity生命周期中的onCreate()和onStart()会在[注释2]所在的performLaunchActivity()方法中调用。但到此为止只是[注释8]PhoneWindow对象创建一个DecorView对象,[注释10]向DecorView的ContentViews添加Activity设置的布局文件,还没有将DecorView添加到PhoneWindow中。因此来看看onResume()方法,该方法在handleResumeActivity()中调用,源码在/frameworks/base/core/java/android/app/ActivityThread.java:

    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
      
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); //12 
    ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE); // 12:DecorView设置为不可见
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l); // 13:DecorView添加到WindowManager
                } else {
                    a.onWindowAttributesChanged(l);
                }
            ...
        }    
        if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
            ...
            r.activity.mVisibleFromServer = true;
            mNumVisibleActivities++;
            if (r.activity.mVisibleFromClient) {
                r.activity.makeVisible(); // 14
            }
        }

        ...
    }

无论是[注释12]还是[注释13],此时DecorView对用户还是不可见,直到[注释14]的makeVisible()方法执行完,界面才对用户可见。对应的源码在frameworks/base/core/java/android/app/Activity.java:

void makeVisible() {
   if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());//15:将DecorView添加到WindowManager
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);//16:DecorView可见
}

真正起作用的是[注释16],到此DecorView就可见了。同时,[注释15]wm.addView()的作用也非常的重要,因为其内部创建了一个ViewRootImpl对象,也就是本文继Window、Activity、DecorView之后,最后一个重要对象。

4、ViewRoot

本节从上述[注释15]开始讲起,其中wm由getWindowManager(),在/frameworks/base/core/java/android/app/Activity.java 可以查到如下源码:

public WindowManager getWindowManager() {
        return mWindowManager;
    }

追述源码在 /frameworks/base/core/java/android/view/Window.java可以知道 mWindowManager其实是WindowManagerImpl类,这是因为WindowManager是个接口,具体是交给WindowManagerImpl来实现的。

mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);

因此[注释15]对应的源码就在/frameworks/base/core/java/android/view/WindowManagerImpl.java:

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); //16
}

在[注释16]交给WindowManagerGlobal的addView()方法去实现。/frameworks/base/core/java/android/view/WindowManagerGlobal.java:

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ...
    root = new ViewRootImpl(view.getContext(), display); //17

    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); //18
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        if (index >= 0) {
            removeViewLocked(index, true);
        }
        throw e;
    }
}

WindowManagerGlobal的addView()方法中不仅会将DecorView添加到Window中,同时会在[注释17]创建了ViewRootImpl对象,并将ViewRootImpl对象和DecorView通过[注释18]root.setView()把DecorView加载到Window中。并最后调用performTraversals()方法,按照Measure() -> Layout() -> Draw()经典流程完成View绘制,详情会在《View绘制流程全解析》小节介绍。

当然,ViewRootImpl 的作用不仅包括View绘制,触摸事件分发也是由 ViewRootImpl 完成,大致流程是:

硬件 -> ViewRootImpl -> DecorView -> PhoneWindow -> Activity

详情会在《触摸事件分发机制全解析》小节介绍。在此知道在[注释15]将DecorView添加到WindowManager后,ViewRootImpl相当于WindowManager与DecorView之间的连接器即可。

5、总结

本文通过源码分析了Activity、Window、DecorView以及ViewRoot之间的层级关系:

  • Activity负责视图界面的统筹,如添加、删除视图,但并不直接对视图做控制;

  • Window是视图界面真正的承载器,所有视图都依托于Window,一个Activity只有一个Window;

  • DecorView是视图树上最顶层的视图,本质是一个FrameLayout,主要包括标题栏和内容栏,我们在Activity加载的布局文件,其实最终加载于DecorView的内容栏里面,一个Window只有一个DecorView;

  • ViewRoot是一个连接器,负责View的绘制和触摸事件分发等。

同时也介绍了如何创建DecorView并显示。DecorView会在PhoneWindow中创建,并在Acitivity执行到onResume()的时候变为对用户可见。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值