AppCompatActivity的魔术——如何做到适配新控件
2017-07-11 17:28
246 查看
大家都知道google要求使用app的模板类继承AppCompatActivity
这是一个继承FragmentAcvitivy的类,他是怎么做到让过时控件去使用酷炫的新特性的呢?
来看源码
核心变量 private AppCompatDelegate mDelegate;
这是兼容的老套路,便于在版本迭代的时候统一维护升级与兼容
回来看AppCompatActivity的onCreate方法
那么就来看这个LayoutInflaterCompat的静态方法setFactory
先来看一下v21 5.0以上的实现
仔细来看一下这个setFactory方法
LayoutInflater的setFactory方法要的参数是一个LayoutInflater.Factory
而入参却是一个android.support.v4.view.LayoutInflaterFactory
回来看看ImplV9到底塞了一个什么Factory进去
待解决疑问:
AppCompatDelegateImplV9类里的onCreateView方法先交给Activity的Factory处理
mOriginalWindowCallback这个CallBack是什么时候被set成了一个LayoutInflater.Factory???
全局能找出setCallBack的 只有setActionBar和DelegateImplBase的构造 后者也只是把原Window的Callback用装饰模式套了个AppCompatWindowCallbackBase 真的是看得一脸懵逼~~~ 完全不知道这强转代码意义何在
源码中无从得知 待探究framework源码寻找答案
这是一个继承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源码寻找答案
相关文章推荐
- AppCompat Toolbar控件如何去除掉阴影
- 没有App_Code,如何使用ASP.NET控件?
- android app如何做到快速启动
- 教你如何升级app适配iOS 7
- 教你如何升级app适配iOS 7
- 如何判断Activity,Service,App是否在运行?
- 没有App_Code,如何使用ASP.NET控件?
- 教你如何升级app适配iOS 7
- Android如何确定Activity控件渲染完成
- 如何编译Support7Demos测试appcompat
- android中如果activity中应用fragementlayout布局文件,如何给其中的控件添加事件监听。
- 如何做到手机应用程序在多平台的适配
- 教你如何升级app适配iOS 7
- 教你如何升级app适配iOS 7
- 如何让App适配iOS7(草稿)
- 没有App_Code,如何使用ASP.NET控件?
- App如何适配Jelly Bean 和 Nexus 7
- 教你如何升级app适配iOS 7
- 教你如何升级app适配iOS 7
- 教你如何升级app适配iOS 7