平方X

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 2751|回复: 0

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

[复制链接]

414

主题

709

帖子

3658

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
3658
QQ
发表于 2017-9-30 16:18:28 | 显示全部楼层 |阅读模式
[md]

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

# 0x00 其他实现方案
[Android换肤技术总结](http://blog.zhaiyifan.cn/2015/09 ... %E6%80%BB%E7%BB%93/)
>## 内部资源加载方案
>## 动态资源加载方案
>### resource替换
>### 自定义LayoutInflator.Factory
>### Hack Resources internally

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

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

[AndroidChangeSkin](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[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = 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 重新加载资源)。


[/md]
我是平方X~
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|手机版|Archiver|平方X ( 冀ICP备14018164号 )

GMT+8, 2024-11-24 08:18 , Processed in 0.092132 second(s), 22 queries .

技术支持:Powered by Discuz!X3.4  © 2001-2013 Comsenz Inc.

版权所有:Copyright © 2014-2018 平方X www.pingfangx.com All rights reserved.

快速回复 返回顶部 返回列表