红橙Darren视频笔记 换肤框架2 原理篇 view创建的拦截

1.实现换肤的要求与方案分析

要求
1.换肤后每一次打开应用都是新的皮肤
2.换肤后所有的activity里面的View都要换肤(即时刷新)
做法:
为所有Activity添加theme的监听 当theme发生变化的时候 通知所有Activity进行换肤 如果使用fragment,activity内部遍历自己的fragment 也进行换肤

换肤分为两步
1.找到皮肤包的位置
2.给所有的view换肤

1.1皮肤包的位置方案

皮肤包根据实现的方式 存储的地方有所不同
像之前的文章(https://blog.csdn.net/u011109881/article/details/115558620)皮肤包自然是与我们的应用分离的
Android7.0左右已经支持theme切换,我们可以利用Android的这种新机制实现换肤 即定义不同的theme 那么此时的皮肤包其实就在apk内部 我们可以创建不同的theme 里面定义的颜色不同 这样我们在皮肤切换时切换theme 就能自动替换颜色
例如
themeDay.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="DayTheme" parent="activityScoutThemeNoTitle">
        <item name="commonButtonBgColor">@color/day_button</item>
        <item name="commonTextColor">@color/day_text_color</item>
        <item name="commonBg">@drawable/commonDayBg</item>
    </style>
</resources>

themeNight.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="NightTheme" parent="DayTheme">
        <item name="commonButtonBgColor">@color/night_button</item>
        <item name="commonBg">@drawable/commonNightBg</item>
        <item name="commonTextColor">@color/night_text_color</item>
    </style>
</resources>

这样我们就可以定义在布局中使用attr了

        <TextView
            android:id="@+id/titleNameText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:minWidth="200dp"
            android:text="@string/addressHome"
            android:textColor="?attr/commonTextColor"
            android:tag="textColor:attr:commonTextColor"
            android:textSize="28sp" />

1.2.替换所有view方案

这里列举三种方案
1.每一个activity里面都把需要换肤的View使用findviewbyid给找出来,然后调用代码去换肤;
弊端:当新增view删除view的时候 需要频繁修改代码 不够灵活 容易出错
2.获取activity里面的根布局,然后通过不断的循环获取子View, 查看xml是否添加了tag;
例子

        <TextView
            android:id="@+id/titleNameText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:minWidth="200dp"
            android:text="@string/addressHome"
            android:textColor="?attr/commonTextColor"
            android:tag="textColor:attr:commonTextColor"
            android:textSize="28sp" />

tag用于标记该view是否需要换肤
3.拦截View的创建,这是我们想要使用的方案
灵感来源于Android源码 我们发现继承Activity 和AppCompatActivity button的式样有所不同(继承AppCompatActivity Button是蓝色的) 好像换了皮肤 为什么呢
我们发现如果我们写一个Activity直接继承自Activity 那么Button使用的是android.widget.Button
但是如果继承的是AppCompatActivity Button的实例是com.google.android.material.button.MaterialButton
这就是Android material design的实现方式 拦截view的创建 换一套自己的view 使用新的皮肤
下面我们分析 Android自己是如何“换肤”的

2.setContentView源码分析

关于Activity的setContentView源码 我之前分析过 参见(https://blog.csdn.net/u011109881/article/details/111085949)
下面分析继承AppCompatActivity 时setContentView的源码(我的demo项目是支持AndroidX的 和不持支AndroidX的项目可能不同)
从Activity的setContentView开始跟进

   	// AppCompatActivity
	public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

	// AppCompatDelegate
	public abstract void setContentView(@LayoutRes int resId);// 无法继续跟进 需要知道getDelegate的实例

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

	// AppCompatDelegateImpl
    public static AppCompatDelegate create(@NonNull Activity activity,
            @Nullable AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, callback);// 看来getDelegate的实例是AppCompatDelegateImpl
        //跟进AppCompatDelegateImpl的setContentView方法
    }

    public void setContentView(int resId) {
        ensureSubDecor();// 初始化DecorView 跟进去发现和继承Activity中installDecor方法的做法类似
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);// 找到系统布局供开发使用的布局
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);// 将setContentView的布局填充到android.R.id.content的地方
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }
	
	

无论是继承Activity还是AppCompatActivity 其代码相差无几 最终的布局格局都是如下:
在这里插入图片描述
到目前位置 我们还没看到AppCompatActivity 是如何将Button替换了的 我们继续跟进

3.view的填充 LayoutInflater inflate填充布局源码分析(继承AppCompatActivity API 27)

首先需要注意AppCompatActivity的创建

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
        super.onCreate(savedInstanceState);
    }

	//AppCompatDelegate的installViewFactory只是抽象方法
	public abstract void installViewFactory();

	//具体实现在AppCompatDelegateImpl
	//AppCompatDelegateImpl继承自Factory2
    @Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            //注意setFactory2被调用
            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");
            }
        }
    }

前期准备工作完成 继续往下看

	// setContentView来自AppCompatDelegateImpl 注意继承自Factory2
	// class AppCompatDelegateImpl extends AppCompatDelegate
    //    implements MenuBuilder.Callback, LayoutInflater.Factory2 
	public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);//跟进
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

	// LayoutInflater.java
	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;
        }
        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) {
            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 {
					...
                    // 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;
                    }
                }

            } 
        	...
        	finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }

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

    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

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

            final String name = parser.getName();// 重点 后面会使用这个变量进行判断

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                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();
        }
    }

    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) {
        ...

        try {
            View view;
            if (mFactory2 != null) {
                //还记得前面的准备条件么 这里就走这条case 
                view = mFactory2.onCreateView(parent, name, context, attrs);// 跟进
                // AppCompatDelegateImpl继承自Factory2 因此走的AppCompatDelegateImpl onCreateView方法
            } 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);
            }

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

            return view;
        }
        ...
    }


    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        return createView(parent, name, context, attrs);
    }

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

        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
	// 重点 将各种view重新创建
    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;
    }

	// MaterialComponentsViewInflater.java
    protected AppCompatButton createButton(@NonNull Context context, @NonNull AttributeSet attrs) {
      return new MaterialButton(context, attrs);
    }

可以看到 填充子布局的时候会根据xml解析后的节点名称 如SeekBar CheckBox Button 将该节点创建为不同的view
而MaterialButton内部有很多设置背景的方法 自然可以显示和普通button不同的式样
值得注意的是其中一个变量
private final MaterialButtonHelper materialButtonHelper;
它包含了一系列setBackground的方法 帮助MaterialButton进行背景设置 我们的换肤可以参考这种设计
AppCompateActivity创建View的时候会被拦截,不会走系统的LayoutInflater的创建而是走的AppCompatViewInflater的创建,就会被替换掉一些特定的View
其中最重要的是我们继承了AppCompatActivity 并在他的delegate调用installViewFactory AppCompatDelegateImpl本身又继承自Factory2
LayoutInflaterCompat.setFactory2(layoutInflater, this);
这句执行是关键

4.view创建的拦截

4.1 view的创建方式有如下三种

         View layoutView = View.inflate(this,R.layout.activity_main,null);
         layoutView = LayoutInflater.from(this).inflate(R.layout.activity_main,null);
         layoutView = LayoutInflater.from(this).inflate(R.layout.activity_main,null,false);

第一种我们看代码实际调用的就是第二种方式 LayoutInflater.from(this).inflate(R.layout.activity_main,null);

    public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);
    }

我们看第二种的调用

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

实际上又是调用的第三种 所以三种方式没有区别

4.2 LayoutInflater的初始化

	// LayoutInflater
	public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

	//Activity
    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }

        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);//here
    }

	// android.view.ContextThemeWrapper
    public Object getSystemService(String name) {
        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
            if (mInflater == null) {
                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
            }
            return mInflater;
        }
        return getBaseContext().getSystemService(name);
    }

	// LayoutInflater 
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

	// androidx.appcompat.view.ContextThemeWrapper
    public Object getSystemService(String name) {
        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
            if (mInflater == null) {
                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
            }
            return mInflater;
        }
        return getBaseContext().getSystemService(name);
    }

	// LayoutInflater 
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

	// ContextImpl
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

	// SystemServiceRegistry
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

	//所有系统服务存储在这个hashMap中了
    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();

	// 提供了静态注册service的方法
    private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }

	// 我们在静态代码块中找到了初始化LAYOUT_INFLATER_SERVICE的部分
    static{
    	...
    	        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
        ...
    }


因此LayoutInflater是一个单例 从始至终只有一个实例

4.3 inflate方法的参数attachToRoot

起点:View.inflate

	// View
	public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);
    }

	// LayoutInflater
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

	// LayoutInflater
    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) + ")");
        }
		// 获取xml解析器
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

	// LayoutInflater
    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. // 寻找xml布局的根标签
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                
				// 没有找到起始点START_TAG 会抛出异常
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                // 获取当前标签的名字 比如 TextView
                final String name = parser.getName();

                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }

                // merge标签只能用在parent不为空并且attachToRoot=true的情况
                // 否则抛出异常
                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);//4.4用到
					// 布局参数     
                    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
                        // 生成布局参数 注意只有root不为空的时候 这些参数才会生效
                        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.
                    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.
                    // 如果attachToRoot为true 并且root不为空 将之前创建的temp都填充到根view root
                    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.
                    // 如果attachToRoot == false 返回子view(temp)填充的view
                    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;
        }
    }


也就是说
inflate(int resource, ViewGroup root, boolean attachToRoot)这个方法有两种使用场景
1.root为空的情况
attachToRoot的作用可以忽略 返回的是填充完毕的resource的view
2.root不为为空的情况
如果 attachToRoot = false 则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效
如果 attachToRoot = true 那么会给加载的布局文件的指定一个父布局,即root
其实很简单 即如果参数root不为空并且attachToRoot 为true 那么Android会解析resource的布局并调用addView添加到root中去
否则 仅仅是解析resource并将他转换为一个view对象 由外部自己决定是否调用addView将布局解析出的view添加到哪里去

4.4 如何拦截view 并重新创建view 具体分析

继续跟进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();
        }

        if (name.equals(TAG_1995)) {// 彩蛋??//试了一下使用TAG_1995 报错 找不到这个类BlinkLayout 可能是AndroidX没有这个类
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        try {
            View view;
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);//走这里拦截view 重新创建
                //上面已经分析过 不再分析
            } 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);
            }

            if (view == null) {// 如果mFactory2.onCreateView返回的view为空 调用其他方法继续创建view
                // 比如自定义view和其他不在mFactory2.onCreateView所列出类型的view
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    // 判断是不是自定义View 因为自定义view中带有. 是路径全名
                    if (-1 == name.indexOf('.')) {// onCreateView内部会调用createView 并且传入的prefix是android.view.
                        view = onCreateView(parent, name, attrs);
                    } else {//Android中的view 如Button TextView
                        view = createView(name, null, attrs);//因为上下两个case
           				//都会走这个方法 继续分析该方法
                    }
                } 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;
        }
    }

	// LayoutInflater
	// 通过反射创建view
    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        // 先从缓存中拿构造方法 sConstructorMap是一个静态hashmap
        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
                // 加载 Class
                // 如果前缀prefix不为空 就将其拼接
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);

                // 创建View的构造函数
                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;

            // 通过反射创建View
            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;

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

拦截view创建的demo:

首先有这样的关系
public class MainActivity extends BaseSkinActivity
public abstract class BaseSkinActivity extends BaseActivity

public abstract class BaseSkinActivity extends BaseActivity {
    private static final String TAG = "BaseSkinActivity";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        LayoutInflater layoutInflater = LayoutInflater.from(this);
        LayoutInflaterCompat.setFactory2(layoutInflater, new LayoutInflater.Factory2() {
            @Nullable
            @Override
            public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
                //在这里拦截view创建
                //可以在这里进行换肤
                Log.e(TAG, "onCreateView: 拦截了view " + name);
                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);
                        ((Button) view).setText("拦截");// 预先将所有Button的文字改了
                        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;
                }
                return view;
            }

            @Nullable
            @Override
            public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {//Factory的方法 可以忽略
                Log.e(TAG, "onCreateView: ");
                return null;
            }
        });
        super.onCreate(savedInstanceState);
    }
}

小结:

本篇讲述的内容稍显杂乱 知识点整理如下:
1.换肤方案分析,分两步 第一,皮肤包存储位置 第二,如何找到所有的view并换肤
2.setContentView源码分析 我们需要清晰知道我们在mainActivity加载的布局 在系统布局中的具体位置
3.分析继承AppCompatActivity时inflate的方法 我们能知道AppCompatViewInflater内部如何将各种view替换为其他的view的
4.LayoutInflater的初始化是在一个静态代码块中 它是一个系统服务,并且是一个单例 各种获取LayoutInflater的方法其实都是返回的同一个对象
5.LayoutInflater.inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)方法详解 主要是看root是否为空以及attachToRoot是否为true
6.拦截view的demo
本节讲述的都是换肤的前提条件 下一步我们就可以具体实现换肤了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值