Android中view的显示原理之Activity,Window,DecorView,布局视图之间的联系

在Activity中要显示界面,相信大家都知道怎么做了。在Activity中的生命周期onCreate方法中调用setContentView(int layoutResID)即可,其中layoutResID就是当前要显示的布局资源id。今天就来分析下为什么调用该方法就会显示出对应的布局呢。那么下面我们就开始发车了。。。
Android中view的显示原理之Activity,Window,DecorView,布局视图之间的联系_第1张图片

源码分析

在Activity中会调用setContentView(int layoutResID),那么进入该方法。

/**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        //这里调用了一个getWindow()方法,返回的是一个window对象,然后再调用该
    	window对象的setContentView(layoutResID)方法
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

这里调用了getWindow()方法的setContentView(layoutResID),那么就来看看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.
     */
     这里看注释是返回一个window给当前activity
    public Window getWindow() {
        return mWindow;
    }

这里返回的是一个成员变量mWindow,它的返回值类型是Window ,进入Window 类接着看。

/**
 * The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
 //这里可以看出Window是个抽象类,并且它的唯一子类是PhoneWindow
public abstract class Window 

那么接着看看它的唯一子类PhoneWindow中的setContentView(layoutResID)。

public void setContentView(int layoutResID) {
          if (mContentParent == null) {
            installDecor();
        }
       		***
            mLayoutInflater.inflate(layoutResID, mContentParent);
            ***
      }

可以看到PhoneWindow中的setContentView(layoutResID)中调用了对mContentParent这个变量进行了判断,若为null则会调用installDecor(),那么进入该方法一探究竟。

private void installDecor() {
        ***
        if (mDecor == null) {
        	//这里创建了一个mDecor对象
            mDecor = generateDecor(-1);
        } 
        ***
        if (mContentParent == null) {
        	//这里创建了一个mContentParent 对象
            mContentParent = generateLayout(mDecor);
            }
            ***
 }

这个方法主要创建了两个对象一个是mDecor ,一个是mContentParent ,我们接着看看这两个对象的数据类型。

	// This is the top-level view of the window, containing the window decor.
	//看到这里估计很多童鞋要激动了 ,这个不是我们常说的DecorView吗! 就那个window的顶层					     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是用来放置window内容的。
    ViewGroup mContentParent;

从注释里可以看出,mDecor就是仅次于Window的顶层view,mContentParent是用来放置Window内容的,那么mDecor与mContentParent到底有什么联系呢?我猜测mDecor应该包含了mContentParent(即mDecor中添加了mContentParent),我们继续看generateLayout(mDecor)。

protected ViewGroup generateLayout(DecorView decor) {
	***
	int layoutResource;
	***
	//这里说明下  此处源码太多,只列出了一个条件。
	大概意思就是根据主题的不同,选择不同的资源文件 
	if(***){
	  layoutResource =R.layout.screen_simple;
	}
      mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
       *** 
       ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)
       ***
       return contentParent ;
}

generateLayout()方法就是根据不同的主题选择不同的资源文件,这些资源文件都是系统自带的。这里我们看下R.layout.screen_simple这个资源文件它的布局是怎样的。


    
    

从布局文件可以看出是个线性布局,上面是一个ViewStub ,下面是一个FrameLayout 对应id为@android:id/content。我们接着上述源码继续看,选择了资源文件后会调用mDecor.onResourcesLoaded(mLayoutInflater, layoutResource),调用上面传进来的decorView的onResourcesLoaded方法,传入获取到的资源文件id。

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        ***
        //这里调用了inflater.inflate方法加载资源文件
        final View root = inflater.inflate(layoutResource, null);
        ***
        //将加载的资源文件对象view添加到decorView中
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT))
        ***
 }

在上述方法里加载资源文件获取到view,并将该view添加到decorView中。回到
generateLayout(DecorView decor)方法中,在该方法中调用了mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)方法后,还执行了
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)这行代码,这里看下ID_ANDROID_CONTENT这个id对应的是哪个布局的id。

/**
     * The ID that the main layout in the XML layout file should have.
     */
     //注释说这个id是xml主布局必须有的
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

到这里我们似乎明白了点什么,之前加载的布局资源中有过这个id(即@android:id/content),这个id对应的View的数据类型应该是FrameLayout 。在上述generateLayout(mDecor)方法中执行的逻辑就是根据不同的主题加载布局资源文件,并将该资源文件视图添加到decorView中,然后将该decorView中id为com.android.internal.R.id.content的View返回,返回的View即为mContentParent。这也印证了之前的猜想,mDecor中添加了mContentParent。
至此,phoneWindow中的setContentView(int layoutResID)方法调用 installDecor()就分析完了。接下来接着分析mLayoutInflater.inflate(layoutResID, mContentParent)。

//会调用LayoutInflater的inflate
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
		//这里会调用inflate(resource, root, root != null)
        return inflate(resource, root, root != null);
    }
 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {	
        	//调用 inflate(parser, root, attachToRoot)
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
      			***
                // Temp is the root view that was found in the xml
				final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                // Create layout params that match root, if supplied
                params = root.generateLayoutParams(attrs);
				root.addView(temp, params);
     }            

上述代码流程相对简单,最后会调用mLayoutInflater.inflate(layoutResID, mContentParent)方法中传进来的layoutResID加载视图,并将该视图添加到mContentParent。
至此整个view的加载流程就分析完毕了。

总结

在Activity中调用setContentView(int layoutResID)会调用到PhoneWindow中的setContentView(int layoutResID)方法,而PhoneWindow中则会创建一个decorView,加载当前主题对应的布局视图,并将该视图添加到decorView中,最后加载setContentView(int layoutResID)中传入的布局资源id对应的视图,并将该视图添加到decorView中的子视图中。最后来张图解,相信童鞋们就会更加明白整个流程了。
Android中view的显示原理之Activity,Window,DecorView,布局视图之间的联系_第2张图片
在下水平有限,若文中有错,还请不吝赐教。

你可能感兴趣的:(Android中view的显示原理之Activity,Window,DecorView,布局视图之间的联系)