网易换肤第一篇:换肤技术解密!

参考


脑图:https://note.youdao.com/s/Q1e6r39j

最终效果:
在这里插入图片描述
Demo源码:点击跳转

技术点分析


换肤的核心思路主要是在setContentView()之前调用setFactory2()来收集控件属性,然后在Factory的onCreateView()中利用收集到的属性来创建view。

不懂?没事,往下看。

在这里插入图片描述
弄明白换肤技术的实现之前,得有上图这几个知识储备。

首先得知道控件是在setContentView()方法中通过XmlPullParser解析我们在xml中定义的控件,然后显示在界面上的

LayoutInflater.java(451,注:本文源码为安卓9.0,api 28,下同

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
...
if (TAG_MERGE.equals(name)) {
	...
	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);
	...
}
...

而且在createViewFromTag()方法中,有一个判断:当mFactory2 != null的时候,就会把从xml中解析到的属性等传给mFactory2.onCreateView(parent, name, context, attrs)方法,利用mFactory2来创建view。

先看源码片段:
LayoutInflater.java(748)

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    ...

    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;
    } catch (InflateException e) {
        ...
    }
}

所以,我们只要通过LayoutlnflaterCompat.setFactory2(xx, yу)设置了Factory,就可以拦截到所有控件及其在xml中定义的属性了。

如此一来,问题就变成了如何在setContentView(R.layout.xxx)之前setFactory2()

答案就是利用AOP方法切面:registerActivityLifecycleCallbacks(xxx)ActivityLifecycleCallbacksonActivityCreated()方法正是在setContentView(R.layout.xxx)之前执行。

所以,我们可以实现Application.ActivityLifecycleCallbacks,然后在onActivityCreated()方法中LayoutInflaterCompat.setFactory2(xx, yy),这样换肤技术的核心部分,就被我们突破了。

参考代码:

public class SkinActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
	...
    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        ...

        skinFactory = new SkinFactory(activity);
        // mFactorySet = true是无法设置成功的(源码312行)
        LayoutInflaterCompat.setFactory2(layoutInflater, skinFactory);

        // 注册观察者(监听用户操作,点击了换肤,通知观察者更新)
        SkinEngine.getInstance().addObserver(skinFactory);
    }

   	...
}


About

网易换肤第一篇:换肤技术解密!
网易换肤第二篇:本地换肤实现!
网易换肤第三篇:动态换肤实现!

架构师系列文章一览

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 21
    评论
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT小瓯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值