很多时候,我们遇到问题,或者为了实现功能。
都回去寻求各种方案,针对这些方案,我们很可能就考虑:
能不能实现?
能不能解决?
其实,当我们 找到了实现方案并且实现了,或者 找到了解决方案并且解决了。都会觉得瞬间喜悦。而又稍微 忽略了一些东西。
问题追溯
由于 Android Studio 3.1 Canary 发布,也带来了 Android gradle plugin 3.1.0-alpha01。
随之而来的问题就是:R2 插件 运行异常了!
Error:A problem occurred configuring project ':app'.
> Could not determine the dependencies of task ':app:processDebugResources'.
> Could not resolve all dependencies for configuration ':app:debugCompileClasspath'.
> A problem occurred configuring project ':widget'.
> Failed to notify project evaluation listener.
> com.android.build.gradle.tasks.ProcessAndroidResources.getPackageForR()Ljava/lang/String;
R2 插件
R2 插件 是从 butterknife 项目提取出来的。
作用:在 预编译期间 processDebugResources
任务执行后,执行 自定义任务 复制一份 R class 成为 R2 class。
R2 class 与 R class 的区别:在于 R2 class 的 field
都是 final
常量。R class 在 library module
的情况下 不是常量。这样就能保证任何 module
都可以取到 final
的 id
。
为什么 要 final
的 id
:
switch
自定义注解 的
value
其实这些看似不必要,但是侵入式的,会额外多一份 R2 class。
当然,可用可不用。
问题探索
String rPackage = processResources.packageForR
这句话引发的异常。原因是没有 getPackageForR()
方法。这里是 kotlin 语句,会自动调用 getPackageForR()
方法。
总结:
看了 Android gradle plugin 3.1.0-alpha01 版本 的 ProcessAndroidResources
源码,发现没有 packageForR Field
,更没有 getPackageForR()
方法 。
尝试解决
Android gradle plugin 3.0.0 版本 的 ProcessAndroidResources
源码 中发现一段:
String packageForR = null;
File srcOut = null;
File symbolOutputDir = null;
File proguardOutputFile = null;
File mainDexListProguardOutputFile = null;
if(generateCode) {
packageForR = this.originalApplicationId;
srcOut = this.getSourceOutputDir();
if(srcOut != null) {
FileUtils.cleanOutputDir(srcOut);
}
symbolOutputDir = (File)this.textSymbolOutputDir.get();
proguardOutputFile = this.getProguardOutputFile();
mainDexListProguardOutputFile = this.getMainDexListProguardOutputFile();
}
但是,讲述到了和 originalApplicationId
有关。而且这里的是 局部变量,值得参考,但是不用完全确定。也有可能 if
不走的话,就有问题。
关于成员变量 packageForR
的赋值是在 ConfigAction#execute
方法内:
@Override
public void execute(@NonNull ProcessAndroidResources processResources) {
final BaseVariantData variantData = variantScope.getVariantData();
...
final GradleVariantConfiguration config = variantData.getVariantConfiguration();
...
processResources.packageForR =
TaskInputHelper.memoize(
() -> {
String splitName = config.getSplitFromManifest();
if (splitName == null) {
return config.getOriginalApplicationId();
} else {
return config.getOriginalApplicationId() + "." + splitName;
}
});
...
}
可以说,需要获取 variantScope
就可以了。
但是,属于一个 私有的成员变量:
public class ProcessAndroidResources extends IncrementalTask {
...
private VariantScope variantScope;
...
}
于是就想到了,用 kotlin
写一个反射获取 VariantScope
的方法。
private fun ProcessAndroidResources.getVariantScope(): VariantScope {
val property = ProcessAndroidResources::class
.declaredMemberProperties
.find { it.name == "variantScope" } as KProperty1<*, *>
property.isAccessible = true
val value = property.getter.call(this)
return value as VariantScope
}
然后,之前的:
String rPackage = processResources.packageForR
可以改为:
val variantScope = processResources.getVariantScope()
val variantData = variantScope.variantData
val config = variantData.variantConfiguration
val splitName = config.splitFromManifest
val rPackage = if (splitName == null) {
config.originalApplicationId
} else {
config.originalApplicationId + "." + splitName
}
思考方式
https://github.com/JakeWharton/butterknife/pull/1128
JakeWharton 的回答:不是一个合适的解决方案,因为在插件的内部增加了更多的依赖关系。
这次改动确实增加了 kotlin.reflect
的依赖。
可能国外人认为精简引入过多的依赖比较好。而且此次 Android gradle plugin 3.1.0-alpha01 的改动,导致 ProcessAndroidResources
删了一些 field
所致。
如果为此引入过多依赖,去达到目的、去实现。可能国外人认为得不偿失,我也想到他的解决方案可能是去 google issue 提个 feature。期望下个 alpha 版本的 android gradle plugin 增加这个 field
。
但是,回过头来想,获取工作紧急上就这么写了。也有理由,因为 gradle plugin 不影响 apk 的大小。只是一个在编译时期的工具。
或许,以后,我应该以更长远的未知领域为目标,去看待,去想想这些角度和问题。