V7包实现拦截View创建的原理

问题

  1. V7包的拦截是通过什么实现的?

  2. 如果要自己实现,那又应该如何实现?

问题一:在解决问题前先了解拦截 View 的原理


1. 在 AppCompatActivity 的 onCreate 中调用了下面两行代码,这是实现拦截 View 创建的起点

final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();

2. getDelegate() 方法返回的是什么?返回的这个对象实现了什么功能?

解答①:getDelegate() 方法调用了 AppCompatDelegate 的 create() 方法,这个方法返回的是一个 AppCompatDelegateImplV9 对象

★ AppCompatDelegateImplV9 类继承关系和实现接口如下:

getDelegateclass AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
implements MenuBuilder.Callback, LayoutInflaterFactory

★ AppCompatDelegate 类中的 create() 方法源码如下:

private static AppCompatDelegate create(Context context, Window window, AppCompatCallback callback) {
    final int sdk = Build.VERSION.SDK_INT;
    if (BuildCompat.isAtLeastN()) {
        return new AppCompatDelegateImplN(context, window, callback);
    } else if (sdk >= 23) {
        return new AppCompatDelegateImplV23(context, window, callback);
    } else if (sdk >= 14) {
        return new AppCompatDelegateImplV14(context, window, callback);
    } else if (sdk >= 11) {
        return new AppCompatDelegateImplV11(context, window, callback);
    } else {
        return new AppCompatDelegateImplV9(context, window, callback);
    }
}

解答②:返回的这个 AppCompatDelegateImplV9 对象实现了 LayoutInflaterFactory 接口中的 onCreateView() 方法,这个方法能对拦截到的 View 进行修改;

★ 实现的 LayoutInflaterFactory 中的 onCreateView() 方法源代码如下:

@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    // First let the Activity's Factory try and inflate the view
    final View view = callActivityOnCreateView(parent, name, context, attrs);
    if (view != null) {
        return view;
    }
    // If the Factory didn't handle it, let our createView() method try
    return createView(parent, name, context, attrs);
}

PS:AppCompatDelegateImplN,AppCompatDelegateImplV23 最终都是继承于 AppCompatDelegateImplV9,所以只要了解AppCompatDelegateImplV9 类做了什么就可以


3. AppCompatDelegateImplV9 中的 installViewFactory() 方法做了什么?

解答:这个方法主要将 实现了 LayoutInflaterFactory 中的 onCreateView() 方法的 this 对象传到系统的 LayoutInflater 类的 mFactory 对象中。

以下是分析过程

3.1 installViewFactory方法的源码如下:

@Override
public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
    if (layoutInflater.getFactory() == null) {
        LayoutInflaterCompat.setFactory(layoutInflater, this);
    } else {
        if (!(LayoutInflaterCompat.getFactory(layoutInflater)
                instanceof AppCompatDelegateImplV9)) {
            Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                    + " so we can not install AppCompat's");
        }
    }
}

分析:installViewFactory() 方法主要的作用是这一条语句 LayoutInflaterCompat.setFactory(layoutInflater, this),这里将this作为参数传入,记住,这个很重要,因为 AppCompatDelegateImplV9 实现了 LayoutInflaterFactory接口,这个接口有一个 onCreateView 方法能修改拦截后的View

3.2 LayoutInflaterCompat.setFactory(layoutInflater, this)的源代码,那这一条语句又干什么了?

public static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
    IMPL.setFactory(inflater, factory);
}

3.2.1 IMPL 对象是 LayoutInflaterCompatImpl 接口,LayoutInflaterCompatImpl 类是 LayoutInflaterCompat 类的一个内部接口,IMPL在LayoutInflaterCompat 初始化的时候被实例化,源码如下:

static final LayoutInflaterCompatImpl IMPL;

static {
    final int version = Build.VERSION.SDK_INT;
    if (version >= 21) {
        IMPL = new LayoutInflaterCompatImplV21();
    } else if (version >= 11) {
        IMPL = new LayoutInflaterCompatImplV11();
    } else {
        IMPL = new LayoutInflaterCompatImplBase();
    }
}

PS :LayoutInflaterCompatImplV21,LayoutInflaterCompatImplV11都是 LayoutInflaterCompat 的静态内部类,而且最终都是继承于LayoutInflaterCompatImplBase ;

3.2.2 LayoutInflaterCompatImplBase 源代码如下,可以发现它实现了 LayoutInflaterCompatImpl 接口,也就是 installViewFactory() 方法最终作用都是取决于 LayoutInflaterCompatBase.setFactory(layoutInflater, factory)

static class LayoutInflaterCompatImplBase implements LayoutInflaterCompatImpl {
    @Override
    public void setFactory(LayoutInflater layoutInflater, LayoutInflaterFactory factory) {
        LayoutInflaterCompatBase.setFactory(layoutInflater, factory);
    }

    @Override
    public LayoutInflaterFactory getFactory(LayoutInflater layoutInflater) {
        return LayoutInflaterCompatBase.getFactory(layoutInflater);
    }
}

4. LayoutInflaterCompatBase.setFactory(layoutInflater, factory) 的作用是什么?

解答:主要是将从 installViewFactory() 方法传过来的 factory(即:AppCompatDelegateImplV9 对象)转换成 LayoutInflater 类的 Factory 接口类型,然后再通过 LayoutInflater 类的 setFactory() 方法传到 LayoutInflater 类中的 mFactory 对象中

★  LayoutInflaterCompatBase.setFactory() 方法的源码如下:

static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
    inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null);
}

★  LayoutInflater 类的 setFactory() 方法的源代码如下:

public void setFactory(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;
    if (mFactory == null) {
        mFactory = factory;
    } else {
        mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
    }
}

分析:这里的 inflater 是系统的 LayoutInflater 对象,调用 setFactory() 方法后,就会将实现了 LayoutInflaterFactory 接口的AppCompatDelegateImplV9 作为参数传入 FactoryWrapper,然后 new 一个 FactoryWrapper 对象作为参数传到 setFactory() 方法中,这个 FactoryWrapper 对象会通过 setFactory() 方法存储到系统 LayoutInflater 类的 mFactory 对象中,mFactory对象最终会被 LayoutInflater 类中的 CreateViewFromTag() 方法调用


扩展点:

问题:为什么new一个 FactoryWrapper 的对象?

解答:因为系统的 LayoutInflater 的 setFactory() 方法接收的是实现了 LayoutInflater 类中的 Factory 接口的对象,而 AppCompatDelegateImplV9 没有实现这样的接口,所以这里就需要一个转换,而 FactoryWrapper 就是这样一个转换类,FactoryWrapper 是LayoutInflaterCompatBase 类中的一个实现了 LayoutInflater.Factory 接口的静态内部类,通过这个类实现了 AppCompatDelegateImplV9 到 Factory 的转换

★ LayoutInflater 类中的 Factory 接口,源码如下:

public interface Factory {
    public View onCreateView(String name, Context context, AttributeSet attrs);
}

★ FactoryWrapper 类的源码如下:

static class FactoryWrapper implements LayoutInflater.Factory {

    final LayoutInflaterFactory mDelegateFactory;

    FactoryWrapper(LayoutInflaterFactory delegateFactory) {
        mDelegateFactory = delegateFactory;
    }

    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        return mDelegateFactory.onCreateView(null, name, context, attrs);
    }
}

5. 通过方法设置的 LayoutInflater 类中的 mFactory 对象是如何被使用的?V7包是在哪里实现拦截 View 的创建的?

★ CreateViewFromTag() 方法源码如下:

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,

                       boolean ignoreThemeAttr) {
    ....
    try {
        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);
        }

        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;
    } catch (InflateException e) {
         .....
    }
}

解答①:由第 8,9 行可知道,如果 mFactory 不为null ,在 mFactory中实现的 onCreateView 方法将会被实现,这个方法经过一系列的调用,最终调用到在 AppCompatDelegateImplV9 中实现的 onCreateView 方法;如果 mFactory 为 null,会在第22行进行下一个判断,如果该 View 不是自定义View则会调用 23 行系统的的 onCreateView(parent, name, attrs) 方法,如果该 View 是自定义 View 的话,则会调用 25 行的 createView(name,null, attrs) 方法

解答②:V7包就是通过设置的 mFactory 对象,然后在 View 的创建前(即在调用系统的方法创建 View前),先调用 mFactory 对象中的 onCreateView() 方法(即 AppCompatDelegateImplV9 中的 onCreateView() 方法),这个方法将在 View 创建前调用


6. V7包是在哪里对拦截到的 View 进行修改的?

解答:在 mFactory 拦截了 View 的创建后,调用了 mFactory 对象的 onCreateView() 方法(即 AppCompatDelegateImplV9 中的 onCreateView() 方法),这个方法会调用 AppCompatViewInflater 类中的 createView() 方法,在这个方法中实现了 对拦截到的 View 的修改

★ AppCompatDelegateImplV9 中实现的 onCreateView 方法,源码如下:

@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    // First let the Activity's Factory try and inflate the view
    final View view = callActivityOnCreateView(parent, name, context, attrs);
    if (view != null) {
        return view;
    }

    // If the Factory didn't handle it, let our createView() method try
    return createView(parent, name, context, attrs);
}

★ AppCompatViewInflater 类中的 createView() 方法,源码如下:

public final View createView(View parent, final String name, @NonNull Context context,
                             @NonNull AttributeSet attrs, boolean inheritContext,
                             boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {

    ....
    View view = null;

    // 我们在这用自己的View实现来对原本的View进行更改
    switch (name) {
        case "TextView":
            view = new AppCompatTextView(context, attrs);
            break;
        case "ImageView":
            view = new AppCompatImageView(context, attrs);
            break;
        case "Button":
            view = new AppCompatButton(context, attrs);
            break;
        case "EditText":
            view = new AppCompatEditText(context, attrs);
            break;
        case "Spinner":
            view = new AppCompatSpinner(context, attrs);
            break;
        case "ImageButton":
            view = new AppCompatImageButton(context, attrs);
            break;
        ....
    }
    ....
    return view;
}

▲ 这是一个测试的结果:证明V7包拦截了 TextView,并对 TextView 进行了修改

继承自Activity:
android.widget.TextView{ac5cd17 V.ED..... ......ID 0,0-0,0 #7f0b002c app:id/text_view}

继承自AppCompatActivity:
android.support.v7.widget.AppCompatTextView{392562b V.ED..... ......ID 0,0-0,0 #7f0b0055 app:id/text_view}

总结:为什么V7包能实现拦截View的创建,并对拦截到的View进行修改,原因是 AppCompatDelegateImplV9 将 实现了 LayoutInflaterFactory 接口方法的 this 对象通过自身的 installViewFactory() 方法中的 LayoutInflaterCompat.setFactory(layoutInflater, this) 这一条语句将这个 this 对象一直传到了 LayoutInflater 类中的 mFactory 对象中,而这个对象对在 createViewFromTag() 方法中对 View 调用系统的方法进行实例化前被调用,在这个 createViewFromTag() 方法中会对 mFactory 对象进行判断,如果不为 null,那么经过一系列的回调,最终会调用到 AppCompatDelegateImplV9 类中的  onCreateView() 方法,这个方法中会再回调 AppCompatViewInflater 类中的 createView() 方法,在这个方法中实现了对拦截到的 View 进行修改。然后等 mFactory 对象调用 createViewFromTag() 方法之后,如果该 View 已被 createViewFromTag() 方法创建则将不再调用系统方法创建 View


问题二:如果要自己实现,那又应该如何实现?

使用方法如下

public abstract class BaseSkinActivity extends BaseActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        // 获取系统的 LayoutInflater 对象
        LayoutInflater defaultLayoutInflater = LayoutInflater.from(this);
        // 设置拦截 View 创建的 mFactory
        LayoutInflaterCompat.setFactory(defaultLayoutInflater, new LayoutInflaterFactory() {
            /**
             * 在这里对 View 进行修改
             * @param parent
             * @param name View的名称
             * @param context 上下文
             * @param attrs
             * @return
             */
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                return null;
            }
        });
        super.onCreate(savedInstanceState);
    }
}

猜你喜欢

转载自blog.csdn.net/myTempest/article/details/78620732