学习是一个过程。
文章目录
- Activity中LayoutInflater加载布局总体时序图
- LayoutInflater源码讲解(api28)
- LayoutInflater设置Factory2
- 实现方式
- LayoutInflater源码总结
Activity中LayoutInflater加载布局总体时序图
LayoutInflater源码讲解(api28)
-
onCreate加载布局,是不是都很熟悉。
@Override protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_happy); }
-
AppCompatActivity的onCreate()方法,注意这不是Activity的onCreate方法。
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { final AppCompatDelegate delegate = getDelegate(); //注意此方法是做换肤的关键。 delegate.installViewFactory(); delegate.onCreate(savedInstanceState); super.onCreate(savedInstanceState); }
-
AppCompatDelegatelmpl的installViewFactory方法,AppCompatDelegatelmpl实现了Factory2接口。
public void installViewFactory() { LayoutInflater layoutInflater = LayoutInflater.from(mContext); if (layoutInflater.getFactory() == null) { //AppCompatDelegatelmpl实现了Factory2接口 LayoutInflaterCompat.setFactory2(layoutInflater, this); } else { if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) { Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed" + " so we can not install AppCompat's"); } } }
-
LayoutInflater中的setFactory2方法,此方法不允许重复设置值,如果设置值会产生异常,所以如果做动态换肤设置Factory2时,要放在super.onCreate()方法之前,防止异常退出。
public void setFactory2(Factory2 factory) { //从这可以看出factory是不可以重复设置值的,如果重复设置会产生异常。 if (mFactorySet) { throw new IllegalStateException("A factory has already been set on this LayoutInflater"); } if (factory == null) { throw new NullPointerException("Given factory can not be null"); } mFactorySet = true; //mFactory与mFactory2一块赋值,mFractory2是按照扩展的方法进行开发的。 if (mFactory == null) { mFactory = mFactory2 = factory; } else { mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2); } }
-
继续分析setContentView()方法,AppCompatActivity中的setContentView调用的是AppCompatDelegatelmpl的方法。
@Override public void setContentView(@LayoutRes int layoutResID) { getDelegate().setContentView(layoutResID); }
-
AppCompatDelegatelmpl的setContentView方法,此方法主要是加载我们自定义的布局,将布局添加到容器中。
public void setContentView(int resId) { //主要是初始化根布局,用来存放我们自定义的布局。 ensureSubDecor(); //存放我们自定义布局的View,此View的类型是FrameLayout ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); contentParent.removeAllViews(); //加载自定义布局,将布局添加到contentParent中。 LayoutInflater.from(mContext).inflate(resId, contentParent); mAppCompatWindowCallback.getWrapped().onContentChanged(); }
-
LayoutInflater的inflate方法,此方法中有以后插件化用到的关键代码,此处先留意一下,以后有机会再进行分享插件化相关的技术。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { //此处是做插件化的关键,activity自定义getResources()方法,用来生产插件对应的资源。 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(); } }
-
继续分析inflate方法,其重要流程是创建根布局,然后创建
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; View result = root; try { // Look for the root node. int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); if (DEBUG) { System.out.println("**************************"); System.out.println("Creating root view: " + name); System.out.println("**************************"); } //处理 merge 标签 if (TAG_MERGE.equals(name)) { if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } rInflate(parser, root, inflaterContext, attrs, false); } else { // Temp is the root view that was found in the xml //自定义View的根布局,就是自己写的布局的根布局。 final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // 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); } } if (DEBUG) { System.out.println("-----> start inflating children"); } // Inflate all children under temp against its context. //把自定义的xml所有除根布局之外的控件全部实例化然后添加进根布局 rInflateChildren(parser, temp, attrs, true); if (DEBUG) { System.out.println("-----> done inflating children"); } // 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 (XmlPullParserException e) { final InflateException ie = new InflateException(e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (Exception e) { final InflateException ie = new InflateException(parser.getPositionDescription() + ": " + e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } return result; } }
-
LayoutInflater的createViewFromTag方法,注意这里有一个BlinkLayout的闪烁小彩蛋,用来闪烁布局。
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } // Apply a theme wrapper, if allowed and one is specified. if (!ignoreThemeAttr) { final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); if (themeResId != 0) { context = new ContextThemeWrapper(context, themeResId); } ta.recycle(); } ``//闪烁的菜单,是为了庆祝??? 1995年庆祝什么节日? if (name.equals(TAG_1995)) { // Let's party like it's 1995! return new BlinkLayout(context, attrs); } try { View view; //如果是AppCompatActivity在这初始化,这个可以自己创建View,可以实现动态换肤。 if (mFactory2 != null) { view = mFactory2.onCreateView(parent, name, context, attrs); } else if (mFactory != null) { view = mFactory.onCreateView(name, context, attrs); } else { view = null; } if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, context, attrs); } //如果是Activity在这初始化。 if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { //不带包路径的View,最终都会调用到createView的这个方法 if (-1 == name.indexOf('.')) { //这个最终会调用createView(name,“android.view.”,attrs),携带android.view前缀。 view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; } catch (InflateException e) { throw e; } catch (ClassNotFoundException e) { final InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + name, e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (Exception e) { final InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + name, e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } }
-
我们跟踪一下Layoutlnflater的createView方法,
//利用反射创建对象,为啥不直接new对象呢?因为有些不能访问到? public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { 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) { // Class not found in the cache, see if it's real, and try to add it clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); if (mFilter != null && clazz != null) { boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { failNotAllowed(name, prefix, attrs); } } constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); //为了增加效率,增加了缓存。 sConstructorMap.put(name, constructor); } else { // If we have a filter, apply it to cached constructor if (mFilter != null) { // Have we seen this name before? Boolean allowedState = mFilterMap.get(name); if (allowedState == null) { // New class -- remember whether it is allowed clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); boolean allowed = clazz != null && mFilter.onLoadClass(clazz); mFilterMap.put(name, allowed); if (!allowed) { failNotAllowed(name, prefix, attrs); } } else if (allowedState.equals(Boolean.FALSE)) { failNotAllowed(name, prefix, attrs); } } } Object lastContext = mConstructorArgs[0]; if (mConstructorArgs[0] == null) { // Fill in the context if not already within inflation. mConstructorArgs[0] = mContext; } Object[] args = mConstructorArgs; args[1] = attrs; final View view = constructor.newInstance(args); if (view instanceof ViewStub) { // Use the same context when inflating ViewStub later. final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } mConstructorArgs[0] = lastContext; return view; } catch (NoSuchMethodException e) { final InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (ClassCastException e) { // If loaded class is not a View subclass final InflateException ie = new InflateException(attrs.getPositionDescription() + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (ClassNotFoundException e) { // If loadClass fails, we should propagate the exception. throw e; } catch (Exception e) { final InflateException ie = new InflateException( attrs.getPositionDescription() + ": Error inflating class " + (clazz == null ? "<unknown>" : clazz.getName()), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
-
继续分析mFactory2.onCreateView()的方法,其最终会调用到AppCompatDelegatelmpl的createView(),此函数主要对mAppCompatViewInflater进行初始化,然后调用其createView()方法。
public View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (mAppCompatViewInflater == null) { TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme); String viewInflaterClassName = a.getString(R.styleable.AppCompatTheme_viewInflaterClass); if ((viewInflaterClassName == null) || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) { // Either default class name or set explicitly to null. In both cases // create the base inflater (no reflection) mAppCompatViewInflater = new AppCompatViewInflater(); } else { try { Class<?> viewInflaterClass = Class.forName(viewInflaterClassName); mAppCompatViewInflater = (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor() .newInstance(); } catch (Throwable t) { Log.i(TAG, "Failed to instantiate custom view inflater " + viewInflaterClassName + ". Falling back to default.", t); mAppCompatViewInflater = new AppCompatViewInflater(); } } } boolean inheritContext = false; if (IS_PRE_LOLLIPOP) { inheritContext = (attrs instanceof XmlPullParser) // If we have a XmlPullParser, we can detect where we are in the layout ? ((XmlPullParser) attrs).getDepth() > 1 // Otherwise we have to use the old heuristic : shouldInheritContext((ViewParent) parent); } return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */ true, /* Read read app:theme as a fallback at all times for legacy reasons */ VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */ ); }
-
AppCompatViewInflater的createView()来创建View,其最终都转换为AppCompat对应的组件,
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; // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy // by using the parent's context if (inheritContext && parent != null) { context = parent.getContext(); } if (readAndroidTheme || readAppTheme) { // We then apply the theme on the context, if specified context = themifyContext(context, attrs, readAndroidTheme, readAppTheme); } if (wrapContext) { context = TintContextWrapper.wrap(context); } View view = null; // We need to 'inject' our tint aware Views in place of the standard framework versions //这里对View进行转换,自动转换为AppCompat对应的View。 switch (name) { case "TextView": view = createTextView(context, attrs); verifyNotNull(view, name); break; case "ImageView": view = createImageView(context, attrs); verifyNotNull(view, name); break; case "Button": view = createButton(context, attrs); verifyNotNull(view, name); break; case "EditText": view = createEditText(context, attrs); verifyNotNull(view, name); break; case "Spinner": view = createSpinner(context, attrs); verifyNotNull(view, name); break; case "ImageButton": view = createImageButton(context, attrs); verifyNotNull(view, name); break; case "CheckBox": view = createCheckBox(context, attrs); verifyNotNull(view, name); break; case "RadioButton": view = createRadioButton(context, attrs); verifyNotNull(view, name); break; case "CheckedTextView": view = createCheckedTextView(context, attrs); verifyNotNull(view, name); break; case "AutoCompleteTextView": view = createAutoCompleteTextView(context, attrs); verifyNotNull(view, name); break; case "MultiAutoCompleteTextView": view = createMultiAutoCompleteTextView(context, attrs); verifyNotNull(view, name); break; case "RatingBar": view = createRatingBar(context, attrs); verifyNotNull(view, name); break; case "SeekBar": view = createSeekBar(context, attrs); verifyNotNull(view, name); break; case "ToggleButton": view = createToggleButton(context, attrs); verifyNotNull(view, name); break; default: // The fallback that allows extending class to take over view inflation // for other tags. Note that we don't check that the result is not-null. // That allows the custom inflater path to fall back on the default one // later in this method. view = createView(context, name, attrs); } //如果不是以上对应的View,则调用以下方法进行创建。 if (view == null && originalContext != context) { // If the original context does not equal our themed context, then we need to manually // inflate it using the name so that android:theme takes effect. view = createViewFromTag(context, name, attrs); } if (view != null) { // If we have created a view, check its android:onClick checkOnClickListener(view, attrs); } return view; }
-
AppCompatViewInflater的createViewFromTag()方法,其与LayoutInflater的createViewFromTag方法有些类似,
private View createViewFromTag(Context context, String name, AttributeSet attrs) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } try { mConstructorArgs[0] = context; mConstructorArgs[1] = attrs; if (-1 == name.indexOf('.')) { //尝试增加不同的前缀进行创建,用反射,如果 反射失败则加载不成功,再重新尝试。 for (int i = 0; i < sClassPrefixList.length; i++) { final View view = createViewByPrefix(context, name, sClassPrefixList[i]); if (view != null) { return view; } } return null; } else { return createViewByPrefix(context, name, null); } } catch (Exception e) { // We do not want to catch these, lets return null and let the actual LayoutInflater // try return null; } finally { // Don't retain references on context. mConstructorArgs[0] = null; mConstructorArgs[1] = null; } }
-
尝试用不同的前缀反射创建View,到了这里View就创建成功了。
private View createViewByPrefix(Context context, String name, String prefix) throws ClassNotFoundException, InflateException { Constructor<? extends View> constructor = sConstructorMap.get(name); try { if (constructor == null) { // Class not found in the cache, see if it's real, and try to add it Class<? extends View> clazz = Class.forName( prefix != null ? (prefix + name) : name, false, context.getClassLoader()).asSubclass(View.class); constructor = clazz.getConstructor(sConstructorSignature); //为了增加速度,这里也尝试了缓存的方式。 sConstructorMap.put(name, constructor); } constructor.setAccessible(true); return constructor.newInstance(mConstructorArgs); } catch (Exception e) { // We do not want to catch these, lets return null and let the actual LayoutInflater // try return null; } }
LayoutInflater设置Factory2
-
在onCreateView中设置Factory2,通过回调函数我们能拿到View的名称(注意,我们连系统的根布局的名称也是可以拿到的),同事也可以拿到其对应的属性值,这样我们就可以根据这些值来创建我们的View,在这里也可以动态的设置我们想要的皮肤。
@Override protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) { getLayoutInflater().setFactory2(new LayoutInflater.Factory2() {//这里主要负责View的创建。 @Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { Log.e(TAG, "name: " + name); int attributeCount = attrs.getAttributeCount(); for (int i = 0; i < attributeCount; i++) { String attributeName = attrs.getAttributeName(i); Log.e(TAG, "attributeName: " + attributeName); } Log.e(TAG, "------------------------------------divide line------------------- "); return null; } @Override public View onCreateView(String name, Context context, AttributeSet attrs) { return null; } }); super.onCreate(savedInstanceState); setContentView(R.layout.activity_happy); }
日志打印:
name: LinearLayout attributeName: orientation attributeName: fitsSystemWindows attributeName: layout_width attributeName: layout_height ------------------------------------divide line------------------- name: ViewStub attributeName: theme attributeName: id attributeName: layout attributeName: inflatedId attributeName: layout_width attributeName: layout_height ------------------------------------divide line------------------- name: FrameLayout attributeName: id attributeName: layout_width attributeName: layout_height attributeName: foreground attributeName: foregroundGravity attributeName: foregroundInsidePadding ------------------------------------divide line------------------- name: androidx.appcompat.widget.FitWindowsLinearLayout attributeName: orientation attributeName: id attributeName: fitsSystemWindows attributeName: layout_width attributeName: layout_height ------------------------------------divide line------------------- name: androidx.appcompat.widget.ViewStubCompat attributeName: id attributeName: layout attributeName: inflatedId attributeName: layout_width attributeName: layout_height ------------------------------------divide line------------------- name: androidx.appcompat.widget.ContentFrameLayout attributeName: id attributeName: layout_width attributeName: layout_height attributeName: foreground attributeName: foregroundGravity ------------------------------------divide line------------------- name: LinearLayout attributeName: orientation attributeName: layout_width attributeName: layout_height ------------------------------------divide line-------------------
实现方式
我们可以以apk的方式来打包资源,这个apk资源包中只存在资源文件,然后我们通过动态加载技术来加载apk资源包,实现动态换肤的功能。
LayoutInflater源码总结
- Activity与AppCompatActivity都是调用LayoutInflater进行View的创建,但是AppCompatActivity的View是用Factory2进行创建的,我们可以用这种机制来实现替换动态换肤的实现。
- LayoutInflater的Factory2是带有重设校验的,它是不支持重复设置参数的,我们可以有两种方式来设置我们的Factory2。
- 在Activity的的surper.onCrate()方法调用setFactory2的方法设置Factory2.
- 通过反射设置Factory2 的值。
- 在LayoutInflater里面有个1995闪烁layout,我们可以通过在标签中使用blink标签,来达到闪烁布局的效果。
- 创建View是通过反射进行创建的,为了加快构造方法的创建速度,将之前生成的构造方法进行缓存。