android中布局和View创建的源码分析---setContentView
2017-05-19 12:56
791 查看
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 c24e 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对象。
相关文章推荐
- Android布局文件的加载过程分析:Activity.setContentView()源码分析
- [置顶] android源码分析——由SetContentView串起来的布局加载机制
- Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起
- Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起
- Android应用setContentView与LayoutInflater加载解析机制源码分析
- 【转载】Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起
- Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起
- Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起
- Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起
- Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起
- Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起
- Android应用setContentView与LayoutInflater加载解析机制源码分析
- Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起
- Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起
- Android应用setContentView与LayoutInflater加载解析机制源码分析(转载)
- Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起
- Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起
- Android应用setContentView与LayoutInflater加载解析机制源码分析
- Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起
- Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起(写的很好,这个不是从启动app说的,说的是UI是怎么绘制的)