View的绘制流程之一:setContentView()方法源码分析

一、知识储备


由 Activity 的启动流程,我们知道 Activity 的启动顺序如下:

--> 栈顶的Activity的onPause() 
--> Instrumentation的newActivity() /*创建Activity*/
--> 待启动Activity的attach()
--> 待启动Activity的onCreate()
--> 待启动Activity的onResume()
--> ActivityThread.handleResumeActivity() /*将DecorView添加到Window*/

也就是说当我们启动一个 Activity 后会先调用这个 Activity的 onCreate()方法,在这个方法中会调用 setContentView()方法,setContentView()方法会将 Activity的 Layout布局文件加载到 DecorView中,然后 DecorView会在 Activity启动流程中的一个 handleResumeActivity()方法被加载 Window中,继而在成功加载到 Window后会开始绘制 Activity的 Layout 布局文件,那么我们现在先分析 setContentView()方法到底完成了什么工作,以 API23的源码分析


二、源码分析


Step 1:

我们直接看到 Activity的 setContentView()方法中
★ Activity # setContentView()

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

★ Activity # getWindow()

public Window getWindow() {
    return mWindow;
}

 
getWindow() 方法会获取 Window抽象类的实现类 PhoneWindow,initWindowDecorActionBar() 方法用于初始化标题栏;那么 mWindow 在什么时候初始化了???

当 Activity 通过反射被创建出来后就会调用 Activity 的 attach() 方法,而在这个方法中会初始化 mWindow ,我们看到这个方法
★ Activity # attach()

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) {
    attachBaseContext(context);
    mFragments.attachHost(null /*parent*/);
    mWindow = new PhoneWindow(this);
    mWindow.setCallback(this);
    ....
    if (info.uiOptions != 0) {
        mWindow.setUiOptions(info.uiOptions);
    }
    mUiThread = Thread.currentThread();
    mMainThread = aThread;
    .....
}

也就是说在 Activity创建的时候 mWindow 就会初始化,这个 mWindow 实现将 Activity 的布局添加到 DecorView 的功能

Step 2:

我们接着看到 PhoneWindow 的 setContentView() 方法中
★ PhoneWindow # setContentView()

@Override
public void setContentView(int layoutResID) {
    // mContentParent其实是DecorView布局中的一个FrameLayout,这个FrameLayout用来摆放Activity的Layout布局
    if (mContentParent == null) {
        // 这个方法会创建DecorView,同时初始化mContentParent
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        ....
    } else {
        // 加载 Activity的布局文件到 mContentParent
        // mContentParent就是系统的com.android.internal.R.id.content 这个View
        // layoutResID:Activity的 Layout布局id
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    ....
}

首先,判断是否安装了 DecorView,如果还没有就调用 installDecor() 方法安装 DecorView
接着,将 Activity 的 Layout 布局文件加载到 DecorView 中

mContentParent 是 DecorView 的布局文件,用来存放 Activity 的布局的;所以说现在重点就是这个 installDecor() 方法了;
结构图
 

Step 3:

我们现在看到 installDecor() 方法
★ PhoneWindow # installDecor()

private void installDecor() {
    if (mDecor == null) {
        // 初始化DecorView
        mDecor = generateDecor();
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    }
    // 获取存放Activity的Layout布局的一个FrameLayout
    if (mContentParent == null) {
        // generateLayout()方法会通过 findViewById(ID_ANDROID_CONTENT)获取到DecorView布局中的
        // 一个FrameLayout,可以存放 Activity的Layout布局文件
        mContentParent = generateLayout(mDecor);

        ....    
    }
}

首先,因为是第一次进到这个方法,所以 mDecor 肯定还没有初始化,所以会先调用 generateDecor() 方法初始化 DecorView
然后,调用 generateLayout() 方法初始化 mContentParent 布局

我们看到 PhoneWindow 类的 generateDecor() 方法
★ PhoneWindow # generateDecor()

protected DecorView generateDecor() {
    return new DecorView(getContext(), -1);
}

★ PhoneWindow.DecorView # 构造方法

public DecorView(Context context, int featureId) {
    super(context);
    mFeatureId = featureId;
    mShowInterpolator = AnimationUtils.loadInterpolator(context,
            android.R.interpolator.linear_out_slow_in);
    mHideInterpolator = AnimationUtils.loadInterpolator(context,
            android.R.interpolator.fast_out_linear_in);
    mBarEnterExitDuration = context.getResources().getInteger(
            R.integer.dock_enter_exit_duration);
}

 
扩展:了解 DecorView
我们来了解一下 DecorView,在这个之前,我们先分清楚 mDecor 和 mContentParent

  • mDecor:这是一个 DecorView 对象,这个 DecorView 继承自 FrameLayout
  • mContentRoot:这个是 DecorView的布局文件 View,相当于 Activity的布局文件
  • mContentParent:这个 DecorView的布局 View 中的一个 FrameLayout,有点像 Activity 布局文件中最外层那个 LinearLayout 、RelativeLayout等

DecorView是 PhoneWindow的一个内部类,用于存放 Activity 的 Layout 布局,也就是说一个 Activity会对应一个 DecorView,同时这个 DecorView会在调用 setContentView() 时初始化
★ PhoneWindow.DecorView # 类结构

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

DecorView 中有几个比较重要的方法:

dispatchTouchEvent()
onInterceptTouchEvent()
onTouchEvent()
onMeasure()
onDraw()
draw()
onLayout()
onAttachedToWindow()
onDetachedFromWindow()

Step 4:

接下来我们分析一下 DecorView 的 布局是如何初始化的
★ PhoneWindow # generateLayout()

protected ViewGroup generateLayout(DecorView decor) {
    // 1、根据当前的主题应用一些数据
    TypedArray a = getWindowStyle();
    ....
    // 2、根据设置的主题样式选择不同的 DecorView布局,一般来说都是用 R.layout.screen_simple这个布局
    int layoutResource;
    int features = getLocalFeatures();
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
    } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        ....
    } .... {
    } else {
        layoutResource = R.layout.screen_simple;
    }
    mDecor.startChanging();
    // 3、加载 DecorView的 Layout布局文件
    View in = mLayoutInflater.inflate(layoutResource, null);
    // decor就是调用 generateLayout()方法传进来的 DecorView对象
    // 将加载成功的布局添加到 DecorView中
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    // mContentRoot就是 DecorView的布局文件
    mContentRoot = (ViewGroup) in;
    // 4、获取 DecorView中存放 Activity的布局FrameLayout
    ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
    ....
    // 最后返回这个ViewGroup
    return contentParent;
}

初始化 DecorView 的过程如下:
1. 根据当前设置的主题样式应用一些数据
2. 根据设置的主题样式选择不同的 DecorView 布局,一般来说都是用 R.layout.screen_simple 这个布局
3. 加载 DecorView 布局文件,并保存到 mContentRoot
4. 调用 findViewById()方法获取 DecorView布局文件的一个 FrameLayout,然后返回

我们看一下 DecorView 常用的布局文件,也就是 R.layout.screen_simple
★ R.layout.screen_simple

<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
         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>

可见,Activity 的布局文件会被添加到这个 FrameLayout 中
 

现在,我们回过头来看(Step 2)中的一段代码
★ PhoneWindow # setContentView()

@Override
public void setContentView(int layoutResID) {
    // mContentParent其实是DecorView布局中的一个FrameLayout,这个FrameLayout用来摆放Activity的Layout布局
    if (mContentParent == null) {
        // 这个方法会创建DecorView,同时初始化mContentParent
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        ....
    } else {
        // 加载 Activity的布局文件到 mContentParent
        // mContentParent就是系统的com.android.internal.R.id.content 这个View
        // layoutResID:Activity的 Layout布局id
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    ....
}

当安装了 DecorView,并初始了 mContentParent后,就会将 Activity 的 布局文件加载到 mContentParent 中,也就是加载到 DecorView 的布局文件中的 FrameLayout中

三、总结


  • setContentView() 主要是初始化 DecorView,并将 Activity 的布局文件加载到 DecorView 的布局文件中的一个 FrameLayout 中

  • DecorView 会在 Activity 启动流程中的一个 handleResumeActivity() 方法中被添加到 远程的 WindowManagerService 中

  • 当 DecorView 成功添加到 WindowManagerService 后就会开始绘制 DecorView,同时也会开始绘制 Activity 的布局;也就是说 Activity 的布局会在这个时候 Activity 的 onResume() 方法之后进行绘制

四、延伸知识点


问题:现在我们已经将 Activity 的 Layout 布局文件添加到了 DecorView 的 FrameLayout 中,但是这个 Activity 的 Layout 布局什么时候绘制???
:在 Activity 启动流程中的 ActivityThread.handleResumeActivity() 方法中会通过调用 Activity 的 getWindwo() 方法获取到 PhoneWindow,然后再调用 PhoneWindow 的 的 getDecorView() 方法获取到 DecorView ,然后会将这个 DecorView 添加到远程的 WindowManagerService 中,当成功添加后就会调用方法对 DecorView 的布局进行绘制,具体的细节可以看下面的文章
View的绘制流程之二:View的绘制入口源码分析




版权声明:本文为博主原创文章,未经博主允许不得转载。

猜你喜欢

转载自blog.csdn.net/myTempest/article/details/81558601