1 背景
最近,把Sophix集成到了一个多渠道的项目里。但,第一次使用就遇坑了。代码里使用了BuildConfig.FLAVOR来判断当前属于哪个渠道,如:
有两个渠道:taobao和tianmao
Test.java类中使用了BuildConfig.FLAVOR:
if ("taobao".equals(BuildConfig.FLAVOR)){
toast("abc");
} else {
toast("hello");
}
现在需要把”abc”换成”xyz”。
更换后,想使用热修复来实现。大概步骤:
- 使用旧包打包时的mapping,生成新包(可以只生成一个渠道的包)
- 使用同一渠道的新旧包,生成补丁文件
- 上传补丁,灰度发布
在灰度发布时,发现taobao渠道APP已经修复。
而,tianmao渠道的APP,因为属于同一版本,肯定也会打补丁。但,没想到,toast的内容,不是原“hello”,却变成了“abc”。
如果使用tianmao渠道来生成补丁,那tianmao正常。但taobao渠道在补丁打完后,却变成了“hello”。
2 问题解决路途
后来咨询阿里技术支持,技术支持说使用白名单的方法。因为没找到mapping文件下的混淆映射关系,我就把BuildConfig类的原包名和类名加入了白名单:
com.ali.six.BuildConfig
结果,还是跟原来的效果一样。技术支持又提供思路:是不是被编译成了常量。
经过反编译发现:编译前的BuildConfig,在编译后没有了,代码中使用到BuildConfig里值的地方,都换成了常量值。
查看BuildConfig类后,发现此类是final类,内部都是成员变量,且都被public static final修饰。这样,编译后,使用它们的地方,肯定会变成常量值。
补丁,是通过比较新旧包的差异来生成的。所以,修改的代码,肯定属于差异范围,而这块代码中的BuildConfig.FLAVOR,一直是个常量值,由生成补丁的渠道决定。
这样,就解释了前面遇到的问题。
最后,使用版本升级的方式更新。
3 解决方案
但心有不甘,那么喜欢Sophix,就不能用了?通过技术支持提供的这些思路,还是找到了解决方法:把BuildConfig封装成一个类MyBuildConfig,让原来所有使用到BuildConfig的地方,改成使用这个类;并把这个类加入到白名单。具体做法:
1、 新建类MyBuildConfig(接口也行,视具体情况而定)
2、使用表达式,或方法,对外提供调用接口,如:MyBuildConfig.IS_FLAVOR_TAO_BAO。这个类,在taobao渠道编译后,这个类只会留下 a = “taobao”.equals(“taobao”)这个表达式。
public class MyBuildConfig {
/**
* 直接常量全部封装在类内,对外只提供方法、或表达式的变量
* 不提供可直接使用的常量,原因是:
* public static final String FLAVOR = BuildConfig.FLAVOR;
* 编译时,BuildConfig.FLAVOR是常量值,所有用到MyBuildConfig.FLAVOR的地方,也都会变成常量值,这样,还是影响到了代码
*/
private static final String FLAVOR_TAO_BAO = "taobao";
private static final String FLAVOR_TIAN_MAO = "tianmao";
public static final boolean IS_FLAVOR_TAO_BAO = FLAVOR_TAO_BAO.equals(BuildConfig.FLAVOR);
}
3、根据基线包的mapping,找到MyBuildConfig类在混淆后的路径,加入到白名单,如:com.ali.six.c
4、生成补丁文件
这样,就保证了因渠道不一样而影响逻辑的代码。如果,修复代码的地方跟渠道没有关系,那就不必使用MyBuildConfig