其他分享
首页 > 其他分享> > setContentView是如何把布局加上去的

setContentView是如何把布局加上去的

作者:互联网

setContentView是如何把布局加上去的

在Android开发中,最常见的代码就是setContentView,然后传入你写的布局ID,那么布局就被加载到界面中了,系统究竟是怎么被加到界面中的,就需要通过源码来查看了。

点击setContentView方法,进去会发现调用了以下的代码

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

可以看到是通过getWindow方法来设置布局文件的,那我们在看一下这个getWindow做了什么事情,在点击进去看一下

/**
 * Retrieve the current {@link android.view.Window} for the activity.
 * This can be used to directly access parts of the Window API that
 * are not available through Activity/Screen.
 *
 * @return Window The current window, or null if the activity is not
 *         visual.
 */
public Window getWindow() {
    return mWindow;
}

其实这个getWindow方法就是返回了当前的window窗口对象,而且通过注释我们还可以知道,如果当前的Activity不可见的时候,这个window对象是为空的,那么这个window到底是什么,我们在进去看一下window类是什么样的,

/**
* Abstract base class for a top-level window look and behavior policy.  An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
 *
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
 /** Flag for the "options panel" feature.  This is enabled by default. */
	public static final int FEATURE_OPTIONS_PANEL = 0;
	/** Flag for the "no title" feature, turning off the title at the top
 	*  of the screen. */
	public static final int FEATURE_NO_TITLE = 1;

关于window类的源码截取了一部分,可以看到这是一个抽象类,通过注释,我们获取信息,这个类有一个唯一的实现类PhoneWindow,所以接下来我们就需要主要去分析一下这个PhoneWindow类了,去看看这个类的setContentView做了什么

@Override
public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    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;
}

PhoneWindow这个类中setContentView方法中,我们重要关注两个方法,一个是installDecormLayoutInflater.inflate(layoutResId,mContentParent);而且在调用installDecor方法的时候,还对mContentParent进行了判断,那这个mContentParent是什么呢,我们通过installDector方法点进去看一下

if (mDecor == null) {
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }

由于源码里面中的方法代码太长了,这里只做部分截取,这里看到,如果mDecor==null的话,我们对mDecor进行了一个初始化,初始化的方法是generateDecor(-1),我们点击去看一下

protected DecorView generateDecor(int featureId) {
    // System process doesn't have application context and in that case we need to directly use
    // the context we have. Otherwise we want the application context, so we don't cling to the
    // activity.
    Context context;
    if (mUseDecorContext) {
        Context applicationContext = getContext().getApplicationContext();
        if (applicationContext == null) {
            context = getContext();
        } else {
            context = new DecorContext(applicationContext, getContext().getResources());
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
    } else {
        context = getContext();
    }
    return new DecorView(context, featureId, this, getAttributes());
}

可以看到,其实这个方法就是对DecorView做了一个初始化,最后也是返回了一个DecorView

DecorView是window的一个顶层容器,继承自FrameLayout

看完这个generateDecor之后,我们在回到installDecor方法,接着往下看,

if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
        ...
}

我们发现,在初始化mDecor之后,又对mContentParent进行了初始化,那么这个mContentParent是什么,在通过generateLayour(mDecor)方法来对里面的具体实现进行探索

代码点进去有点多,在开始的时候,是通过系统内部的一些样式来进行一些特性样式的设置,这里可以略过,然后真正需要研究的代码是从注释中 Inflate the window decor开始
可以在这里看到,源码中初始化了一个int layoutResource;那么我们往下研究,发现下面就是对这个layoutResource字段进行赋值操作。简单的理解就是通过不同的样式来加载系统中一些默认的布局文件,

在对这个layoutResource字段进行赋值之后,我们重点关注两行代码,

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

第一行代码中,主要是这个mDecor.onResourcesLoaded()方法,传入了之前赋值的layoutResource字段,这个方法点进去之后发现,其实就是对这个layoutResource指定的布局进行的绘制然后设置给了mDecor,其实也就是相当于理解为给顶层DecorView设置了一个布局,而这个布局是系统内置的,可以通过样式来指定加载哪些不同的布局文件。

第二行代码中,发现进行了一个findViewById操作,那这个ID是什么,点击去看一下

/**
 * The ID that the main layout in the XML layout file should have.
 */
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

通过注释可以获取到信息就是,这个id是主要的入口布局ID,并且必须有,在获取到这个contentParent之后,这个方法就将这个对象进行了返回,那这里就很疑问了,为什么这个id一定含有,我们通过layouttResource字段来看看之前加载的系统的布局文件。我们以系统中的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>

可以看出,这有一个布局id为content的FrameLayout,所以其实我们通过findviewViewById来获取的控件就是这个FrameLayout,通过查看系统中其他的布局文件,我们都能发现有一个ID为content的FrameLayout控件。所以是shuld have

那么这里我们一层层的返回,就会发现,其实PhoneWindow类中的mContentParent就是DecorView中的一个FrameLayout,在回到setContentView方法中,在对mContentParent进行初始化完成后,调用了mLayoutInflater.inflate(layoutResID, mContentParent);方法,这里的layoutResId就是我们传进来的布局ID,然后将布局进行填充添加到界面中,这样我们的setContentView的整个工作就完成了。

总结

看一张示意图

WX20190807-154652@2x.png

我们以R.layout.screen_simple.xml为例来进行讲解,当调用setContentView的时候,系统会先对DecorView进行判断,如果为空的话就初始化,初始化完DecorView之后,在对其布局进行一个初始化,这个布局会根据开发者指定的样式来指定不同的布局,但是每一个布局文件中都会有一个id为contentFrameLayout控件,初始化完DevorView的布局的时候,也会初始化这个FrameLayout,源码中的字段就是mContentParent,在拿到这个mContentParent之后,会将我们传入的布局文件加载到这个FrameLayout中,这样我们就能看见自己写的布局文件了

标签:初始化,setContentView,加上,布局,mContentParent,window,mDecor
来源: https://blog.csdn.net/u014697083/article/details/98765284