美文网首页Android 源码分析
源码级分析AppCompatActivity适配过程

源码级分析AppCompatActivity适配过程

作者: aruba | 来源:发表于2017-07-07 12:41 被阅读2640次
    自android5.0推出以来,google大力宣扬Meterail Design这款视觉设计语言,在新系统上,大量的运用到了Meterail Design风格,显然这些效果低版本时并没有实现,那么google是如何在低版本中做兼容的?
    在as上新建项目时我们会发现activity会自动继承AppCompatActivity,它继承至FragmentActivity,查看AppCompatActivity源码我们会发现,以前FragmentActivity中的方法都会调用AppCompatDelegate的方法。这里以onCreate方法为例
    
       @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            final AppCompatDelegate delegate = getDelegate();
            delegate.installViewFactory();
            //调用AppCompatDelegate的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);
        }
    
    
        /**
         * @return The {@link AppCompatDelegate} being used by this Activity.
         */
        @NonNull
        public AppCompatDelegate getDelegate() {
            if (mDelegate == null) {
                //调用AppCompatDelegate接口的create方法
                mDelegate = AppCompatDelegate.create(this, this);
            }
            return mDelegate;
        }
    
    AppCompatDelegate的create方法中做了sdk版本判断,分别返回了不同版本的AppCompatDelegate实现类
        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);
            }
        }
    
    通过源码我们发现setContentView() 方法实现是在 AppCompatDelegateImplV9 这个类中。这个时候我们可以猜想下,由于Activity是通过一个xmlpull解析器根据tag,将xml解析为一个个组件和控件的(详情自行了解Activity中setContentView作用),那么,是否google在解析xml时做了一定手脚,来达到兼容的目的?我们找到setContentView(int resId)方法
        @Override
        public void setContentView(int resId) {
            ensureSubDecor();
            ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
            contentParent.removeAllViews();
            //解析xml
            LayoutInflater.from(mContext).inflate(resId, contentParent);
            mOriginalWindowCallback.onContentChanged();
        }
    
    通过inflate方法,我们最终找到createViewFromTag方法
     public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
            synchronized (mConstructorArgs) {
              /*
                省略
              */
    
                        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);
    
                        ViewGroup.LayoutParams params = null;
    
              /*
                省略
              */
        }
    
    调用了onCreateView实例化view
     View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                boolean ignoreThemeAttr) {
             /*
                省略
              */
                if (mFactory2 != null) {
                    view = mFactory2.onCreateView(parent, name, context, attrs);
              /*
                省略
              */
        }
    
    Factory2是一个接口,而我们发现AppCompatDelegateImplV9 实现了LayoutInflaterFactory
    class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
            implements MenuBuilder.Callback, LayoutInflaterFactory 
    
    查看LayoutInflaterFactory注释可知LayoutInflater.Factory2与LayoutInflaterFactory是一样的,所以mFactory2.onCreateView的方法实际上就是调用AppCompatDelegateImplV9 中的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);
            if (view != null) {
                return view;
            }
    
            // If the Factory didn't handle it, let our createView() method try
            return createView(parent, name, context, attrs);
        }
    
    调用了createView方法,通过AppCompatViewInflater返回了View对象,那么AppCompatViewInflater究竟做了什么处理?
        @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);
    
            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的createView方法,我们发现,里面做了一个偷梁换柱的处理,将我们xml中写的控件改成了AppCompat控件,来达到兼容的目的。
    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;
    
            // 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;
            }
              /*
                省略
              */
    
            return view;
        }
    
    总结:
    AppCompatDelegate 的工作就是涂色。
    替换:widget着色是通过这个widget 的layout 在inflation 的时候,被AppCompatDelegate 拦截下来,然后根据控件的名字,强制被系统转换成为 以AppCompat 开头的控件,兼容控件继承原控件,在使用过程中,开发人员写法和原来一样。
    AppCompat+类图.png

    相关文章

      网友评论

        本文标题:源码级分析AppCompatActivity适配过程

        本文链接:https://www.haomeiwen.com/subject/nuklhxtx.html