android中布局和View创建的源码分析---setContentView

android中布局和View创建的源码分析

因为我们使用的是拦截view创建的过程来实现插件换肤的功能,所以首先要熟悉android中创建视图的过程,下面让我们一起来分析下源码,这里我们从两个方面来分析。

一.设置布局分析

1.继承至Activity时,设置布局的情况

setContentView()的源码

public void setContentView(@LayoutRes int layoutResID) {    
    //重点看Window中setContentView方法
    getWindow().setContentView(layoutResID);
    ...
}

我们知道getWindow()获取的是PhoneWindow的实例,所以我们来看下PhoneWindow中setContentView()的代码:

@Override
public void setContentView(int layoutResID) {

    if (mContentParent == null) {
        //1.初始化decorView,给它添加布局,初始化mContentParent
        installDecor();
    } 

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        ...
    } else {
        //2.将我们自定义的布局添加到mContentParent中
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    ...
}

我们从PhoneWindow代码中可以看到,mLayoutInflater.inflate(layoutResID, mContentParent)只是将我们自定义的布局添加到系统提供的布局中,而installDecor()完成了所有初始化布局的工作,所以我们的核心是分析installDecor()中的代码,

private void installDecor() {
    ...

    if (mDecor == null) {
        //1.初始化decorView,很简单
        mDecor = generateDecor(-1);
        ...
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        //2.初始化mContentParent
        mContentParent = generateLayout(mDecor);

        ...
        } 

    ...

    }
}

installDecor其实也很简单,就是初始化decorView,然后在初始化mContentParent,我们先来看下初始化decorView的过程:

//其实就是实例化DecorView的对象
protected DecorView generateDecor(int featureId) {
    ...
    return new DecorView(context, featureId, this, getAttributes());
}

那么DecorView又是什么呢?

public class DecorView extends FrameLayout ... {
    ...
}

我们看到DecorView其实就是一个FrameLayout,就这么简单,下面我们一起看下初始化mContentParent的过程:

protected ViewGroup generateLayout(DecorView decor) {

    TypedArray a = getWindowStyle();

    //根据当前主题初始化数据
    ...

    //根据当前版本设置属性
    ...

    //重点!!!:填充DecorView

    int layoutResource;

    int features = getLocalFeatures();

    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        //根据不同的特征,填充不同的布局(比如,有title的)           
        ...    
    } else {
        //默认最简单的布局,我们就分析这个布局
        layoutResource = R.layout.screen_simple;
    }

    ...     

    //将上面得到的布局添加到DecorView中
    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

    //获得contentView,这个就是mContentParent
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

    ...

    return 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">
<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" />
//添加我们自定义布局的地方
<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>

看到这里我们应该心中有个大概的过程了,window包裹着DecorView,DecorView添加的是系统的布局(比如,screen_simple.xml),系统的布局中mContentParent又添加我们自定义的布局,用一个图表示就是:


2.继承AppCompatActivity时,设置布局的情况
@Override
public void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);
}

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

private static AppCompatDelegate create(Context context, Window window,
        AppCompatCallback callback) {
    final int sdk = Build.VERSION.SDK_INT;
    if (BuildCompat.isAtLeastN()) {
        return new AppCompatDelegateImplN(context, window, callback);
    } else if (sdk >= 23) {
        return new AppCompatDelegateImplV23(context, window, callback);
    } else if (sdk >= 14) {
        return new AppCompatDelegateImplV14(context, window, callback);
    } else if (sdk >= 11) {
        return new AppCompatDelegateImplV11(context, window, callback);
    } else {
        return new AppCompatDelegateImplV9(context, window, callback);
    }
}

可以看到AppCompatActivity中setContentView()兼容很多版本。

我们看源码就知道AppCompatDelegateImplN,AppCompatDelegateImplV23..最终继承的是AppCompatDelegateImplV9,所以来看下AppCompatDelegateImplV9源码:

@Override
public void setContentView(int resId) {
    //1.初始化mSubDecor
    ensureSubDecor();
    //2.初始化contentParent
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    //3.添加我们的布局到    contentParent中
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mOriginalWindowCallback.onContentChanged();
}

private void ensureSubDecor() {
    if (!mSubDecorInstalled) {
        mSubDecor = createSubDecor();

       ...
        onSubDecorInstalled(mSubDecor);

       ...
    }
}

private ViewGroup createSubDecor() {

    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;

    //初始化subDecor
    subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);

    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);


    // Now set the Window's content view with the decor
    //将subDecor添加到decorView中
    mWindow.setContentView(subDecor);


    return subDecor;
}

我们看到AppCompatDelegateImplV9中的setContentView()的做的事情和PhoneWindow中是一样的,只是多了一层subDecor而已。

通过上面的分析,我们发现AppCompatActivity只是多了兼容的功能和布局多了subDecor,最后也是通过LayoutInflater.from(mContext).inflate(resId, contentParent);来添加我们自定义的布局,那么为什么继承Activity和AppCompatActivity显示的界面不一样?会不会是inflate()方法引起的呢?我们继续来分析。

二.创建View代码分析

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();

    final XmlResourceParser parser = res.getLayout(resource);
    try {
        //我们重点来分析这个地方
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}


public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        ...
        View result = root;

        try {

            //xml解析
            ...


                //重点:创建view
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;


                // 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);
                }

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

        } catch (Exception e) {

        } finally {

        }

        return result;
    }
}

我们看到创建View的地方就在createViewFromTag中,这边也是导致界面显示不一样的原因所在。

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

    ...

    try {
        View view;
        if (mFactory2 != null) {
            //第一个创建view的地方
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            //第二个创建view的地方
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }

        if (view == null && mPrivateFactory != null) {
            //第三个创建view的地方
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        if (view == null) {

            try {
                //第四个创建view的地方
                if (-1 == name.indexOf('.')) {
                    //创建系统的view
                    view = onCreateView(parent, name, attrs);
                } else {
                    //创建自定义的view
                    view = createView(name, null, attrs);
                }
            } 
        }

        return view;
    } 
}

大家可以看到,上面有四种方式创建View,分别是mFactory2,mFactory,mPrivateFactory,默认方式,
这边就是导致Activity和AppCompatActivity显示不同的原因:

1. 如果是继承Activity,那么就使用默认的创建View的方式。
2. 如果是继承AppCompatActivity,那么就使用mFactory创建View的方式。

下面我们来证明上面的结论。

首先来看下Activity,因为Activity中没有设置mFactory2,mFactory,mPrivateFactory,所以它们都为null,所以直接调用默认创建View的方式。

再来看下AppCompatActivity是怎么给mFactory赋值的:

AppCompatActivity->onCreate
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    final AppCompatDelegate delegate = getDelegate();
    //这边就是设置mFactory的地方
    delegate.installViewFactory();

    ...

}

上面注释的地方就是设置mFactory的地方,我们往下看:

AppCompatDelegateImplV9->installViewFactory

@Override
public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
    if (layoutInflater.getFactory() == null) {
        //设置mFactory的地方
        LayoutInflaterCompat.setFactory(layoutInflater, this);
    } else {
       ...
    }
}

我们找到最终设置mFactory的地方在LayoutInflaterCompatBase中

static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
    inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null);
}

LayoutInflater->setFactory

public void setFactory(Factory factory) {

    if (mFactory == null) {
        mFactory = factory;
    } else {
        mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
    }
}

我们看到在这边设置了mFactory.

class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
    implements MenuBuilder.Callback, LayoutInflaterFactory{

    @Override
    public final View onCreateView(View parent, String name,
        Context context, AttributeSet attrs) {

    return createView(parent, name, context, attrs);
}
}

在看上面的LayoutInflaterCompat.setFactory(layoutInflater, this);
可以看到mFactory.onCreateView()其实调用的就是AppCompatDelegateImplV9中的onCreateView()方法。

我们再来分析createView()方法:

public final View createView(View parent, final String name, @NonNull Context context,
        @NonNull AttributeSet attrs, boolean inheritContext,
        boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
    final Context originalContext = context;


    if (inheritContext && parent != null) {
        context = parent.getContext();
    }


    View view = null;
    switch (name) {
        case "TextView":
            view = new AppCompatTextView(context, attrs);
            break;
        case "ImageView":
            view = new AppCompatImageView(context, attrs);
            break;
        case "Button":
            view = new AppCompatButton(context, attrs);
            break;
        case "EditText":
            view = new AppCompatEditText(context, attrs);
            break;
        case "Spinner":
            view = new AppCompatSpinner(context, attrs);
            break;
        case "ImageButton":
            view = new AppCompatImageButton(context, attrs);
            break;
        case "CheckBox":
            view = new AppCompatCheckBox(context, attrs);
            break;
        case "RadioButton":
            view = new AppCompatRadioButton(context, attrs);
            break;
        case "CheckedTextView":
            view = new AppCompatCheckedTextView(context, attrs);
            break;
        case "AutoCompleteTextView":
            view = new AppCompatAutoCompleteTextView(context, attrs);
            break;
        case "MultiAutoCompleteTextView":
            view = new AppCompatMultiAutoCompleteTextView(context, attrs);
            break;
        case "RatingBar":
            view = new AppCompatRatingBar(context, attrs);
            break;
        case "SeekBar":
            view = new AppCompatSeekBar(context, attrs);
            break;
    }

    if (view == null && originalContext != context) {

        view = createViewFromTag(context, name, attrs);
    }

    if (view != null) {

        checkOnClickListener(view, attrs);
    }

    return view;
}

可以看到AppCompatActivity显示的是view的兼容类(AppCompatTextView),这就导致了Activity和AppCompatActivity显示界面的不一样。

下面我们来看下Activity中创建View的代码:

LayoutInflater->createView

public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    //1.获取view的构造函数(反射) ,为了提高性能,这边做了缓存
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    if (constructor != null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    }
    Class<? extends View> clazz = null;

    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

        if (constructor == null) {
            //创建构造函数(反射)
            clazz = mContext.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name).asSubclass(View.class);

            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
        } 

        ...

        Object[] args = mConstructorArgs;
        args[1] = attrs;
        //反射调用构造函数,创建view实例
        final View view = constructor.newInstance(args);

        return view;

    } 
    } catch (Exception e) {

    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

就边就是通过反射实例化View对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值