天天看點

Android LayoutInflater 源碼解析

源碼基于 Android 11 Api 30

LayoutInflater 簡介

  • android.view.LayoutInflater
  • 布局解析器
  • 是一個抽象類
  • 是一個系統服務,服務名稱是 Context.LAYOUT_INFLATER_SERVICE = “layout_inflater”

擷取 LayoutInflater 方式

1 Context#getSystemService

2 LayoutInflater#from

public static LayoutInflater from(Context context) {
//就是調用了 Context#getSystemService
LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    	//做了判空
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
}
           

3 Activity#getLayoutInflater

private Window mWindow;
public Window getWindow() {
        return mWindow;
    }
@NonNull
public LayoutInflater getLayoutInflater() {
    //PhoneWindow 是 mWindow 的唯一繼承實作類 
   return getWindow().getLayoutInflater();
}
           
  • PhoneWindow#getLayoutInflater
    private LayoutInflater mLayoutInflater;
    public PhoneWindow(Context context) {
            super(context);
            //就是調用了 LayoutInflater#from
            mLayoutInflater = LayoutInflater.from(context);
            mRenderShadowsInCompositor = Settings.Global.getInt(context.getContentResolver(),
                    DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 1) != 0;
    }
    @Override
    public LayoutInflater getLayoutInflater() {
            return mLayoutInflater;
    }
               

inflate 方法

1 View#inflate

public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);
}
           
  • 其實就是調用了 LayoutInflater.from(context).inflate(resource, root) 方法
  • 解析布局生成 View 加入 ViewGroup 裡面

2 LayoutInflater#inflate

  • Android 内置了 org.xmlpull 元件,以 pull 方式處理 xml
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                  + Integer.toHexString(resource) + ")");
        }
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
    	//XmlBlock#newParser 建立 XmlBlock$Parser 傳回
        XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
 }
//org.xmlpull.v1.XmlPullParser
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
        return inflate(parser, root, root != null);
}
//org.xmlpull.v1.XmlPullParser
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;
            //預設是傳入的 root
            View result = root;

            try {
                //移動到開始标簽
                advanceToRootNode(parser);
                //如 androidx.constraintlayout.widget.ConstraintLayout
                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
                    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
                        // new LayoutParams(getContext(), attrs)
                        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");
                    }
					//遞歸解析 xml 布局中的子 View
                    // Inflate all children under temp against its context.
                    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(
                        getParserStateDescription(inflaterContext, attrs)
                        + ": " + 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;
        }
    }
           
  • root == null || attachToRoot == false 傳回 xml 解析出的 temp 視圖
  • root != null && attachToRoot == true 傳回傳入的 root 視圖,進行了 root.addView(temp, params) ,addView 方法裡進行了 temp .setLayoutParams(params)
  • root != null && attachToRoot == false 時 xml 解析出的 temp 視圖 ,此時需要 temp .setLayoutParams(params)

由此可以得出當 root 為空時,xml 最外層布局的屬性會失效,因為壓根沒有設定參數嘛!

tryInflatePrecompiled

  • Android 10 Api 29 新增特征,暫不深究
  • 采用預編譯方式減少 xml 解析時間
private @Nullable
    View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
        boolean attachToRoot) {
        if (!mUseCompiledView) {
            //不使用預編譯,直接傳回 null
            return null;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");
        // Try to inflate using a precompiled layout.
     	//包名,如 com.louisgeek.xxx
        String pkg = res.getResourcePackageName(resource);
     	//layout名稱,如 activity_main
        String layout = res.getResourceEntryName(resource);
		//通過反射建立對應的 View
        try {
            Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
            Method inflater = clazz.getMethod(layout, Context.class, int.class);
            View view = (View) inflater.invoke(null, mContext, resource);

            if (view != null && root != null) {
                //解析屬性資訊設定到上面生成的 view 上
                // We were able to use the precompiled inflater, but now we need to do some work to
                // attach the view to the root correctly.
                XmlResourceParser parser = res.getLayout(resource);
                try {
                    AttributeSet attrs = Xml.asAttributeSet(parser);
                    advanceToRootNode(parser);
                    ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
					//是否添加到 root,以及設定布局參數
                    if (attachToRoot) {
                        root.addView(view, params);
                    } else {
                        view.setLayoutParams(params);
                    }
                } finally {
                    parser.close();
                }
            }

            return view;
        } catch (Throwable e) {
            if (DEBUG) {
                Log.e(TAG, "Failed to use precompiled view", e);
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        return null;
    }
           
  • mUseCompiledView = true 的情況,現僅供内部測試使用
    protected LayoutInflater(Context context) {
            mContext = context;
        	//無參方法預設 mUseCompiledView = false
            initPrecompiledViews();
    }
    
    protected LayoutInflater(LayoutInflater original, Context newContext) {
            mContext = newContext;
            mFactory = original.mFactory;
            mFactory2 = original.mFactory2;
            mPrivateFactory = original.mPrivateFactory;
            setFilter(original.mFilter);
       		 //無參方法預設 mUseCompiledView = false
            initPrecompiledViews();
    }  
     /**
       * @hide for use by CTS tests
       */
    @TestApi
    public void setPrecompiledLayoutsEnabledForTesting(boolean enablePrecompiledLayouts) {
            initPrecompiledViews(enablePrecompiledLayouts);
    }
    //無參方法預設 mUseCompiledView = false
     private void initPrecompiledViews() {
         // Precompiled layouts are not supported in this release.
         boolean enabled = false;
        initPrecompiledViews(enabled);
    }
    private void initPrecompiledViews(boolean enablePrecompiledViews) {
            mUseCompiledView = enablePrecompiledViews;
    
            if (!mUseCompiledView) {
                mPrecompiledClassLoader = null;
                return;
            }
            // Make sure the application allows code generation
            ApplicationInfo appInfo = mContext.getApplicationInfo();
            if (appInfo.isEmbeddedDexUsed() || appInfo.isPrivilegedApp()) {
                mUseCompiledView = false;
                return;
            }
            // Try to load the precompiled layout file.
            try {
                mPrecompiledClassLoader = mContext.getClassLoader();
                String dexFile = mContext.getCodeCacheDir() + COMPILED_VIEW_DEX_FILE_NAME;
                if (new File(dexFile).exists()) {
                    mPrecompiledClassLoader = new PathClassLoader(dexFile, mPrecompiledClassLoader);
                } else {
                    // If the precompiled layout file doesn't exist, then disable precompiled
                    // layouts.
                    mUseCompiledView = false;
                }
            } catch (Throwable e) {
                if (DEBUG) {
                    Log.e(TAG, "Failed to initialized precompiled views layouts", e);
                }
                mUseCompiledView = false;
            }
            if (!mUseCompiledView) {
                mPrecompiledClassLoader = null;
            }
        }
               

XmlResourceParser

XmlBlock$Parser 、BridgeXmlBlockParser 實作了 XmlResourceParser 接口
  • Resources#getLayout
@NonNull
    public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
        return loadXmlResourceParser(id, "layout");
    }
@NonNull
XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
            throws NotFoundException {
    	//擷取臨時 TypedValue,内部有緩存機制
        final TypedValue value = obtainTempTypedValue();
        try {
            final ResourcesImpl impl = mResourcesImpl;
            impl.getValue(id, value, true);
            if (value.type == TypedValue.TYPE_STRING) {
                //
                return loadXmlResourceParser(value.string.toString(), id,
                        value.assetCookie, type);
            }
            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
        } finally {
            releaseTempTypedValue(value);
        }
}
 @NonNull
XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie,
                                            String type) throws NotFoundException {
        return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type);
}
           
  • 使用 XmlPullParser 周遊 xml 檔案内的所有節點

AttributeSet

  • Xml#asAttributeSet
public static AttributeSet asAttributeSet(XmlPullParser parser) {
     //parser is XmlPullParser
     //parser is XmlResourceParser
     //XmlBlock$Parser implements XmlResourceParser
        return (parser instanceof AttributeSet)
                ? (AttributeSet) parser
                : new XmlPullAttributes(parser);
}
           

advanceToRootNode

  • 通過 parser.next() 移動
private void advanceToRootNode(XmlPullParser parser)
        throws InflateException, IOException, XmlPullParserException {
        // 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!");
        }
    }
           

rInflate

  • XmlPullParser.START_DOCUMENT 文檔開始标記,代表未讀取任何内容
  • XmlPullParser.END_DOCUMENT 文檔結束标記
  • XmlPullParser.START_TAG 開始标簽
  • XmlPullParser.END_TAG 結束标簽
  • XmlPullParser.TEXT 文本
void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
		//xml 的深度
        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while (
            	( 
                    (type = parser.next()) != XmlPullParser.END_TAG 
                 	|| parser.getDepth() > depth
           		)
               && type != XmlPullParser.END_DOCUMENT
        ) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }
			//如 TextView
            final String name = parser.getName();
			
            if (TAG_REQUEST_FOCUS.equals(name)) {
                //判斷 <requestFocus />
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                //判斷 <tag />
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                //判斷 <include />
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                //判斷 <merge />
                throw new InflateException("<merge /> must be the root element");
            } else {
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                //
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }
           

createViewFromTag

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) {
        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();
        }

        try {
            //通過 LayoutInflater$Factory2、LayoutInflater$Factory 執行 onCreateView 方法
            View view = tryCreateView(parent, name, context, attrs);

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(context, parent, name, attrs);
                    } else {
                        view = createView(context, name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } catch (InflateException e) {
            throw e;

        } catch (ClassNotFoundException e) {
            final InflateException ie = new InflateException(
                    getParserStateDescription(context, attrs)
                    + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;

        } catch (Exception e) {
            final InflateException ie = new InflateException(
                    getParserStateDescription(context, attrs)
                    + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        }
    }
           
  • LayoutInflater#tryCreateView
    @Nullable
        public final View tryCreateView(@Nullable View parent, @NonNull String name,
            @NonNull Context context,
            @NonNull AttributeSet attrs) {
            if (name.equals(TAG_1995)) {
                // Let's party like it's 1995!
                return new BlinkLayout(context, attrs);
            }
            View 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);
            }
            return view;
     }
               
  • LayoutInflater#onCreateView
    @Nullable
        public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
                @NonNull String name, @Nullable AttributeSet attrs)
                throws ClassNotFoundException {
            return onCreateView(parent, name, attrs);
    }
     protected View onCreateView(View parent, String name, AttributeSet attrs)
                throws ClassNotFoundException {
            return onCreateView(name, attrs);
    }
     protected View onCreateView(String name, AttributeSet attrs)
                throws ClassNotFoundException {
            return createView(name, "android.view.", attrs);
    }
               
  • LayoutInflater#createView
    public final View createView(String name, String prefix, AttributeSet attrs)
                throws ClassNotFoundException, InflateException {
            Context context = (Context) mConstructorArgs[0];
            if (context == null) {
                context = mContext;
            }
            return createView(context, name, prefix, attrs);
    }
    @Nullable
        public final View createView(@NonNull Context viewContext, @NonNull String name,
                @Nullable String prefix, @Nullable AttributeSet attrs)
                throws ClassNotFoundException, InflateException {
            Objects.requireNonNull(viewContext);
            Objects.requireNonNull(name);
            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 = Class.forName(prefix != null ? (prefix + name) : name, false,
                            mContext.getClassLoader()).asSubclass(View.class);
    
                    if (mFilter != null && clazz != null) {
                        boolean allowed = mFilter.onLoadClass(clazz);
                        if (!allowed) {
                            failNotAllowed(name, prefix, viewContext, 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 = Class.forName(prefix != null ? (prefix + name) : name, false,
                                    mContext.getClassLoader()).asSubclass(View.class);
    
                            boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                            mFilterMap.put(name, allowed);
                            if (!allowed) {
                                failNotAllowed(name, prefix, viewContext, attrs);
                            }
                        } else if (allowedState.equals(Boolean.FALSE)) {
                            failNotAllowed(name, prefix, viewContext, attrs);
                        }
                    }
                }
    
                Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = viewContext;
                Object[] args = mConstructorArgs;
                args[1] = attrs;
    
                try {
                    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]));
                    }
                    return view;
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            } catch (NoSuchMethodException e) {
                final InflateException ie = new InflateException(
                        getParserStateDescription(viewContext, attrs)
                        + ": 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(
                        getParserStateDescription(viewContext, attrs)
                        + ": 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(
                        getParserStateDescription(viewContext, attrs) + ": Error inflating class "
                                + (clazz == null ? "<unknown>" : clazz.getName()), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
    }
               

rInflateChildren

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
           

View.inflate 注意事項

  • root == null 成立,那麼 attachToRoot = false
  • root != null 成立,那麼 attachToRoot = true

是以不适合在清單的 Adapter 裡使用,因為它會自己添加 child view ,做不到 root !=null 同時 attachToRoot = false 的情況

是以也不合适在 Fragment#onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) 方法裡使用,因為 FragmentManager 裡會進行執行一次 container.addView

AppCompatDelegateImpl#onCreateView

@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    return createView(parent, name, context, attrs);
}
 @Override
    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) {
                // Set to null (the default in all AppCompat themes). 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);
        }
		//createView
        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

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

        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;
}
           
  • 解析 xml ,判斷 view 類型,執行個體化指定控件對象