换肤、字体库使用出现的问题.md

换肤原理(android-skin-support)

插件换肤需要设置layoutInflater的Factory2。调用LayoutInflater::setFactory2(每个LayoutInflater都需要设置一次)。因此在Activity onCreate的时候统一设置 setFactory2。

备注:下面所说的Factory,都为 LayoutInflater.Factory 接口的实现类。

换肤冲突(calligraphy)

calligraphy 为 app 中已有的一套全局设置字体的框架。此框架会拦截 LayoutInflate (通过调用Activity的 attachBaseContext)。并对LayoutInflater 设置默认的 mFactory。

Fragment 换肤问题:

1.因为同时设置Factory所以Factory不能同时生效。

2.出现如下异常。

    java.lang.IllegalStateException: A factory has already been set on this LayoutInflater
        at android.view.LayoutInflater.setFactory2(LayoutInflater.java:369)
        at android.support.v4.view.LayoutInflaterCompat.setFactory2(LayoutInflaterCompat.java:139)
        at android.support.v4.app.Fragment.getLayoutInflater(Fragment.java:1332)
        at android.support.v4.app.Fragment.onGetLayoutInflater(Fragment.java:1277)
        at android.support.v4.app.Fragment.performGetLayoutInflater(Fragment.java:1308)
问题分析
  • 正常情况:onStart的时候,activity 会分发 Fragment 的 create 事件,view 创建之前会调用

    Fragment::performGetLayoutInflater()方法来获取 LayoutInflater。performGetLayoutInflater会调用调用 Fragment::onGetLayoutInflater() -> FragmentHostCallback::onGetLayoutInflater() -> FragmentActivity.this.getLayoutInflater().cloneInContext(FragmentActivity.this); 来得到一个新的LayoutInflater.

    FragmentActivity::getLayoutInflater()里面调用 getWindow().getLayoutInflater() 。window为PhoneWindow,所以这里的Inflater的实现类为 PhoneLayoutInflater。

    cloneInContext(Context newContext)中,通过原来的PhoneLayoutInflater 又创建一个新的 PhoneLayoutInflater ,并继承原来的Factory等属性。(因为LayoutInflater为新创建的,虽然保留了Activity中Inflater的Factory,但调用setFactory不会抛异常,会重新进行赋值)。

  • 实际情况:

    calligraphy字体库使用时会调用

        override fun attachBaseContext(newBase: Context?) {
    //        super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase))
            super.attachBaseContext(CalligraphyContextWrapper(newBase))
        }
    

    attachBaseContext()方法可以拦截 ContextImpl 中的实现方法。此字体库对 getSystemService() 方法进行了拦截,并对其中的Inflater服务进行拦截处理

        public Object getSystemService(String name) {
            if (LAYOUT_INFLATER_SERVICE.equals(name)) {
                if (mInflater == null) {
                    mInflater = new CalligraphyLayoutInflater(LayoutInflater.from(getBaseContext()), this, mAttributeId);
                }
                return mInflater;
            }
            return super.getSystemService(name);
        }
    

    并在构造方法中对Factory进行了设置

        protected CalligraphyLayoutInflater(LayoutInflater original, Context newContext, int attributeId) {
            super(original, newContext);
            mAttributeId = attributeId;
            setUpLayoutFactory();
        }
    
        private void setUpLayoutFactory() {
            if (!(getFactory() instanceof CalligraphyFactory)) {
                setFactory(new CalligraphyFactory(getFactory(), mAttributeId));
            }
        }
    

    如果字体皮肤框架的Factory没有和其他Factory冲突的话,这样设置是没问题的,但是Factory已经被皮肤框架给换了,所以这里 !(getFactory() instanceof CalligraphyFactory) = true 。因此就会对Fragment的LayoutInflater设置了Factory ,但是Fragment的Inflater默认自己会设置工厂,这里就会有冲突,导致出现上面的异常。

问题解决
  • 将此字体框架升到最新版本(2.3.0),最新版本中对cloneInContext 创建的Inflater(Fragment之类使用的LayoutInflater)不进行设置Factory。并且在CalligraphyLayoutInflater中对setFactory2进行了重写

      @Override
        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        public void setFactory2(Factory2 factory2) {
            // Only set our factory and wrap calls to the Factory2 trying to be set!
            if (!(factory2 instanceof WrapperFactory2)) {
    //            LayoutInflaterCompat.setFactory(this, new WrapperFactory2(factory2, mCalligraphyFactory));
                super.setFactory2(new WrapperFactory2(factory2, mCalligraphyFactory));
            } else {
                super.setFactory2(factory2);
            }
        }
    

    android-skin-support和calligraphy结合的调用流程:

    • 因为字体和换肤都要设置Factory,而字体库不仅拦截了 Factory 还对 LayoutInflater 进行了拦截,所以优先被 CalligraphyLayoutInflater 进行拦截。

    • 当换肤拦截器调用 setFactory2(),会执行到 CalligraphyLayoutInflater 的 setFactory2 中。

    • 当View创建的时候则会调用 WrapperFactory2.onCreateView() ==>这个factory包含两个信息,一个是原factory2(SkinCompatDelegate),一个是字体的 mCalligraphyFactory (CalligraphyFactory)。

    • WrapperFactory2.onCreateView()中先使用原来的factory2(SkinCompatDelegate)去创建View (创建换完皮肤的View)。将创建好的View拿过来调用 mCalligraphyFactory.onViewCreated 来对View进行字体的设置。

复现问题Demo地址:https://github.com/zhonghaohu/SkinTest.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值