首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >源码级分析AppCompatActivity适配过程

源码级分析AppCompatActivity适配过程

作者头像
aruba
发布2020-07-03 10:33:46
7910
发布2020-07-03 10:33:46
举报
文章被收录于专栏:android技术android技术android技术
自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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 自android5.0推出以来,google大力宣扬Meterail Design这款视觉设计语言,在新系统上,大量的运用到了Meterail Design风格,显然这些效果低版本时并没有实现,那么google是如何在低版本中做兼容的?
  • 在as上新建项目时我们会发现activity会自动继承AppCompatActivity,它继承至FragmentActivity,查看AppCompatActivity源码我们会发现,以前FragmentActivity中的方法都会调用AppCompatDelegate的方法。这里以onCreate方法为例
  • AppCompatDelegate的create方法中做了sdk版本判断,分别返回了不同版本的AppCompatDelegate实现类
  • 通过源码我们发现setContentView() 方法实现是在 AppCompatDelegateImplV9 这个类中。这个时候我们可以猜想下,由于Activity是通过一个xmlpull解析器根据tag,将xml解析为一个个组件和控件的(详情自行了解Activity中setContentView作用),那么,是否google在解析xml时做了一定手脚,来达到兼容的目的?我们找到setContentView(int resId)方法
  • 通过inflate方法,我们最终找到createViewFromTag方法
  • 调用了onCreateView实例化view
  • Factory2是一个接口,而我们发现AppCompatDelegateImplV9 实现了LayoutInflaterFactory
  • 查看LayoutInflaterFactory注释可知LayoutInflater.Factory2与LayoutInflaterFactory是一样的,所以mFactory2.onCreateView的方法实际上就是调用AppCompatDelegateImplV9 中的onCreateView方法
  • 调用了createView方法,通过AppCompatViewInflater返回了View对象,那么AppCompatViewInflater究竟做了什么处理?
  • 查看AppCompatViewInflater的createView方法,我们发现,里面做了一个偷梁换柱的处理,将我们xml中写的控件改成了AppCompat控件,来达到兼容的目的。
  • 总结:
  • AppCompatDelegate 的工作就是涂色。
  • 替换:widget着色是通过这个widget 的layout 在inflation 的时候,被AppCompatDelegate 拦截下来,然后根据控件的名字,强制被系统转换成为 以AppCompat 开头的控件,兼容控件继承原控件,在使用过程中,开发人员写法和原来一样。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档