Android View 显示原理分析1

132 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第23天,点击查看活动详情

当启动一个Activity的时候,Activity执行了onCreate() 、onStart()、 onResume()3个方法后,用户就能看见界面了,相应的XML布局文件中的View是如何显示到屏幕上的?有一个重要的方法是setContentView(R.layout.main_layout),我们一起来看一下setContentView()的执行过程。

一.setContentView()执行流程

1.Activity

      当start一个包含界面的activity时,会首先调用setContentView(resourceID),代码如下:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.recent_main_view);
}

      跟随调用逻辑,看一下setContentView的实现:

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

public Window getWindow() {
    return mWindow;
}

private Window mWindow;
mWindow = new PhoneWindow(this, window, activityConfigCallback);

      根据Activity的启动过程,mWindow是在attach()时创建的,是PhoneWindow实例,此处是调用到了PhoneWindow里面的setContentView(layoutResID)方法:

2.PhoneWindow

public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        //初始化DecorView
        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 {
        //解析传进来的xml布局,将其加载到mContentParent上
        //mContentParent是在installDecor()中创建的,后续会看到
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

      首先看一下installDecor()方法:

// This is the top-level view of the window, containing the window decor.
// PhoneWindow的顶层View
private DecorView mDecor;
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
// 最终内容防置即显示的view
ViewGroup mContentParent;
private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        //创建DecorView
        mDecor = generateDecor(-1);
        ......
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        //上个方法中的mContentParent在此创建
        mContentParent = generateLayout(mDecor);
    }
    .......
}

      我们可以看到,在installDecor()内部主要执行了两个方法:

      一个是generateDecor()来创建DecorView;

      一个是generateLayout(mDecorView)来将resId对应的xml文件加载到DecorView上。

//创建DecorView
protected DecorView generateDecor(int featureId) {
    ......
    return new DecorView(context, featureId, this, getAttributes());
}

      generateDecor()内部就是创建了一个DecorView对象返回。

//1.将R.layout.screen_simple布局文件通过addView加载到DecorView上
//2.从R.layout.screen_simple布局文件获取到R.id.content控件,返回contentParent给mContentParent
protected ViewGroup generateLayout(DecorView decor) {
    ......
    // Inflate the window decor.

    int layoutResource; //是一个布局文件id
    int features = getLocalFeatures();
    // 根据不同的主题将对应的布局文件id赋值给layoutResource
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
        setCloseOnSwipeEnabled(true);
    } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        ......
    }
        ......          
     else {
        layoutResource = R.layout.screen_simple;
    }

    mDecor.startChanging();
    //通过LayoutInflater加载解析layoutResource =  R.layout.screen_simple(最常用)布局文件
    //该方法执行会在后续的DecorView.java里面介绍
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    //从R.layout.screen_simple里面获取R.id.content控件,获取到contentParent
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }
    .......
    return contentParent;
}

      在generateLayout()内部主要做了三件事:

      一.找到对应的layoutResource,如果没有特别配置,则返回默认的R.layout.screen_simple;

      二.将layoutResource作为参数传入执行DecorView的onResourceLoaded()方法;

      三.找到ID_ANDROID_CONTENT对应的view,然后作为contentParent返回;

      看一下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">
    <!-- ActionBar    -->
    <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>

      从布局文件看,DecorView加载上的是一个LinearLayout,返回的contentParent(即activity展示界面)是一个FrameLayout。