AppCompatActivity的魔术——如何做到适配新控件

来源:互联网 发布:淘宝网虚假交易处罚 编辑:程序博客网 时间:2024/06/05 19:06

大家都知道google要求使用app的模板类继承AppCompatActivity

这是一个继承FragmentAcvitivy的类,他是怎么做到让过时控件去使用酷炫的新特性的呢?

来看源码

核心变量 private AppCompatDelegate mDelegate;

这是兼容的老套路,便于在版本迭代的时候统一维护升级与兼容

    /**     * @return The {@link AppCompatDelegate} being used by this Activity.     */    @NonNull    public AppCompatDelegate getDelegate() {        if (mDelegate == null) {            mDelegate = AppCompatDelegate.create(this, this);        }        return mDelegate;    }
调用静态方法create 其中第二个参数是第一个参数activity的getWindow()即phoneWindow

    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);        }    }
这些不同版本的Impl类之间是继承关系 也就是说基类实现类其实是ImplV9

回来看AppCompatActivity的onCreate方法

    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        final AppCompatDelegate delegate = getDelegate();        delegate.installViewFactory(); //在onCreate的顶部调用 奥秘就在这里        delegate.onCreate(savedInstanceState);        if (delegate.applyDayNight() && mThemeId != 0) {            // If DayNight has been applied, we need to re-apply the theme for            // the changes to take effect. On API 23+, we should bypass            // setTheme(), which will no-op if the theme ID is identical to the            // current theme ID.            if (Build.VERSION.SDK_INT >= 23) {                onApplyThemeResource(getTheme(), mThemeId, false);            } else {                setTheme(mThemeId);            }        }        super.onCreate(savedInstanceState);    }
installViewFactory方法仅在ImplV9中有实现

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

那么就来看这个LayoutInflaterCompat的静态方法setFactory

    /**     * Attach a custom Factory interface for creating views while using     * this LayoutInflater. This must not be null, and can only be set once;     * after setting, you can not change the factory.     *     * @see LayoutInflater#setFactory(android.view.LayoutInflater.Factory)     */    public static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {        IMPL.setFactory(inflater, factory);    }
又是个代理模式的兼容实现IMPL

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

先来看一下v21 5.0以上的实现

class LayoutInflaterCompatHC {    private static final String TAG = "LayoutInflaterCompatHC";    private static Field sLayoutInflaterFactory2Field;    private static boolean sCheckedField;    static class FactoryWrapperHC extends LayoutInflaterCompatBase.FactoryWrapper            implements LayoutInflater.Factory2 {        FactoryWrapperHC(LayoutInflaterFactory delegateFactory) {            super(delegateFactory);        }        @Override        public View onCreateView(View parent, String name, Context context,                AttributeSet attributeSet) {            return mDelegateFactory.onCreateView(parent, name, context, attributeSet);        }    }    static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {        final LayoutInflater.Factory2 factory2 = factory != null                ? new FactoryWrapperHC(factory) : null;        inflater.setFactory2(factory2);        final LayoutInflater.Factory f = inflater.getFactory();        if (f instanceof LayoutInflater.Factory2) {            // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).            // We will now try and force set the merged factory to mFactory2            forceSetFactory2(inflater, (LayoutInflater.Factory2) f);        } else {            // Else, we will force set the original wrapped Factory2            forceSetFactory2(inflater, factory2);        }    }    /**     * For APIs >= 11 && < 21, there was a framework bug that prevented a LayoutInflater's     * Factory2 from being merged properly if set after a cloneInContext from a LayoutInflater     * that already had a Factory2 registered. We work around that bug here. If we can't we     * log an error.     */    static void forceSetFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {        if (!sCheckedField) {            try {                sLayoutInflaterFactory2Field = LayoutInflater.class.getDeclaredField("mFactory2");                sLayoutInflaterFactory2Field.setAccessible(true);            } catch (NoSuchFieldException e) {                Log.e(TAG, "forceSetFactory2 Could not find field 'mFactory2' on class "                        + LayoutInflater.class.getName()                        + "; inflation may have unexpected results.", e);            }            sCheckedField = true;        }        if (sLayoutInflaterFactory2Field != null) {            try {                sLayoutInflaterFactory2Field.set(inflater, factory);            } catch (IllegalAccessException e) {                Log.e(TAG, "forceSetFactory2 could not set the Factory2 on LayoutInflater "                        + inflater + "; inflation may have unexpected results.", e);            }        }    }}

仔细来看一下这个setFactory方法

LayoutInflater的setFactory方法要的参数是一个LayoutInflater.Factory

而入参却是一个android.support.v4.view.LayoutInflaterFactory

/** * Used with {@code LayoutInflaterCompat.setFactory()}. Offers the same API as * {@code LayoutInflater.Factory2}. */public interface LayoutInflaterFactory {    /**     * Hook you can supply that is called when inflating from a LayoutInflater.     * You can use this to customize the tag names available in your XML     * layout files.     *     * @param parent The parent that the created view will be placed     * in; <em>note that this may be null</em>.     * @param name Tag name to be inflated.     * @param context The context the view is being created in.     * @param attrs Inflation attributes as specified in XML file.     *     * @return View Newly created view. Return null for the default     *         behavior.     */    public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
适配器模式,用代理包装类FactoryWrapperHC extends LayoutInflaterCompatBase.FactoryWrapper implements LayoutInflater.Factory2实现转化

回来看看ImplV9到底塞了一个什么Factory进去

LayoutInflaterCompat.setFactory(layoutInflater, this);
也就是v9自己实现的LayoutInflaterFactory接口onCreateView方法
    /**     * From {@link android.support.v4.view.LayoutInflaterFactory}     */    @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);//常用流程里 并不会返回view 但是有个大大的疑问?        if (view != null) {            return view;        }        // If the Factory didn't handle it, let our createView() method try        return createView(parent, name, context, attrs); //重点来了    }
createView方法

    @Override    public View createView(View parent, final String name, @NonNull Context context,            @NonNull AttributeSet attrs) {        final boolean isPre21 = Build.VERSION.SDK_INT < 21;        if (mAppCompatViewInflater == null) {            mAppCompatViewInflater = new AppCompatViewInflater();        }        // We only want the View to inherit its context if we're running pre-v21        final boolean inheritContext = isPre21 && shouldInheritContext((ViewParent) parent);//原来createView被替换成了这个AppCompatViewInflater //shouldInheritContext方法:是否继承Context         return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,                isPre21, /* 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 干的!

    public final View createView(View parent, final String name, @NonNull Context context,            @NonNull AttributeSet attrs, boolean inheritContext,            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {        final Context originalContext = context;        // 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 = new AppCompatTextView(context, attrs);                break;            case "ImageView":                view = new AppCompatImageView(context, attrs);                break;            case "Button":                view = new AppCompatButton(context, attrs);                break;            case "EditText":                view = new AppCompatEditText(context, attrs);                break;            case "Spinner":                view = new AppCompatSpinner(context, attrs);                break;            case "ImageButton":                view = new AppCompatImageButton(context, attrs);                break;            case "CheckBox":                view = new AppCompatCheckBox(context, attrs);                break;            case "RadioButton":                view = new AppCompatRadioButton(context, attrs);                break;            case "CheckedTextView":                view = new AppCompatCheckedTextView(context, attrs);                break;            case "AutoCompleteTextView":                view = new AppCompatAutoCompleteTextView(context, attrs);                break;            case "MultiAutoCompleteTextView":                view = new AppCompatMultiAutoCompleteTextView(context, attrs);                break;            case "RatingBar":                view = new AppCompatRatingBar(context, attrs);                break;            case "SeekBar":                view = new AppCompatSeekBar(context, attrs);                break;        }        if (view == null && originalContext != context) {            // 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 it's android:onClick            checkOnClickListener(view, attrs);        }        return view;    }



待解决疑问:

AppCompatDelegateImplV9类里的onCreateView方法先交给Activity的Factory处理

    View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) {        // Let the Activity's LayoutInflater.Factory try and handle it        if (mOriginalWindowCallback instanceof LayoutInflater.Factory) {            final View result = ((LayoutInflater.Factory) mOriginalWindowCallback)                    .onCreateView(name, context, attrs);            if (result != null) {                return result;            }        }        return null;    }

mOriginalWindowCallback这个CallBack是什么时候被set成了一个LayoutInflater.Factory???

全局能找出setCallBack的 只有setActionBar和DelegateImplBase的构造 后者也只是把原Window的Callback用装饰模式套了个AppCompatWindowCallbackBase 真的是看得一脸懵逼~~~ 完全不知道这强转代码意义何在

源码中无从得知 待探究framework源码寻找答案













阅读全文
0 0
原创粉丝点击