UI绘制流程一(setContentView之后做了哪些事)

228 阅读6分钟
我们在Activity中的onCreate方法中通过setContentView(R.layout.activity_main)就把我们的xml布局显示出来,那么Android系统具体帮我们做了那些事情呢,跟进源码
public class UIActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

我们发现setContentView是在getWindow类里去执行,但我们点进去发现getWindow是一个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 {}

通过注释我们了解到Window是一个承载View的区域,它的唯一实现类是PhoneWindow ,那么我们去该类中寻找setContentView方法

@Override
    public void setContentView(int layoutResID) {
        
        if (mContentParent == null) {
        //这里我们看到有个方法,跟进去
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        ...
    }
private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
        //创建DecorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();
            }
            ...
    }

跟进去我们看到有两个判空if (mDecor == null)和if (mContentParent == null),我们看一下这两个对象代表什么

// This is the top-level view of the window, containing the window decor.
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.
ViewGroup mContentParent;

通过注释我们了解到DecorView是Window(PhoneWindow)的第一级布局,mContentParent是一个ViewGroup,它可以是DecorView也可以是DecorView的子View,那么我们继续看源码

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());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        //可以看到这里是new出来decorView
        return new DecorView(context, featureId, this, getAttributes());
    }

再来回来看创建mContentParent

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

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();
            }
protected ViewGroup generateLayout(DecorView decor) {
        
        ...省略,我们只看关键代码...
        
        //这里发现开始inflate  Decorview
        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        ...
        
        else {
            // Embedded, so no decoration is needed.
            //这里的simple就是系统生成的布局,内部是线性布局
            //可以查看系统SDk下面的布局文件找到
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }
        
        //这里面把我们自己的Xml放到系统布局的framlayout中
        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        
        。。。
        //最终返回出来我们自己的xml
        return contentParent;
    }

可以看到这里根据不同的样式对应不同的布局layoutResource 我们看一下系统自己的XML布局screen_simple

我们发现这个布局是纵向的线性布局上面是个id为action_mode_bar_stub的ViewStub,下面是个id为content的FrameLayout,然后通过mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);加载进DecorView中,我们跟进去看一下

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        if (mBackdropFrameRenderer != null) {
            loadBackgroundDrawablesIfNeeded();
            mBackdropFrameRenderer.onResourcesLoaded(
                    this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                    mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                    getCurrentColor(mNavigationColorViewState));
        }

        mDecorCaptionView = createDecorCaptionView(inflater);
        //看到把刚才的一些布局inflate到View上
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {

            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

可以看到mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)最后addView到我们DecorView中,我们再回到PhoneWindow往下看代码

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

我们先按住Ctrl然后数遍移动到这个id上发现id号为16908290 这里通过findViewById生成ViewGroup,这个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就是我们上面看到的xml中的FrameLayout布局,然后return contentParent;把该布局返回出去。现在我们就了解到上文的mContentParent就是系统xml中的FrameLayout布局,到这里我们的installDecor方法跟进完了。继续回过头回到setContentView(int layoutResID)方法,往下看关键代码

//这里判断是否有反转动画
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
        //正常布局
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }

发现这里先判断是否有转场动画,这里的layoutResID就是我们自己的xml布局,layoutResID就是DecorView中的FrameLayout布局我们先看没有转场动画的,跟进去

**public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        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 {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

到这发现开始解析我们的xml文件,转场效果同理通过scene.enter();最终也进入inflate流程

总结一下:Activity中的setContentView是调用PhoneWindow中的setContentView。该方法中在installDecor,通过generateDecor生成DecorView,通过generateLayout,判断不同的样式添加进DecorView中,然后将系统的FrameLayout布局返回到mContentParent当中,然后把我们的自己的xml布局添加进该FrameLayout中

但是我们的Activity是继承Activity我们发现Android5.0之后系统默认继承AppCompatActivity,那么我们再来分析系统做了哪些改动

public class UIActivity extends AppCompatActivity {

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

跟进去

public void setContentView(@LayoutRes int layoutResID) {
        this.getDelegate().setContentView(layoutResID);
    }

发现我们进入到了AppCompataActivity类中,发现该类也是抽象类,在该类的上面我们找到该类的实现类AppCompatDelegateImpl,我们跟进去找到setContentView方法

 public void setContentView(int resId) {
        //跟进这个方法
        this.ensureSubDecor();
        //发现这个id跟content的id一样都是16908290
        ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
        contentParent.removeAllViews();
        LayoutInflater.from(this.mContext).inflate(resId, contentParent);
        this.mOriginalWindowCallback.onContentChanged();
    }

我们先看ensureSubDecor()

private void ensureSubDecor() {
        if (!this.mSubDecorInstalled) {
        //发现这里去创建一个subDecor
            this.mSubDecor = this.createSubDecor();
            。。。
        }
 }    
 
 
 private ViewGroup createSubDecor() {
            ...
            //往下寻找关键代码
            ...
            if (this.mOverlayActionMode) {
                    subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple_overlay_action_mode, (ViewGroup)null);
                } else {
                //这里的subDecor是一个ViewGroup
                    subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple, (ViewGroup)null);
                }
            ...
            ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);
                ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290);
                if (windowContentView != null) {
                    while(windowContentView.getChildCount() > 0) {
                        View child = windowContentView.getChildAt(0);
                        windowContentView.removeViewAt(0);
                        contentView.addView(child);
                    }
                    //这里把原来xml中的content的id设置为-1,把subDecorView里面的framlayout设置为原来的id
                    windowContentView.setId(-1);
                    contentView.setId(16908290);
                    if (windowContentView instanceof FrameLayout) {
                        ((FrameLayout)windowContentView).setForeground((Drawable)null);
                    }
                }
                //再把整个SubDecorView添加进Framlayout中
                this.mWindow.setContentView(subDecor);
    }

我们看到跟原来的方式差不多不过这个布局换成了abc...我们还看abc_screen_simple布局,该布局是在v7包下面

我们看到该布局结构跟原来的差不多都是线性布局下面一个ViewStub和一个FrameLayout,但是布局都加了修饰FitWindowsLinearLayoutViewStubCompatContentFrameLayout,这里是Android5.0谷歌做了适配,弥补原来的适配问题 我们接着分析

ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);

这里拿到我们看到的ContentFrameLayout,它的id名称为action_bar_activity_content,然后又通过ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290);拿到上面我们看到的熟悉的16908290的FrameLayout,然后遍历原来的Framelayout的子View,把它的子View添加到新的ContentFrameLayout中,然后把windowContentView.setId(-1);原来的Framelayout的id设为-1,把新的ContentFrameLayout的id设为16908290,最终通过this.mWindow.setContentView(subDecor);把新的subDecor添加到原来FrameLayout的位置 如图

总结::AppCompatActivity中的setContentView是在AppCompatDelegate的实现类AppCompatDelegateImpl类中调用,然后通过ensureSubDecor方法创建一个为subDecor的ViewGroup,subDecor里面的布局是在元布局上对适配的优化,是一个新的布局,然后把原来FrameLayout中的子View都添加到新的ContentFrameLayout中,然后把原来的content设置id为-1,新的mContent设置为原来的id,最后把整个subDecor替换了原来的FrameLayout的位置,我们自己的布局都是添加进了subDecor中的ContentFrameLayout。所以Android5.0之后的布局是在我们创建的布局外面有套了一层。