android高级UI布局层次结构及布局加载流程源码解析

Posted Android Developer

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android高级UI布局层次结构及布局加载流程源码解析相关的知识,希望对你有一定的参考价值。

我们写的布局位于DecorView中,DecorView是PhoneWindow的成员变量,而PhoneWindow又是Activity的成员变量,我们称这为布局的层级结构,下面我们从源码出发看看是如何形成的;

一、布局层次结构分析

1、setContentView分析

当调用到我们的Activity的onCreate生命周期方法中后,接着会调用setContentView方法将我们传入的布局显示在界面上,那么这一过程如何实现呢?

相关方法调用如下所示。setContentView方法会调用父类Activity的setContentView方法,注释1处会调用getWindow的setContentView;注释2处是getWindow的实现,返回mWindow,mWindow是Window类型的变量;注释3处对Activity的成员变量mWindow进行赋值,PhoneWindow是window的唯一实现类,那么这个attach方法是在哪里调用的呢?就是在ActivityThread的performLaunchActivity方法中调用,其实就是AMS和activty的启动流程中调用的,我们在后边会单独进行分析。

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

public Window getWindow() {
    return mWindow; //2
}

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,
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
    attachBaseContext(context);

    mWindow = new PhoneWindow(this, window, activityConfigCallback); //3
    、、、
}
复制代码

2、mWindow.setContentView(layoutResID)

通过上面我们知道最终调用的是PhoneWindow的setContentView,方法如下所示,主要干了两件事情,注释1用于生成DecorView和mContentParent;注释2处用于将layoutResID(也就是我们实现的布局id)设置到mContentParent中去;

public void setContentView(int layoutResID) {           
    if (mContentParent == null) {
        installDecor(); //1
    }
    、、、
    mLayoutInflater.inflate(layoutResID, mContentParent);//2            
    、、、
}
复制代码

3、installDecor()

installDecor方法代码如下所示,注释1处通过generateDecor方法生成mDecor变量,generateDecor方法内部其实就是new了一个DecorView;注释2处通过generateLayout方法获得mContentParent;mDecor是PhoneWindow的成员变量,是DecorView类型的,继承自FrameLayout;而这个mContentParent代表mDecor本身或者mDecor的子布局,什么意思呢?其实在mDecor的上部有一个占位View,根据不同的主题加载不同的DecorView,如果没有加载顶部view那mContentParent就是mDecor了;

private void installDecor() {
    、、、
    mDecor = generateDecor(-1); //1
    、、、
    mDecor.setWindow(this);
    、、、
    mContentParent = generateLayout(mDecor); //2
}
复制代码

4、布局层次

经过上面的分析,我们发现Activity持有一个PhoneWindow的成员变量;PhoneWindow持有一个DecorView的成员变量,DecorView内部持有一个mContentParent,所以布局层次结构就是 Activity – PhoneWindow – DecorView – mContentParent;

而我们写的xml布局就是放在了mContentParent中;那么xml布局是如何加载到mContentParent容器的呢?其实也就是步骤2的注释2的mLayoutInflater.inflate(layoutResID, mContentParent)进行加载的,我们在下一部分分析加载的流程;

二、布局加载流程分析

1、mLayoutInflater.inflate(layoutResID, mContentParent)的调用链如下所示;

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null); //1
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot); //2
    } finally {
        parser.close();
    }
}
复制代码

2、最终会调用到如下inflate方法,注释1处将root赋值给result;注释2处会通过createViewFromTag创建根View;注释4处用于inflate所有的children;这里解释一下最后一个attachToRoot参数,如果设置为true,会调用注释5将根view添加到root中然后将root返回;如果设置为false,会调用到注释3处,将root的参数设置到根view中去,但并不会执行addView操作,然后调用到注释6处将根view赋值给result,这样返回的就是根view而不是root;

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    View result = root; //1

    final String name = parser.getName();

    // Temp is the root view that was found in the xml
    final View temp = createViewFromTag(root, name, inflaterContext, attrs); //2

    ViewGroup.LayoutParams params = null;

    if (root != null) {

        // Create layout params that match root, if supplied
        params = root.generateLayoutParams(attrs);
        if (!attachToRoot) {
            // Set the layout params for temp if we are not
            // attaching. (If we are, we use addView, below)
            temp.setLayoutParams(params); //3
        }
    }

    // Inflate all children under temp against its context.
    rInflateChildren(parser, temp, attrs, true); //4

    // We are supposed to attach all the views we found (int temp)
    // to root. Do that now.
    if (root != null && attachToRoot) {
        root.addView(temp, params); //5
    }

    // Decide whether to return the root that was passed in or the
    // top view found in xml.
    if (root == null || !attachToRoot) {
        result = temp; //6
    }
    return result;  
}
复制代码

3、通过createViewFromTag创建根view,此方法调用链如下所示;注释1处是第一次创建view;注释2和注释3最终都会调用到注释3处的方法;注释1是一种情况,注释3是另一种情况,我们分别讨论一下;

private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
    return createViewFromTag(parent, name, context, attrs, false);
}

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {

    View view = tryCreateView(parent, name, context, attrs); //1

    if (view == null) {
        final Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = context;
        try {
            if (-1 == name.indexOf('.')) {
                view = onCreateView(context, parent, name, attrs); //2
            } else {
                view = createView(context, name, null, attrs); //3
            }
        }
    }
    return view;        
}
复制代码

3-1 tryCreateView

第一种情况tryCreateView方法如下所示,如果mFactory2不为空,会通过注释1处mFactory2的onCreateView方法创建view;如果mFactory不为空,会通过注释2处mFactory的onCreateView创建view;那么这两个factory是什么呢?如注释3和注释4所示,Factory是一个单方法的接口,Factory2继承自Factory,重载了一个onCreateView方法;这两个变量初始都为空,因此我们开发者可以实现这两个接口,然后赋值给这两个变量,这样就会优先通过我们设置的方法加载view了;

public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {       
    View view;
    if (mFactory2 != null) {
        view = mFactory2.onCreateView(parent, name, context, attrs); //1
    } else if (mFactory != null) {
        view = mFactory.onCreateView(name, context, attrs); //2
    } else {
        view = null;
    }
    return view;
}

private Factory mFactory; //3
private Factory2 mFactory2; //4
public interface Factory {        
    View onCreateView(@NonNull String name, @NonNull Context context,
            @NonNull AttributeSet attrs);
}
public interface Factory2 extends Factory {       
    View onCreateView(@Nullable View parent, @NonNull String name,
    @NonNull Context context, @NonNull AttributeSet attrs);
}
复制代码

3-2 createView

因为系统并没有给mFactory和mFactory2赋值,因此会走到createView方法;方法代码如下所示,注释1处首先从sConstructorMap获取构造方法,第一次肯定为空;然后走到注释2处,通过反射获得构造方法,mConstructorSignature这个参数代表的是两个参数的构造方法,因此返回的就是带两个参数的构造方法;接着在注释3处将构造方法放入sConstructorMap中;最后在注释4处通过newInstance方法获取view实例并返回;

public final View createView(@NonNull Context viewContext, @NonNull String name,
    @Nullable String prefix, @Nullable AttributeSet attrs)
    throws ClassNotFoundException, InflateException {            
    Constructor<? extends View> constructor = sConstructorMap.get(name); //1

    if (constructor == null) {
        constructor = clazz.getConstructor(mConstructorSignature); //2
        constructor.setAccessible(true);
        sConstructorMap.put(name, constructor); //3
    }

    Object lastContext = mConstructorArgs[0];
    mConstructorArgs[0] = viewContext;
    Object[] args = mConstructorArgs;
    args[1] = attrs;

    final View view = constructor.newInstance(args); //4           
    return view;                
}
复制代码

三、androidX的AppCompatActivity源码变化

1、当我们的Activity继承自AppCompatActivity时,setContentView方法会调用到AppCompatActivity的setContentView;因此最终调用的是AppCompatDelegateImpl的setContentView;

AppCompatActivity -- setContentView

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

AppCompatActivity -- getDelegate

public AppCompatDelegate getDelegate() {
    if (mDelegate == null) {
        mDelegate = AppCompatDelegate.create(this, this);
    }
    return mDelegate;
}

AppCompatDelegate -- create

public static AppCompatDelegate create(@NonNull Activity activity,
        @Nullable AppCompatCallback callback) {
    return new AppCompatDelegateImpl(activity, callback);
}
复制代码

2、AppCompatDelegateImpl的setContentView方法如下所示,注释1处的ensureSubDecor()最终还是会调用到installDecor方法;注释2处用于获得contentParent;注释3处用于将我们的布局添加到contentParent中;

public void setContentView(int resId) {
    ensureSubDecor(); //1
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); //2
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);//3
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}
复制代码

本文在开源项目:https://github.com/Android-Alvin/Android-LearningNotes 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

以上是关于android高级UI布局层次结构及布局加载流程源码解析的主要内容,如果未能解决你的问题,请参考以下文章

Android 高级面试题及答案

Android View之布局加载流程

Android Studio 4.0+ 中新的 UI 层次结构调试工具

Android优化——UI优化优化布局层次

Android之UI View与ViewGroup

[Android]Android布局优化之 merge