Android反射修改buildConfigField生成的属性失效问题
Posted Ever69
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android反射修改buildConfigField生成的属性失效问题相关的知识,希望对你有一定的参考价值。
在开发的时候,我们经常需要在项目的build.gradle文件中通过buildConfigField方法在BuildConfig类中生成一些常量属性供项目运行使用,比如一些第三方SDK的id和key,或是根据打包环境或者渠道对应的接口服务器地址等等。
现在问题来了,我们希望不止在打包时可以切换对应环境的接口服务器地址,在App运行时也可以切换,因为打包时的切换只算一种静态切换,它在编译时就已经将地址设定好了,如果想换一个地址,只能再从新打包,这在某些情况下就显得非常麻烦。
所以我们需要在支持“静态切换”的基础上再支持“动态切换”,那么怎么做呢,我们知道“静态切换”是通过在编译时生成修改BuildConfig类来完成的,这个BuildConfig类内部都是一些常量,并且不支持我们直接进行编辑,那么如何才能在程序运行时修改BuildConfig类中的属性呢?那自然是通过反射大法了,思路有了,直接上代码。
首先我在build.gradle文件中添加BASE_URL属性
buildConfigField "String","BASE_URL","\\"https://www.baidu.com\\""
然后在java代码中去修改这个值
private void changeBuildConfigField(String fieldName)
try
Log.e("kkk", "修改前");
printBaseUrl();
BuildConfig config = new BuildConfig();
Field declaredField = config.getClass().getDeclaredField(fieldName);
declaredField.setAccessible(true);
declaredField.set(config, "https://www.csdn.net");
Log.e("kkk", "修改后");
printBaseUrl();
catch (Exception e)
e.printStackTrace();
private void printBaseUrl()
Log.e("kkk", BuildConfig.BASE_URL);
运行查看log发现
E/kkk: 修改前
E/kkk: https://www.baidu.com
E/kkk: 修改后
E/kkk: https://www.baidu.com
WTF!?反射后竟然没有修改?
这是为啥,是我写代码姿势不对吗?于是我翻前倒后的在百度出来的答案中寻找原因,
在一番艰苦的查找过后,皇天不负有心人,终于被我找到原因了。
其实不是反射没有成功,而是编译器在编译的时候做了一些优化,它直接将BuildConfig里的常量的值替换了你所用到其引用的地方,这么说是不是有点听不懂,直接看编译后的class文件就懂了。
//编译前
private void printBaseUrl()
Log.e("kkk", BuildConfig.BASE_URL);
//编译后
private void printBaseUrl()
Log.e("kkk", "https://www.baidu.com");
注意上面的printBaseUrl方法,我在java里面输出的是BuildConfig.BASE_URL这个引用的值,再看看编译后的class文件,它是直接将这个字符串输出了。所以即使我们再怎么反射修改BASE_URL这个值,在这个方法内输出的还是原来的内个值。
不信,我们直接将反射修改后的BASE_URL打印出来看看。
//在changeBuildConfigField()方法内添加以下几行代码
Object o = declaredField.get(config);
Log.e("kkk","直接输出BASE_URL");
Log.e("kkk",o.toString());
E/kkk: 修改前
E/kkk: https://www.baidu.com
E/kkk: 修改后
E/kkk: https://www.baidu.com
E/kkk: 直接输出BASE_URL
E/kkk: https://www.csdn.net
嗨,看看这编译器干的好事,那怎么才能解决编译器优化产生的这个值替换引用的问题呢?
我这有两种解决方案,
第一种,避免直接使用,在需要用到BuildConfig属性的地方通过反射获取属性再使用,就像上面直接输出那里一样,相比第二种方案使用上略显麻烦。
第二种,这个也是在网上得到的答案,就是让BuildConfig中的属性至少经过一次运算再赋值,这样编译器在编译的时候就不会再将值替换引用。
这里我就直接验证第二种方案了,第一种在上面的示例中已经证明是可行的了。
让属性至少经过一次运算再赋值,网上提供的方法不少,比如,
private final String BASE_URL = new StringBuilder("https://www.baidu.com").toString();
或是修改属性类型,因为编译器只会对String和其他基础类型进行这种优化。
private final StringBuilder BASE_URL = new StringBuilder("https://www.baidu.com");
再或者是使用null判断
private final String BASE_URL = (null == null ? "https://www.baidu.com" : "");
方法倒不少,我们挨个都试一遍。
在build.gradle文件中添加三种不同方法生成的属性
//使用StringBuilder.toString()赋值String
buildConfigField "String", "BASE_URL", "new StringBuilder(\\"https://www.baidu.com\\").toString()"
//修改为StringBuilder类型
buildConfigField "StringBuilder", "BASE_URL2", " new StringBuilder(\\"https://www.baidu.com\\")"
//null判断
buildConfigField "String", "BASE_URL3", "null==null?\\"https://www.baidu.com\\":\\"\\""
添加完后编译一下,查看BuildConfig文件是否正确生成
public final class BuildConfig
...
// Fields from default config.
public static final String BASE_URL = new StringBuilder("https://www.baidu.com").toString();
public static final StringBuilder BASE_URL2 = new StringBuilder("https://www.baidu.com");
public static final String BASE_URL3 = null==null?"https://www.baidu.com":"";
ok,完美,看到这都感觉不用往下验证了,应该没问题。
private void changeBaseUrl()
try
Log.e("kkk", "\\n通过StringBuilder.toString判断设置的");
Log.e("kkk", "修改前");
printBaseUrl();
BuildConfig config = new BuildConfig();
Field declaredField = config.getClass().getDeclaredField("BASE_URL");
declaredField.setAccessible(true);
declaredField.set(config, "https://www.csdn.net");
Log.e("kkk", "修改后");
printBaseUrl();
catch (Exception e)
e.printStackTrace();
private void changeBaseUr2()
try
Log.e("kkk", "\\n通过修改属性类型为StringBuilder设置的");
Log.e("kkk", "修改前");
printBaseUrl2();
BuildConfig config = new BuildConfig();
Field declaredField = config.getClass().getDeclaredField("BASE_URL2");
declaredField.setAccessible(true);
declaredField.set(config, new StringBuilder("https://www.csdn.net"));
Log.e("kkk", "修改后");
printBaseUrl2();
catch (Exception e)
e.printStackTrace();
private void changeBaseUr3()
try
Log.e("kkk", "\\n通过null判断设置的");
Log.e("kkk", "修改前");
printBaseUrl3();
BuildConfig config = new BuildConfig();
Field declaredField = config.getClass().getDeclaredField("BASE_URL3");
declaredField.setAccessible(true);
declaredField.set(config, "https://www.csdn.net");
Log.e("kkk", "修改后");
printBaseUrl3();
catch (Exception e)
e.printStackTrace();
private void printBaseUrl()
Log.e("kkk", BuildConfig.BASE_URL);
private void printBaseUrl2()
Log.e("kkk", BuildConfig.BASE_URL2.toString());
private void printBaseUrl3()
Log.e("kkk", BuildConfig.BASE_URL3);
输出结果
E/kkk: 通过StringBuilder.toString判断设置的
E/kkk: 修改前
E/kkk: https://www.baidu.com
E/kkk: 修改后
E/kkk: https://www.csdn.net
E/kkk: 通过修改属性类型为StringBuilder设置的
E/kkk: 修改前
E/kkk: https://www.baidu.com
E/kkk: 修改后
E/kkk: https://www.csdn.net
E/kkk: 通过null判断设置的
E/kkk: 修改前
E/kkk: https://www.baidu.com
E/kkk: 修改后
E/kkk: https://www.csdn.net
nice,都修改成功了。
再看看编译后的class文件
private void printBaseUrl()
Log.e("kkk", BuildConfig.BASE_URL);
private void printBaseUrl2()
Log.e("kkk", BuildConfig.BASE_URL2.toString());
private void printBaseUrl3()
Log.e("kkk", BuildConfig.BASE_URL3);
没有替换成常量,完美解决~
以上是关于Android反射修改buildConfigField生成的属性失效问题的主要内容,如果未能解决你的问题,请参考以下文章