[应用出海] 使用 Gradle 解决 Android 模块化项目中的多语言支持
对使用 Monorepo 或者非模块的工程,处理字符串资源并不是麻烦事,所以不在本文的讨论范围。
APK 中的 resources.arsc
AAR 文件中的 res/values[-*] 目录
获取AAR文件
Gradle 中的 Configuration
需要说明的是:AGP 中的 implementation 和 api 等配置复用了 Java Plugin 中的配置定义。
afterEvaluate {
val configuration = configurations.getByName("implementation")
val resolve = configuration.resolve()
resolve.forEach {
println(it.absolutePath)
}
}
# Console Error #
A problem occurred configuring project ':app'.
> Resolving dependency configuration 'implementation' is not allowed as it is defined as 'canBeResolved=false'.
Instead, a resolvable ('canBeResolved=true') dependency configuration that extends 'implementation' should be resolved.
//已忽略无关代码
//https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dependency/VariantDependenciesBuilder.java
package com.android.build.gradle.internal.dependency;
...
public class VariantDependenciesBuilder {
...
private final Set<Configuration> compileClasspaths = Sets.newLinkedHashSet();
public VariantDependenciesBuilder addSourceSet(@Nullable DefaultAndroidSourceSet sourceSet) {
....
//获取compileOnly
compileClasspaths.add(configs.getByName(sourceSet.getCompileOnlyConfigurationName()));
//获取implementation
final Configuration implementationConfig =
configs.getByName(sourceSet.getImplementationConfigurationName());
compileClasspaths.add(implementationConfig);
....
}
public VariantDependencies build() {
...
final ConfigurationContainer configurations = project.getConfigurations();
final DependencyHandler dependencies = project.getDependencies();
final String compileClasspathName = variantName + "CompileClasspath";
Configuration compileClasspath = configurations.maybeCreate(compileClasspathName);
compileClasspath.setVisible(false);
compileClasspath.setDescription(
"Resolved configuration for compilation for variant: " + variantName);
compileClasspath.setExtendsFrom(compileClasspaths); //设置配置继承关系
...
compileClasspath.setCanBeConsumed(false);
compileClasspath
.getResolutionStrategy()
.sortArtifacts(ResolutionStrategy.SortOrder.CONSUMER_FIRST);
}
}
成功的尝试
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
releaseImplementation("com.squareup.okhttp3:okhttp:4.11.0")
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.espresso.core)
}
afterEvaluate {
val configuration = configurations.getByName("releaseRuntimeClasspath")
val resolve = configuration.resolve()
resolve.forEach {
println(it.absolutePath)
}
}
# Console Output #
> Configure project :app
/home/prosixe/.gradle/caches/modules-2/files-2.1/com.squareup.okhttp3/okhttp/4.11.0/436932d695b2c43f2c86b8111c596179cd133d56/okhttp-4.11.0.jar
/home/prosixe/.gradle/caches/modules-2/files-2.1/com.squareup.okio/okio-jvm/3.2.0/332d1c5dc82b0241cb1d35bb0901d28470cc89ca/okio-jvm-3.2.0.jar
/home/prosixe/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.8.0/ed04f49e186a116753ad70d34f0ac2925d1d8020/kotlin-stdlib-jdk8-1.8.0.jar
/home/prosixe/.gradle/caches/modules-2/files-2.1/androidx.core/core/1.9.0/aa21c91d72e5d2a8dcc00c029ec65fc8d804ce02/core-1.9.0.aar
/home/prosixe/.gradle/caches/modules-2/files-2.1/androidx.core/core-ktx/1.9.0/b56f6b1bcb7882a9933c963907818da2094ae3a4/core-ktx-1.9.0.aar
/home/prosixe/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.8.0/3c91271347f678c239607abb676d4032a7898427/kotlin-stdlib-jdk7-1.8.0.jar
/home/prosixe/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation-experimental/1.3.0/5087c6f545117dcd474e69e1a93cacec9d7334af/annotation-experimental-1.3.0.aar
....
AGP 中的 TransformAction
//代码地址:https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/DependencyConfigurator.kt
//ExtractAarTransform的注册
registerTransform(
ExtractAarTransform::class.java,
aarOrJarTypeToConsume.aar,//AndroidArtifacts.ArtifactType.AAR
AndroidArtifacts.ArtifactType.EXPLODED_AAR
)
//AarTransform的注册 getTransformTargets是不同的转换目标属性,循环注册
for (transformTarget in AarTransform.getTransformTargets(aarOrJarTypeToConsume)) {
dependencies.registerTransform(
AarTransform::class.java
) { spec ->
spec.from.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, AndroidArtifacts.ArtifactType.EXPLODED_AAR.type)
spec.from.attribute(Category.CATEGORY_ATTRIBUTE, libraryCategory)
spec.to.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, transformTarget.type)
spec.to.attribute(Category.CATEGORY_ATTRIBUTE, libraryCategory)
spec.parameters.projectName.setDisallowChanges(project.name)
spec.parameters.targetType.setDisallowChanges(transformTarget)
spec.parameters.sharedLibSupport.setDisallowChanges(sharedLibSupport)
}
}
----From AarTransform
@NonNull
public static ArtifactType[] getTransformTargets(AarOrJarTypeToConsume aarOrJarTypeToConsume) {
return new ArtifactType[] {
aarOrJarTypeToConsume.getJar(),
// For CLASSES, this transform is ues for runtime, and AarCompileClassesTransform is
// used for compile
ArtifactType.SHARED_CLASSES,
ArtifactType.JAVA_RES,
ArtifactType.SHARED_JAVA_RES,
ArtifactType.MANIFEST,
ArtifactType.ANDROID_RES,
ArtifactType.ASSETS,
ArtifactType.SHARED_ASSETS,
ArtifactType.JNI,
ArtifactType.SHARED_JNI,
ArtifactType.AIDL,
ArtifactType.RENDERSCRIPT,
ArtifactType.UNFILTERED_PROGUARD_RULES,
ArtifactType.LINT,
ArtifactType.ANNOTATIONS,
ArtifactType.PUBLIC_RES,
ArtifactType.COMPILE_SYMBOL_LIST,
ArtifactType.DATA_BINDING_ARTIFACT,
ArtifactType.DATA_BINDING_BASE_CLASS_LOG_ARTIFACT,
ArtifactType.RES_STATIC_LIBRARY,
ArtifactType.RES_SHARED_STATIC_LIBRARY,
ArtifactType.PREFAB_PACKAGE,
ArtifactType.AAR_METADATA,
ArtifactType.ART_PROFILE,
ArtifactType.NAVIGATION_JSON,
};
}
afterEvaluate {
val configuration = configurations.getByName("releaseRuntimeClasspath")
val artifacts = configuration.incoming.artifactView {
attributes.attribute(
AndroidArtifacts.ARTIFACT_TYPE,
AndroidArtifacts.ArtifactType.ANDROID_RES.type
)
}.artifacts
artifacts.artifactFiles.forEach {
println(it)
}
}
#output
/home/prosixe/.gradle/caches/transforms-3/b15e5c11ab5458dc40071e292632fa4c/transformed/core-1.9.0/res
/home/prosixe/.gradle/caches/transforms-3/5409291e0f20302a556f1f822f907835/transformed/core-ktx-1.9.0/res
/home/prosixe/.gradle/caches/transforms-3/7589258eaf55d137a37e590e9111e0d3/transformed/annotation-experimental-1.3.0/res
/home/prosixe/.gradle/caches/transforms-3/67e97da11edfa018e3236656720b711b/transformed/lifecycle-runtime-2.3.1/res
Gradle获取res目录
val extension = project.extensions.getByName("android") as BaseExtension
val sourceSets = extension.sourceSets
val mainSourceSets = sourceSets.findByName("main")
val dirs = mainSourceSets!!.res.srcDirs
参考资料
[1]AAPT2 源码:
https://cs.android.com/android/platform/superproject/+/master:frameworks/base/tools/aapt2/
[2]APK 分析器:
https://developer.android.com/studio/debug/apk-analyzer?hl=zh-cn
[3]APK 分析器:
https://developer.android.com/studio/debug/apk-analyzer?hl=zh-cn
[4]默认开启:
https://developer.android.com/studio/releases/past-releases/as-bumblebee-release-notes?hl=zh-cn#rclasses-default
[5]Java Plugin:
https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_plugin_and_dependency_management
[6]Configuration:
https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.Configuration.html
[7]ExtractAarTransform :
https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dependency/ExtractAarTransform.kt
[8]AarTransform :
https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dependency/AarTransform.java
[9]源码:
https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/publishing/AndroidArtifacts.java
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!