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

来源:互联网 发布:台湾中华电信网络制式 编辑:程序博客网 时间:2024/05/20 23:57

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

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

一.设置布局分析

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

setContentView()的源码

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

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

@Overridepublic 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时,设置布局的情况

@Overridepublic void setContentView(@LayoutRes int layoutResID) {    getDelegate().setContentView(layoutResID);}@NonNullpublic 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源码:

@Overridepublic 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
@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {    final AppCompatDelegate delegate = getDelegate();    //这边就是设置mFactory的地方    delegate.installViewFactory();    ...}

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

AppCompatDelegateImplV9->installViewFactory

@Overridepublic 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对象。

阅读全文
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 注册消防工程师考试难度 一级消防工程师考试题型 注册二级消防工程师 注册消防工程师论坛 一级注册消防工程师教材 注册消防工程师报名 二级消防工程师考试科目 消防工程师报考条件二级 二级注册消防工程师报考条件 一级注册消防工程师论坛 二级消防工程师好考吗 一级注册消防工程师招聘 注册消防工程师难考吗 考一级消防工程师有用吗 消防工程师难度 一消防工程师 一级消防工程师考试难度 一级消防工程师好考吗 消防工程师待遇 注册消防工程师一级二级区别 一级注册消防工程师好考吗 注册消防工程师好不好考 一级消防工程师难度 二级注册消防工程师考试科目 二级消防考试时间 一级消防工程师招聘 陕西二级消防工程师报名时间 2017消防工程师考试时间 消防工程师代报名 消防技术综合能力 一级注册消防工程师报考条件 河北二级消防工程师报名时间 二级消防工程师报名条件 消防员考试报考条件 消防技术实务 一级注册消防工程师报名时间 消防安全工程师考试时间 消防注册工程师报考条件 2017一级消防工程师报名时间 二级消防考试科目 辽宁消防工程师报名时间