Android从Activity启动到View显示中间发生了什么?

前言

Activity的启动过程中,通常我们在onCreate生命周期中调用setContentView方法设置布局文件(即xml文件),似乎这样就完成了布局文件对应的View的绘制及显示,那么背后具体的原理是什么呢?

👉 Activity onCreate生命周期
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
                   👇 布局文件
    setContentView(R.layout.activity_main);
}

很明显布局文件对应的View的绘制和显示是伴随着Activity的生命周期而进行的。在Activity的启动过程中,ActivityThread类中的handleLaunchActivityperformLaunchActivityhandleResumeActivity这3个主要的方法完成了Activity创建到启动工作,完成了ActivityonCreate、onStart、onResume这三个生命周期的执行。

具体的一个Activity启动过程可以参考Android Activity生命周期,启动模式,启动过程详解

让我们伴随着Activity的生命周期执行过程,来具体进行分析和相关源码解读。先来张总结本文的图:

Activity Window DecorView.png

Activity启动过程

我们按照Activity的启动过程中涉及到的ActivityThread类三个方法来逐步说明,首先来看handleLaunchActivity方法。

1. ActivityThread#handleLaunchActivity()方法:调用performLaunchActivity()和handleResumeActivity()方法

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    ...
    
    // 👉 此方法中完成 Activity 生命周期的 onCreate 和 onStart 方法
    Activity a = performLaunchActivity(r, customIntent);
    
    if (a != null) {
        
        ...
        
        // 👉 此方法中完成 Activity 生命周期的 onResume 方法
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
                
        ...
    }
    
    ...
}

handleLaunchActivity()方法中主要调用了performLaunchActivity()方法和handleResumeActivity()方法,performLaunchActivity()中完成onCreateonStart回调;handleResumeActivity()中完成onResume回调。

2. ActivityThread#performLaunchActivity()方法:执行onCreate、onStart生命周期

performLaunchActivity()方法中主要做的工作包括:

  • 1️⃣ 创建Activity实例:调用mInstrumentation.newActivity方法来创建Activity对象;
  • 2️⃣ 创建Application对象,如果已经创建就不再创建,一个进程只有一个Application对象;
  • 3️⃣ 调用Activityattach方法,在其中创建了PhoneWindow对象(有关PhoneWindow对象下文再慢慢解释);
  • 4️⃣ 调用了InstrumentationcallActivityOnCreate方法,从而间接执行了onCreate生命周期;
  • 5️⃣ 调用了ActivityperformStart方法,从而间接执行了onStart生命周期。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    ...
    
    // 👉 通过反射创建 Activity 实例
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);

    // 👉 通过反射构建 Application,如果已经构建则不会重复构建,一个进程只能有一个 Application
    Application app = r.packageInfo.makeApplication(false, mInstrumentation);

    if (activity != null) {
        Context appContext = createBaseContextForActivity(r, activity);
        
        ...
        
        // 👉 在这里实例化了 PhoneWindow,并将该 Activity 设置为 PhoneWindow 的 Callback 回调;
        // 初始化 WindowManager
        activity.attach(appContext, this, getInstrumentation(), r.token,
                r.ident, app, r.intent, r.activityInfo, title, r.parent,
                r.embeddedID, r.lastNonConfigurationInstances, config);
        
        ...
        
        // 👉 调用了 Activity 的 performCreate 方法,间接调用了 Activity 的 onCreate 方法
        mInstrumentation.callActivityOnCreate(activity, r.state);
        
        // 👉 调用 Activity 的 onStart 方法
        if (!r.activity.mFinished) {
            activity.performStart();
            r.stopped = false;
        }
        
        ...
    }
}

performLaunchActivity()方法的前两步工作:Activity对象和Application对象的创建过程,这里不再具体描述,可以参考Android Activity生命周期,启动模式,启动过程详解

下面重点看下attach()方法,其中创建了PhoneWindow对象,而PhoneWindowWindow的一个子类(其实也是唯一子类)。
要想理解PhoneWindow的作用,肯定要先知道Window这个概念。

Window:视图容器

Window代表一个窗口,是视图的容器。Android中的视图是以View树的形式组织的,而View树必须依附在Window上才能工作,一个Window对应着一个View树Activity并不负责视图控制,它只是控制生命周期和处理事件,真正控制视图的是Window

启动Activity时会创建一个Window,显示Dialog时也会创建一个Window,而显示Toast时也会创建Window,因此Activity内部可以有多个Window。由于View的测量、布局、绘制只是在View树内进行的,因此一个Window内View的改动不会影响到另一个Window。Window是一个抽象类,它只有一个实现类PhoneWindow,也就是说在PhoneWindow中完成了Window的真正工作。

Activity#attach()方法:实例化PhoneWindow

介绍完WindowPhoneWindow后,再回到attach方法。其中主要做了以下两步工作:

  • 1️⃣ 实例化PhoneWindow:直接new一个PhoneWindow对象;
  • 2️⃣ 为Activity的Window设置WindowManager:Android 中对 Window 的管理都是通过 WindowManager来完成的,创建 PhoneWindow 之后还会为该 Window 对象设置 WindowManagerWindowManager 是一个接口,继承 ViewManager 接口,从这里也能看出对 Window 的操作其实就是对 View 的操作WindowManager 的实现类是 WindowMangerImpl 。具体关于 Window和WindowManager 的关系下文再说。
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) {
    ...

    // 👉 实例化 PhoneWindow
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    
    ...
    
    // 👉 为 Activity 的 Window 设置 WindowManager
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

    mWindowManager = mWindow.getWindowManager();
    ...
}

Activity#onCreate生命周期:调用setContentView方法(设置Activity对应的布局)

performLaunchActivity()方法的第4️⃣步中,调用了InstrumentationcallActivityOnCreate方法,源码较为简单,如下:

public void callActivityOnCreate(Activity activity, Bundle icicle) {
    prePerformCreate(activity);
    activity.performCreate(icicle);
    postPerformCreate(activity);
}

// 👉 然后调用 Activity 的 performCreate 方法
final void performCreate(Bundle icicle) {
    performCreate(icicle, null);
}

// 👉 执行 onCreate 生命周期
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
    ....
    if (persistentState != null) {
        onCreate(icicle, persistentState);
    } else {
        onCreate(icicle);
    }
    ......
}

Activity#setContentView方法

onCreate方法中,我们通过setContentView来加载我们定义的布局文件。Activity的源码中提供了三个重载的setContentView方法,如下:

// 👉 最常用的 setContentView 方法
public void setContentView(int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

public void setContentView(View view) {
    getWindow().setContentView(view);
    initWindowDecorActionBar();
}

public void setContentView(View view, ViewGroup.LayoutParams params) {
    getWindow().setContentView(view, params);
    initWindowDecorActionBar();
}

这三种方法都先调用了getWindow()setContentView方法,而getWindow()方法返回的正是之前创建的PhoneWindow对象;然后调用Activity的initWindowDecorActionBar方法。


// 👉 Activity 中 Window 成员变量
private Window mWindow;

public Window getWindow() {
    // 👉 这个 mWindow 是在 attach 方法中设置的;
    // 👉 mWindow 是一个 PhoneWindow 对象
    return mWindow;
}

所以最终调用了PhoneWindow中的setContentView方法。

PhoneWindow#setContentView方法

PhoneWindow中也有三个对应的重载setContentView方法,下面以参数为布局文件
id
(layoutResID)的方法进行说明,其中大概做了以下几步工作:

  • 1️⃣ 初始化:首先判断 mContentParent 是否为null,如果为 null,则调用 installDecor()方法初始化mContentParent,此方法后续会说明;

mContentParent 用来装xml布局文件解析出来的view树,是一个FrameLayout,后文还会继续说明其作用。

  • 2️⃣ 填充布局:默认情况下会将设置的布局文件解析为View树,并添加到 mContentParent 中;
  • 3️⃣ 通知Activity布局改变:通过相关回调通知Activity布局已发改变。
public void setContentView(int layoutResID) {
    // 👉 首先判断 mContentParent 是否为 null,如果是第一次调用,则调用 installDecor() 方法
    if (mContentParent == null) {
            👇
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        // 否则判断是否设置 FEATURE_CONTENT_TRANSITIONS Window属性(默认false),
        // 如果没有就移除该 mContentParent 内所有的所有子View;
        mContentParent.removeAllViews();
    }

    // 👉 填充布局
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext()););
        transitionTo(newScene);
    } else {
        // 将xml资源文件通过 LayoutInflater 转换为 View 树
        // 并且添加至 mContentParent 视图中
                          👇
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    
    // 👉 通知 Activity 布局改变
    // 获取 Callback,在 Activity attach 方法中通过 setCallback 设置
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        // onContentChanged 是个空方法,当 Activity 的布局改动时,
        // 即 setContentView() 或者 addContentView() 方法执行完毕时就会调用该方法
               👇
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

PhoneWindowsetContentView方法将我们想要显示的View添加到mContentParent中,而mContentParent又是由installDecor方法得来,下面我们关注此方法。

DecorView:窗口顶层视图

再介绍installDecor()方法之前,我们需要介绍一个重要的概念:DecorView

installDecor()方法中重点完成了两个对象的初始化,一个是mDecor对象,另一个就是之前说的mContentParent对象,这两个对象的定义如下:

// 👉 是一个 DecorView 对象,而 DecorView 是窗口顶层视图
DecorView mDecor;

// 👉 是一个 ViewGroup ,本质上是一个 FrameLayout
ViewGroup mContentParent;

这里让我们先回顾下已经提到的概念,Activity(调用setContentView设置布局),PhoneWindowsetContentView中完成 mContentParent 初始化,填充布局等),这两个与视图有关的概念并未真正直接承载视图,而承载视图的便是 DecorView 对象,即此处的 mDecorDecorView 继承自 FrameLayout,如下所示:

public class DecorView extends FrameLayout

DecorView既然是一个FrameLayout,那么肯定也有其对应的布局文件,只不过是系统默认设置的布局文件,由于在不同的Activity主题情况下,系统默认的 DecorView 对应的布局不一样,我们这里以其中一种举例说明:

// 👉 screen_simple.xml 为例
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
                       👇 对应 mContentParent
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

那么mDecormContentParent 之间是什么关系呢? DecorView 分为两部分,一部分是 ActionBar有的Activity Theme情况下没有ActionBar),另一部分就是 mContentParent即上图中id为content的FrameLayout)。

DecorView.jpg

那么Activity、PhoneWindow、DecorView三者之间有什么关系呢?见下图所示:

Activity PhoneWindow DecorView.png

PhoneWindow#installDecor方法

介绍完DecorView后,我们再回头介绍installDecor方法的具体工作,其中主要做了以下几步工作:

  • 1️⃣ 初始化mDecor:调用generateDecor方法来创建mDecor对象;
  • 2️⃣ 初始化mContentParent:调用generateLayout方法创建mContentParent对象;
private void installDecor() {
    ...
    
    // 👉 初始化 mDecor
    if (mDecor == null) {
        // 如果 mDecor 为空,则生成一个 DecorView 对象
        mDecor = generateDecor(-1);
        
        ...
        
    } else {
        // 将 PhoneWindow 设置给 mDecor
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        // 👉 初始化 mContentParent
        mContentParent = generateLayout(mDecor);
        
        ...
        
    }
}

// 👉 generateDecor:初始化 DecorView
protected DecorView generateDecor(int featureId) {

    ...
    
    return new DecorView(context, featureId, this, getAttributes());
}

// 👉 generateLayout:根据窗口的风格,为 DecorView 选择对应的布局文件
protected ViewGroup generateLayout(DecorView decor) {

    // 获取窗口属性
    TypedArray a = getWindowStyle();

    ...
    
    
    int layoutResource;
    int features = getLocalFeatures();
    
    // 👉 根据设定好的 features 值选择不同的窗口布局文件,得到 layoutResource 值,前文中曾以 screen_simple 举例
    // 注意:此处还有很多分支判断代码省略了
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
    } 
    
    ...

    // 👉 把选中的窗口布局文件解析成 View 树,并添加到 DecorView 中
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    // 👉 指定 contentParent 值,对应的是布局文件中 id 为 content 的 View
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

    ...

    // 👉 返回 contentParent,赋值给 mContentParent,所以 mContentParent 对应的是 R.id.content
    return contentParent;
}

介绍完installDecor方法,我们再来看看PhoneWindow.setContentView方法中的第2️⃣步工作:填充布局,即默认情况下会将我们设置的布局文件解析为 View 树,并添加到 mContentParent 中,现在我们应该理解的更加清楚了。

3. ActivityThread#handleResumeActivity()方法:执行onResume生命周期

执行完onCreate、onStart生命周期后,来到了handleResumeActivity()方法中执行onResume生命周期,有一点需要注意的是,到目前为止,我们也仅是生成了一个Activity,一个PhoneWindow,一个DecorView,而并未真正的将我们需要显示的内容和Android系统进行交互,以进行View绘制,而onResume是我们启动Activity过程中的生命周期的最后一步,那我们有理由猜想有关绘制的执行应该在此生命周期中执行。此方法主要工作包括以下几步:

  • 1️⃣ 执onResume生命周期:调用 performResumeActivity 来间接执行 Activity 的 onResume 生命周期;
  • 2️⃣ 获取Activity的Window和DecorView:获取这两个变量赋值给相关变量,同时暂时使 DecorView 不可见;
  • 3️⃣ 获取 WindowManager:在Activity.attach()方法中,我们为Activity设置了WindowManager
  • 4️⃣ WindowManager添加DecorView:调用WindowManager.addView方法为Window添加DecorView
  • 5️⃣ 使得 DecorView 可见:调用Activity.makeVisible方法使得DecorView重新可见。
final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        
    ...
    
    // 👉 在其内部间接执行 Activity 的 onResume 方法,此时界面还不可见
    ActivityClientRecord r = performResumeActivity(token, clearHide, reason);

    ...
    
    final Activity a = r.activity;
    
    ...
    
    // 👉 获取 Window,DecorView 等对象
    r.window = r.activity.getWindow();
    View decor = r.window.getDecorView();
    // 👉 使 DecorView 不可见
    decor.setVisibility(View.INVISIBLE);
    
    // 👉 获取 WindowManager
    ViewManager wm = a.getWindowManager();
    WindowManager.LayoutParams l = r.window.getAttributes();
    a.mDecor = decor;

    if (a.mVisibleFromClient && !a.mWindowAdded) {
        ...
        // 👉 WindowManager 添加 DecorView,此时依然不可见
        wm.addView(decor, l);
        ...
    }
    
    ...
    
    if (r.activity.mVisibleFromClient) {
        // 👉 使得 DecorView 可见
        r.activity.makeVisible();
    }
            
    ...

}

// 👉 Activity.makeVisible方法:使得 DecorView 可见
void makeVisible() {
    
    ...
    
    mDecor.setVisibility(View.VISIBLE);
}

可见到目前为止,还是没有相关直接绘制View的操作,但对DecorView的操作有好几处,重点是第4️⃣步,其中调用WindowManageraddView方法添加DecorView。我们之前解释过View必须依附于Window才能显示,而Android 中对 Window 的管理都是通过 WindowManager 来完成的,那相关的绘制操作应该就是在此方法中了。

在理解addView方法之前,我们先来看看WindowManager的初始化过程,即Activity.attach方法中调用的setWindowManager方法:

// 👉 Window.setWindowManager 方法
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
    
    ...
    
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

// 👉 WindowManagerImpl.createLocalWindowManager 方法
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    return new WindowManagerImpl(mContext, parentWindow);
}

可见此处WindowManager它的真正实现是WindowManagerImpl,而WindowManagerImpl也并没有真正实现ViewManager接口的三大操作(addView,updateViewLayout,removeView),而是交给了WindowManagerGlobal

// 👉 WindowManagerImpl.addView 方法
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
      👇 WindowManagerGlobal对象
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

mGlobal 对象的初始化如下,就是 WindowManagerGlobal 的单例模式。

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

WindowManagerGlobal#addView方法

下面再来看看 WindowManagerGlobal.addView 方法,主要做了两步工作:

  • 1️⃣ 初始化ViewRootImplViewRootImpl视图层次的最顶层,连接WindowManagerService和DecorView的纽带, 同时 ViewRootImpl 的内容大部分都是 WindowManagerGlobal 类的内部实现细节
  • 2️⃣ 调用 ViewRootImpl.setView 方法:调用此方法来完成真正的添加 View 操作。
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ...

    // 👉 ViewRootImpl 对象
    ViewRootImpl root;
    
    synchronized (mLock) {
    
        ...
        
        // 👉 初始化 ViewRootImpl 
        root = new ViewRootImpl(view.getContext(), display);

        ...
        
        // 👉 调用 ViewRootImpl.setView 方法
        root.setView(view, wparams, panelParentView);
    }
}

ViewRootImpl#setView方法

真正完成添加 View 的操作是此方法:

// 👉 ViewRootImpl.setView 方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    
        👇
    requestLayout();
    
    ...
                            👇
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
    
    ...
            👇 将 DecorView 的 parent 设置为 ViewRootImpl
    view.assignParent(this);
    
    ... 
}

此方法中做的工作主要包括以下两个方面:

  • 1️⃣ requestLayout()方法调用View的测量,布局,绘制三个流程
  • 2️⃣ mWindowSession.addToDisplay()方法mWindowSession 是一个aidl,ViewRootImpl 利用它来和 WindowManagerService 进行跨进程交互,这里先不过多介绍有关 WindowManagerService 内容,只简单介绍下其作用,如下。

WindowManagerService(WMS)的作用有很多:

  • 窗口的管理者:负责窗口的启动、添加和删除,另外窗口的大小和层级也是由WMS进行管理;
  • 事件的管理和派发工作:通过对窗口的触摸从而产生触摸事件,InputManagerService(IMS)会对触摸事件进行处理,它会寻找一个最合适的窗口来处理触摸反馈信息,WMS是窗口的管理者,因此,WMS“理所应当”的成为了事件的中转站。
  • Surface管理:窗口并不具备有绘制的功能,因此每个窗口都需要有一块Surface来供自己绘制。为每个窗口分配Surface是由WMS来完成的。

摘自Android解析WindowManagerService(一)WMS的诞生

我们重点看看 requestLayout() 方法,源码如下:

public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        // 👉 检查是不是主线程
        checkThread();
        mLayoutRequested = true;
        // 👉 view绘制三大流程入口
        scheduleTraversals();
    }
}

熟悉View绘制流程的同学应该知道,当View的大小、形状发生了变化的时候,可以调用此方法来进行重新绘制,此方法会从View树重新进行一次测量、布局、绘制这三个流程。此方法主要做了两步工作:

  • 1️⃣ 检查当前线程:如果调用此方法的现场不是主线程,那么就在checkThread()方法中抛出异常,这一点与我们"通常意义"上所说的:只能在主线程更新UI相吻合,具体checkThread()方法稍后写明;
  • 2️⃣ 调用 scheduleTraversals 方法:此方法后续完成了View的三大流程(测量(measure),布局(layout),绘制(draw)),具体分析见后续 。

再看下 checkThread() 方法:

void checkThread() {
          👇
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

mThread 的初始化是在 ViewRootImpl 的构造函数中完成的:

// ViewRootImpl 构造函数
public ViewRootImpl(Context context, Display display) {
    ...
    
      👇
    mThread = Thread.currentThread();
    
    ...
}

回忆下前文, ViewRootImpl 的初始化是在 WindowManagerGlobal.addView 方法中完成的,因此mThread 肯定对应的是主线程,因为 ActivityThread.handleLaunchActivity 方法就是在主线程中执行的,而其中并未切换过线程。因此如果我们在子线程中更新UI,那么最终会走到 requestLayout 方法进行重绘制,但此时会发现 mThread(主线程) 和 Thread.currentThread()(子线程)不是同一个线程,那么便会抛出异常

再来说说 ViewRootImpl ,其作用非常重大。所有View的绘制以及事件分发等交互都是通过它来执行或传递的。View的三大流程(测量(measure),布局(layout),绘制(draw))均通过ViewRootImpl来完成。Android的所有触屏事件、按键事件、界面刷新等事件都是通过ViewRootImpl进行分发的。

从源码实现上来看,ViewRooImpl 既非View的子类,也非View的父类,但是,它实现了ViewParent接口,这让它可以作为View的名义上的父视图。
ViewRootImpl.setView 方法中调用了 View.assignParent 方法:

void assignParent(ViewParent parent) {
    if (mParent == null) {
        mParent = parent;
    } 
    
    ...
}

View是以View树组织的,每个View都有其Parent,DecorView作为最顶层的View,其Parent被设置为ViewRootImpl对象这样它可以作为View的名义上的父视图,实质上完成了View的更新,绘制等工作

View Tree.png

View类中也有requestLayout方法:用于 View 的位置,大小、形状发生了变化的时候进行调用。当一个子 View 调用此方法时,便会令 View 树重新进行一次测量、布局、绘制这三个流程,具体源码如下:

// 👉 View#requestLayout:
public void requestLayout() {
    
    ...
   
    if (mParent != null && !mParent.isLayoutRequested()) {
        // 👉 调用父容器的 `requestLayout` 方法
        mParent.requestLayout();
    }
    
    ... 
}

此方法中最重要的是调用mParent.requestLayout方法,是向父容器请求布局,即调用父容器的 requestLayout 方法,此方法沿着View树向上传递,最终来到了 DecorView#requestLayout中,而DecorView是顶层View,其mParent便是ViewRootImpl,所以子View的requestLayout方法,经过层层传递,最终会被ViewRootImpl 接收并处理

那具体的 scheduleTraversals 方法中主要完成了 View 的三大流程,本文暂且不进行分析。

总结

通过以上可以知道,Activity就像个控制器,不负责视图部分Window像个承载器,装着内部视图DecorView是个顶层视图,是所有View的最外层布局ViewRootImpl像个连接器,负责沟通,通过硬件的感知(分发事件,绘制View内容)来通知视图,进行用户之间的交互

其实本文还存在两个问题没有说明:

  • 1️⃣ WindowManagerService具体原理ViewRootImpl 和 WindowManagerService 进行跨进程交互的背后原理是什么,怎么做到View的真正显示?
  • 2️⃣ View绘制的三大流程View 测量,布局,绘制的详细流程是什么?

这些分析后续陆续进行说明。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342