Android 中的模块化

Cuy*_*yer 0 android gradle android-gradle-plugin gradle-kotlin-dsl

我正在尝试了解 Android 中的模块化是如何工作的。我正在使用 Kotlin DSL。

在每个 build.gradle.kts 文件中,我是否必须添加默认配置,例如 minSdk、目标 SDK 等?换句话说,我是否只能在应用程序模块 build.gradle.kts 文件中设置配置,而在其他模块中(例如功能)是否可以只声明依赖项?

NowInAndroid 应用程序的 build.gradle.kts 文件具有以下代码:

plugins {
    id("nowinandroid.android.feature")
    id("nowinandroid.android.library.compose")
    id("nowinandroid.android.library.jacoco")
}

android {
    namespace = "com.google.samples.apps.nowinandroid.feature.bookmarks"
}

dependencies {
    implementation(libs.androidx.compose.material3.windowSizeClass)
}

Run Code Online (Sandbox Code Playgroud)

它只声明依赖项和插件,但我认为这些插件中有不同的配置。

我看到的另一种方法是使用继承其他 build.gradle 文件

apply {
 (path to build.gradle) file
}
Run Code Online (Sandbox Code Playgroud)

但使用该方法时,我无法声明名称空间,因此我必须将其添加到 build.gradle 文件中,该文件在不同功能之间共享,如果没有它,我将无法访问给定功能模块中的资源。

bql*_*ang 5

多项目构建中的构建逻辑可以组织成可重用的插件(Gradle 约定插件)。
\n约定插件并不是指特定的插件(如 com.android.library),而是指一类预编译的插件,其作用是提取一些共享的构建逻辑。

\n
\n

首先,让我们手动创建一个build-logic具有以下结构的新模块调用:

\n
rootProject\n\xe2\x94\x9c\xe2\x94\x80build-logic\n\xe2\x94\x82  \xe2\x94\x82  settings.gradle.kts\n\xe2\x94\x82  \xe2\x94\x94\xe2\x94\x80convention\n\xe2\x94\x82      \xe2\x94\x82  build.gradle.kts\n\xe2\x94\x82      \xe2\x94\x94\xe2\x94\x80src\n\xe2\x94\x82          \xe2\x94\x94\xe2\x94\x80main\n\xe2\x94\x82              \xe2\x94\x94\xe2\x94\x80kotlin\n\xe2\x94\x82                    AndroidLibraryConventionPlugin.kt\n\xe2\x94\x9c\xe2\x94\x80...\n
Run Code Online (Sandbox Code Playgroud)\n
\n

我们将通过组合构建将该模块包含build-logic在根项目中。

\n
\n

复合构建只是包含其他构建的构建。在许多方面,复合构建与 Gradle 多项目构建类似,只不过它不包含单个项目,而是包含完整的构建。

\n
\n

rootProject/settings.gradle.kts

\n
rootProject\n\xe2\x94\x9c\xe2\x94\x80build-logic\n\xe2\x94\x82  \xe2\x94\x82  settings.gradle.kts\n\xe2\x94\x82  \xe2\x94\x94\xe2\x94\x80convention\n\xe2\x94\x82      \xe2\x94\x82  build.gradle.kts\n\xe2\x94\x82      \xe2\x94\x94\xe2\x94\x80src\n\xe2\x94\x82          \xe2\x94\x94\xe2\x94\x80main\n\xe2\x94\x82              \xe2\x94\x94\xe2\x94\x80kotlin\n\xe2\x94\x82                    AndroidLibraryConventionPlugin.kt\n\xe2\x94\x9c\xe2\x94\x80...\n
Run Code Online (Sandbox Code Playgroud)\n
\n
\n

包含在复合构建中的构建自然被称为“包含的构建”。包含的构建不与复合构建或其他包含的构建共享任何配置。每个包含的构建都是独立配置和执行的。

\n
\n

由于包含的构建是完全独立的,因此我们需要在 中声明存储库源settings.gradle.kts,并显式声明版本目录文件的路径。

\n

rootProject/build-logic/settings.gradle.kts

\n
pluginManagement {\n    includeBuild("build-logic") // include build-logic module\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\nrootProject.name = "GradleConventionPluginsSample"\ninclude(":app")\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,您需要确保文件 rootProject/gradle/verison.toml 存在,并向其中添加以下行。

\n

rootProject/gradle/verison.toml

\n
dependencyResolutionManagement {\n    repositories {\n        google()\n        mavenCentral()\n    }\n    versionCatalogs {\n        create("libs") {\n            // make sure the file rootProject/gradle/verison.toml exists!\n            from(files("../gradle/libs.versions.toml"))\n        }\n    }\n}\n\nrootProject.name = "build-logic"\ninclude(":convention")\n
Run Code Online (Sandbox Code Playgroud)\n
\n

rootProject/build-logic/convention/build.gradle.kts

\n
[versions]\n...\nandroidGradlePlugin = "8.1.2"\nkotlin = "1.9.10"\n\n\n[libraries]\n...\n# Dependencies of the included build-logic\nandroid-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }\nkotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }\n\n[plugins]\n...\n
Run Code Online (Sandbox Code Playgroud)\n

在 的文件中build-logic/convention/build.gradle.kts,我们应用kotlin-dsl插件,添加依赖项...最后注册约定插件。

\n

请注意,我们还没有实现implementationClass,这是我们的最后一步。

\n
\n

将共享构建逻辑放入其中,AndroidLibraryConventionPlugin以便我们可以在其他 Android 库模块中重用它。

\n

rootProject/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt

\n
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile\n\nplugins {\n    `kotlin-dsl`\n}\n\ngroup = "com.bqliang.gradleconventionplugins.buildlogic"\n\n// Configure the build-logic plugins to target JDK 17\n// This matches the JDK used to build the project, and is not related to what is running on device.\njava {\n    sourceCompatibility = JavaVersion.VERSION_17\n    targetCompatibility = JavaVersion.VERSION_17\n}\ntasks.withType<KotlinCompile>().configureEach {\n    kotlinOptions {\n        jvmTarget = JavaVersion.VERSION_17.toString()\n    }\n}\n\ndependencies {\n    compileOnly(libs.android.gradlePlugin)\n    compileOnly(libs.kotlin.gradlePlugin)\n}\n\ngradlePlugin {\n    // register the convention plugin\n    plugins {\n        register("androidLibrary") {\n            id = "bqliang.android.library"\n            implementationClass = "AndroidLibraryConventionPlugin"\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

我的Gradle版本是8.4,如果你在上面的代码中找不到某些类,你可以先尝试升级你的Gradle版本。
\n rootProject/gradle/wrapper/gradle-wrapper.properties:
\ndistributionUrl=https://services.gradle.org/distributions/gradle-8.4-bin.zip

\n
\n
\n

因为我们已经在使用版本目录。为什么不把我们刚刚创建的 AndroidLibraryConventionPlugin 的插件 id 放在libs.versions.toml文件中呢?

\n

rootProject/gralde/libs.versions.toml

\n
import com.android.build.api.dsl.CommonExtension\nimport com.android.build.gradle.LibraryExtension\nimport org.gradle.api.JavaVersion\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport org.gradle.kotlin.dsl.configure\nimport org.gradle.kotlin.dsl.withType\nimport org.jetbrains.kotlin.gradle.tasks.KotlinCompile\n\nclass AndroidLibraryConventionPlugin : Plugin<Project> {\n    override fun apply(project: Project) {\n        with(project) {\n            with(pluginManager) {\n                apply("com.android.library")\n                apply("org.jetbrains.kotlin.android")\n            }\n\n            extensions.configure<LibraryExtension> {\n                configureKotlinAndroid(this)\n                defaultConfig.targetSdk = 34\n            }\n        }\n    }\n\n    private fun Project.configureKotlinAndroid(\n        commonExtension: CommonExtension<*, *, *, *, *>,\n    ) {\n        commonExtension.apply {\n            compileSdk = 34\n\n            defaultConfig {\n                minSdk = 26\n            }\n\n            compileOptions {\n                sourceCompatibility = JavaVersion.VERSION_11\n                targetCompatibility = JavaVersion.VERSION_11\n            }\n        }\n\n        configureKotlin()\n    }\n\n    private fun Project.configureKotlin() {\n        // Use withType to workaround https://youtrack.jetbrains.com/issue/KT-55947\n        tasks.withType<KotlinCompile>().configureEach {\n            kotlinOptions {\n                // Set JVM target to 11\n                jvmTarget = JavaVersion.VERSION_11.toString()\n            }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

现在我们可以在其他android库模块中使用AndroidLibraryConventionPlugin了。创建一个由 Android Studio 调用的新 Android 库模块mylibrary。module中的build.gradle.ktsmylibrary如下:

\n
[versions]\n...\n\n[libraries]\n...\n\n[plugins]\nbqliang-android-library = { id = "bqliang.android.library", version = "unspecified" }\n...\n
Run Code Online (Sandbox Code Playgroud)\n

我们已将上述有关 Android 库的构建逻辑放入 中AndroidLibraryConventionPlugin,因此我们可以删除它们并仅应用我们的约定插件。

\n
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed\nplugins {\n    alias(libs.plugins.com.android.library)\n    alias(libs.plugins.org.jetbrains.kotlin.android)\n}\n\nandroid {\n    namespace = "com.bqliang.mylibrary"\n    compileSdk = 33\n\n    defaultConfig {\n        minSdk = 24\n\n        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"\n        consumerProguardFiles("consumer-rules.pro")\n    }\n\n    ...\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n    kotlinOptions {\n        jvmTarget = "1.8"\n    }\n}\n\ndependencies {\n    ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n

伟大的!我们创建了一个可重用的约定插件,可以在多个项目之间共享构建逻辑。通过这种方式,我们可以从不同的模块中抽象出相同的构建逻辑(如compileSdk、sourceCompatibility、targetCompatibility...)。据我所知,现在Android项目中也使用这种方式。

\n

上面只是一个非常简单的示例,实际上您可以在约定插件中共享更多构建逻辑,例如productFlavors、Jetpack Compose、Room、构建类型、测试等。

\n

这些插件是可附加的和可组合的,并尝试只完成单一职责。然后模块可以选择它们需要的配置。

\n
\n

参考:

\n\n