平方X 发表于 2017-9-30 16:18:28

阅读《Debug 7.1.1源码一步步写Android换肤框架》



感谢[徐爱卿.《Debug 7.1.1源码一步步写Android换肤框架》](http://www.jianshu.com/p/c8748fbf6f60)
[项目地址](https://github.com/xujianhui404/SKinAppDemo)

# 0x00 其他实现方案
(http://blog.zhaiyifan.cn/2015/09/10/Android%E6%8D%A2%E8%82%A4%E6%8A%80%E6%9C%AF%E6%80%BB%E7%BB%93/)
>## 内部资源加载方案
>## 动态资源加载方案
>### resource替换
>### 自定义LayoutInflator.Factory
>### Hack Resources internally

张鸿洋的两篇博客,在加载view时,对每个可变换的view进行保存,又对每个view的可变换的属性进行保存,然后换肤时调用 SkinAttrType 的apply 方法。
(https://github.com/hongyangAndroid/ChangeSkin)

```
    private String appendSuffix(String name)
    {
      if (!TextUtils.isEmpty(mSuffix))
            return name += "_" + mSuffix;
      return name ;
    }
```

(https://github.com/hongyangAndroid/AndroidChangeSkin)
```
    //com.zhy.changeskin.attr.SkinAttrSupport#getSkinView
    public static SkinView getSkinView(View view)
    {
      Object tag = view.getTag(R.id.skin_tag_id);
      if (tag == null)
      {
            tag = view.getTag();
      }
      if (tag == null) return null;
      if (!(tag instanceof String)) return null;
      String tagStr = (String) tag;

      List<SkinAttr> skinAttrs = parseTag(tagStr);
      if (!skinAttrs.isEmpty())
      {
            changeViewTag(view);
            return new SkinView(view, skinAttrs);
      }
      return null;
    }

    //com.zhy.changeskin.attr.SkinAttrType
    BACKGROUND("background")
            {
                @Override
                public void apply(View view, String resName)
                {
                  Drawable drawable = getResourceManager().getDrawableByName(resName);
                  if (drawable != null)
                  {
                        view.setBackgroundDrawable(drawable);
                  } else
                  {
                        try{
                            int color = getResourceManager().getColor(resName);
                            view.setBackgroundColor(color);
                        } catch (Resources.NotFoundException ex) {
                            ex.printStackTrace();
                        }
                  }
                }

```


# 0x01 源码追踪
之前只读了 infalte 的方法,没有注意 factory
```
android.app.Activity#startActivity(android.content.Intent)
android.app.Activity#startActivity(android.content.Intent, android.os.Bundle)
android.support.v4.app.FragmentActivity#startActivityForResult
android.app.Activity#startActivityForResult(android.content.Intent, int)
android.support.v4.app.BaseFragmentActivityJB#startActivityForResult
android.app.Activity#startActivityForResult(android.content.Intent, int, android.os.Bundle)
android.app.Instrumentation#execStartActivity(android.content.Context, android.os.IBinder, android.os.IBinder, android.app.Activity, android.content.Intent, int, android.os.Bundle)
android.app.ActivityManagerProxy#startActivity
...
android.app.ActivityThread.H#handleMessage
android.app.ActivityThread#handleLaunchActivity
android.app.ActivityThread#performLaunchActivity
android.app.Instrumentation#callActivityOnCreate(android.app.Activity, android.os.Bundle)
android.app.Activity#performCreate(android.os.Bundle)
...
android.support.v7.app.AppCompatActivity#onCreate
android.support.v7.app.AppCompatDelegateImplV9#installViewFactory
android.support.v4.view.LayoutInflaterCompat#setFactory
android.support.v4.view.LayoutInflaterCompat.LayoutInflaterCompatImplV21#setFactory
android.support.v4.view.LayoutInflaterCompatLollipop#setFactory
android.view.LayoutInflater#setFactory2
```
到这里就设置了mFactory2 ,后面的 infalte 流程以前已经分析过了。
```
android.support.v7.app.AppCompatActivity#setContentView(int)
android.support.v7.app.AppCompatDelegateImplV9#setContentView(int)
android.view.LayoutInflater#inflate(int, android.view.ViewGroup)
android.view.LayoutInflater#inflate(org.xmlpull.v1.XmlPullParser, android.view.ViewGroup, boolean)
android.view.LayoutInflater#createViewFromTag(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet)
android.view.LayoutInflater#createViewFromTag(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet, boolean)
这里有好几个可加载的。
    ...
      try {
            View view;
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            if (view == null) {
                final Object lastContext = mConstructorArgs;
                mConstructorArgs = context;
                try {
                  if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                  } else {
                        view = createView(name, null, attrs);
                  }
                } finally {
                  mConstructorArgs = lastContext;
                }
            }

            return view;
      }
    ...
可以看到依次用 mFactory2、mFactory、mPrivateFactory及自己加载。

mFactory2 前面分析过了。
如果继承的android.support.v7.app.AppCompatActivity会到
android.support.v7.app.AppCompatDelegateImplV9#onCreateView
android.support.v7.app.AppCompatDelegateImplV9#callActivityOnCreateView
android.support.v7.app.AppCompatDelegateImplV9#createView
android.support.v7.app.AppCompatViewInflater#createView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet, boolean, boolean, boolean, boolean)


mFactory 在调试时没有调用,setFactory2 的时候会一起设置。

mPrivateFactory 如果继承的是Activity才会调用到,它的设置在
android.app.Activity#attach
mWindow.getLayoutInflater().setPrivateFactory(this);
参数为Factory2,要实现的方法为
android.view.LayoutInflater.Factory2#onCreateView
android.app.Activity#onCreateView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet)
android.app.Activity#onCreateView(java.lang.String, android.content.Context, android.util.AttributeSet)
了就是说可以重写此方法即可修改创建的view
...
```



# 0x02 实现思路
继承 LayoutInflaterFactory 并实现 onCreateView,
创建 view 后在 saveViewAttrs 中判断如果有支持换肤的属性则将 view 添加到列表中,要换肤时遍历列表执行变换。
变换时,调用 SkinManager 的方法,其可以以相同的 resId 针对不同的皮肤加载出不同的资源(换肤时 SkinManager 重新加载资源)。



页: [1]
查看完整版本: 阅读《Debug 7.1.1源码一步步写Android换肤框架》