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
- AppCompatActivity的魔术——如何做到适配新控件
- AppCompatActivity如何去掉标题栏
- 如何做到深思熟虑的编程
- 【Java SE】————如何做到跨平台性的?
- 后缀树的生成——如何做到简单快速(含源码)
- 算法优化——如何将人脸检测的速度做到极致
- 关于大型asp.net应用系统的架构—如何做到高性能高可伸缩性
- 关于大型ASP.NET应用系统的架构—如何做到高性能高可伸缩性
- 深入剖析Android四大组件(九)——Activity之AppCompatActivity与toolbar的结合
- 深入剖析Android四大组件(九)——Activity之AppCompatActivity与toolbar的结合
- 深入辨析Android四大组件(九)——Activity之AppCompatActivity与toolbar的结合
- 深入辨析Android四大组件(九)——Activity之AppCompatActivity与toolbar的结合
- 深入剖析Android四大组件(九)——Activity之AppCompatActivity与toolbar的结合
- AppCompatActivity的出现
- AppCompatActivity的View树
- 致歉 —— 关于《WINX如何做到可视化界面开发》
- 继承AppCompatActivity如何实现无标题
- AppCompatActivity
- POJ 3281 Dinging(网络流最大流)解析
- linux 应用编程——多进程
- ios 分离出字符串中的数字
- (转载)JAVA设计模式之单例模式
- 大商创短信宝短信插件
- AppCompatActivity的魔术——如何做到适配新控件
- 前端面试题集锦一
- 百练_2736大整数减法(大数相减)
- hdu 1074 Doing Homework (状压dp)
- 页面显示部分文字,title再显示一个提示
- struts2配置文件中的method={1}详解
- python中向列表中添加字典时,出现前面的覆盖了后面的
- 图文详解远程部署ASP.NET MVC 5项目
- Netty ByteBuf 零拷贝