是时候弃用 buildSrc ,使用 Composing builds 加快编译速度了

作者:北海道浪子 链接:https://juejin.cn/post/7208015274079387707

e25a30c95d7706ec53e9c6d7ce9c3388.png

为什么要使用复合构建

依赖管理一直是一个优化痛点,从硬编码到ext,再发展到buildSrc,尽管表面上看复杂度在发展中增长了,但是对于追求更快更干净的构建来说确实进步了不少。不过buildSrc虽然给了我们相对干净的使用方式,但是依然没有解决最核心的编译速度问题,在编译过程中 Gradle 最大的低效就是它的单线程配置阶段,这意味着每个额外的模块都会对构建产生持续的开销,因此我们依然经历着配置时间的线性增长,通常大型项目编译一次,就要去喝杯咖啡。

使用 Gradle 的复合构建工具就避免了在其他构建模式时很容易观察到的配置时间损失,依赖不再是全量编译了。复合构建将大型项目构建分解为更小、更独立的块,这些块可以根据需要独立或一起工作,包含的构建不与复合构建或其他包含的构建共享任何配置。每个包含的构建都是独立配置和执行的。

更详细的对比,请参考大佬的再见吧 buildSrc, 拥抱 Composing builds 提升 Android 编译速度(https://juejin.cn/post/6844904176250519565),这里不再赘述。因为找到的相关使用文档均已过时,所以下面就记录下来最新的创建使用方法。

基本使用

创建版本依赖插件 Module

这个步骤可以手动创建,也可以借助 Android Studio 创建。

  • 手动创建
  1. 切换到 Project 视图,创建 version-plugin 文件夹,在  version-plugin 文件夹里创建 src -> main -> java 文件

  2. 在 java 文件夹里创建你的包名文件夹,例如 com -> example -> plugin (不想要包名文件夹的话,这一步可以省略),在 plugin 文件夹里创建两个文件Dependencies.ktVersionPlugin.kt

  3. 在 version-plugin 文件夹下创建build.gradle.kts文件,这里使用 kotlin DSL 更方便

  4. build.gradle.kts里添加所需的插件

    plugins {
        `kotlin-dsl`
    }
  5. 在version-plugin 根目录创建settings.gradle.kts,并添加依赖仓库

    dependencyResolutionManagement {
        repositories {
            google()
            mavenCentral()
        }
    }
    rootProject.name = "version-plugin"
    include (":version-plugin")
  6. 在项目根目录的settings.gradle里添加includeBuild("version-plugin")引入插件

    pluginManagement {
        includeBuild("version-plugin")
        repositories {
            google()
            mavenCentral()
            gradlePluginPortal()
        }
    }
    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            google()
            mavenCentral()
        }
    }
    rootProject.name = "ComposeBuild"
    include ':app'
AS创建

a1753500f58621e898d922bea32e9966.jpeg

  1. File -> New -> New Module ,选择 Java or kotlin Library,创建一个 Module

  2. 创建Dependencies.kt文件

  3. 删除 version-plugin 文件夹下的 libs 文件夹

  4. build.gradle转化为build.gradle.kts文件

    plugins {
        `kotlin-dsl`
    }

    在 version-plugin 根目录创建settings.gradle.kts,并添加依赖仓库

    dependencyResolutionManagement {
        repositories {
            google()
            mavenCentral()
        }
    }
    rootProject.name = "version-plugin"
    include (":version-plugin")

    项目根目录settings.gradle里的include ':version-plugin'替换为includeBuild("version-plugin"),为了规范,把它注册在上面的pluginManagement里

    pluginManagement {
        includeBuild("version-plugin")
        repositories {
            google()
            mavenCentral()
            gradlePluginPortal()
        }
    }
    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            google()
            mavenCentral()
        }
    }
    rootProject.name = "ComposeBuild"
    include ':app'
    //include ':version-plugin'

    完成后的项目目录:961373e40f611f25837671a01665f506.jpeg


编写插件

Gradle 是一个框架,作为框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的,我们要引入插件,而达到获取插件配置的目的。

实现插件类

VersionPlugin.kt中实现插件

package com.example.plugin


import org.gradle.api.Plugin
import org.gradle.api.Project


class VersionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        println("VersionPlugin")
    }
}
配置依赖

Dependencies.kt中,把项目的依赖库拷贝在这里:

object Versions {
    const val composeUi = "1.3.1"
    const val composeVersion = "1.2.0"
    const val kotlin = "1.8.0"
    const val lifecycle = "2.5.1"
    const val activityCompose = "1.5.1"
    const val composeMaterial3 = "1.0.0-alpha11"
    const val junit = "4.13.2"
    const val androidxJunit = "1.1.3"
    const val espresso = "3.4.0"
}


object Libraries {
//    依赖库
    const val coreKtx = "androidx.core:core-ktx:${Versions.kotlin}"
    const val lifecycle = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle}"
    const val activityCompose = "androidx.activity:activity-compose:${Versions.activityCompose}"
    const val composeUi = "androidx.compose.ui:ui:${Versions.composeUi}"
    const val composePreview = "androidx.compose.ui:ui-tooling-preview:${Versions.composeVersion}"
    const val composeMaterial3 = "androidx.compose.material3:material3:${Versions.composeMaterial3}"
//    测试库
    const val junit = "junit:junit:${Versions.junit}"
    const val androidxJunit = "androidx.test.ext:junit:${Versions.androidxJunit}"
    const val espresso = "androidx.test.espresso:espresso-core:${Versions.espresso}"
    const val uiTestJunit4 = "androidx.compose.ui:ui-test-junit4:${Versions.composeVersion}"
    const val uiTooling = "androidx.compose.ui:ui-tooling:${Versions.composeVersion}"
    const val uiTestManifest = "androidx.compose.ui:ui-test-manifest:${Versions.composeVersion}"


}
注册插件

插件需要注册才能被别的 Module 引入,在插件 Module 的build.gradle.kts中,注册一个id

plugins {
    `kotlin-dsl`
}
gradlePlugin {
    plugins.register("versionPlugin") {
        id = "version-plugin"
        implementationClass = "com.example.plugin.VersionPlugin"
    }
}

使用

在用到的 Module 里添加插件,app 目录下的build.gradle

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    // 依赖插件
    id 'version-plugin'
}

这时候就可以引用插件 Module 里定义的依赖了:

implementation Libraries.coreKtx

扩展

依赖优化

上面一通操作,在使用的时候,并没有方便多少,只是达到了buildSrc的地步并没有体现复合构建的优势。为了不再一个一个的引入依赖,我们需要写个扩展优化。为了方便操作和提示,建议使用 Kotlin 的 DSL ,首先把build.gradle转为build.gradle.kts

转化前:

import com.example.plugin.Libraries


plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'version-plugin'
}


android {
    namespace 'com.example.composingbuilds'
    compileSdk 33


    defaultConfig {
        applicationId "com.example.composingbuilds"
        minSdk 24
        targetSdk 33
        versionCode 1
        versionName "1.0"


        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary true
        }
    }


    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion '1.1.1'
    }
    packagingOptions {
        resources {
            excludes += '/META-INF/{AL2.0,LGPL2.1}'
        }
    }
}


dependencies {
    implementation Libraries.coreKtx
//    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
    implementation 'androidx.activity:activity-compose:1.3.1'
    implementation "androidx.compose.ui:ui:$compose_version"
    implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
    implementation 'androidx.compose.material3:material3:1.0.0-alpha11'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
    debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
    debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
}

转化后:

import com.example.plugin.Libraries


plugins {
    id("com.android.application")
    id("kotlin-android")
    id("version-plugin")
}


android {
    namespace = "com.example.composingbuilds"
    compileSdk = 33


    defaultConfig {
        applicationId = "com.example.composingbuilds"
        minSdk = 23
        targetSdk = 33
        versionCode = 1
        versionName = "1.0"


        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary = true
        }
    }


    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.1.1"
    }
    packagingOptions {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }
}


dependencies {
    implementation(Libraries.coreKtx)
    implementation(Libraries.lifecycle)
    implementation(Libraries.activityCompose)
    implementation(Libraries.composeUi)
    implementation(Libraries.composePreview)
    implementation(Libraries.composeMaterial3)


    testImplementation(Libraries.junit)
    androidTestImplementation(Libraries.androidxJunit)
    androidTestImplementation(Libraries.espresso)
    androidTestImplementation(Libraries.uiTestJunit4)
    debugImplementation(Libraries.uiTooling)
    debugImplementation(Libraries.uiTestManifest)


}

dependencies里还是需要一个一个的依赖,有时候项目并不是一个 Module 而是多 Module 的状态,每个build.gradle都要写依赖,要简化这个繁琐的过程,就需要把依赖分类集中处理。

在插件 Module 里新建Extension.kt,可以把依赖库分为kotlin、android、compose、test四部分。扩展DependencyHandlerScope

fun DependencyHandlerScope.kotlinProject() {
    "implementation"(Libraries.coreKtx)
}




fun DependencyHandlerScope.androidProject() {
    "implementation"(Libraries.lifecycle)
}


fun DependencyHandlerScope.composeProject() {
    "implementation"(Libraries.activityCompose)
    "implementation"(Libraries.composeUi)
    "implementation"(Libraries.composePreview)
    "implementation"(Libraries.composeMaterial3)
}


fun DependencyHandlerScope.androidTest() {
    "testImplementation"(Libraries.junit)
    "androidTestImplementation"(Libraries.androidxJunit)
    "androidTestImplementation"(Libraries.espresso)
    "androidTestImplementation"(Libraries.uiTestJunit4)
    "debugImplementation"(Libraries.uiTooling)
    "debugImplementation"(Libraries.uiTestManifest)
}

然后修改项目依赖,调用上面的扩展,短短几行就可实现:

dependencies {
    kotlinProject()
    androidProject()
    composeProject()
    androidTest()
    
//    implementation(Libraries.coreKtx)
//    implementation(Libraries.lifecycle)
//    implementation(Libraries.activityCompose)
//    implementation(Libraries.composeUi)
//    implementation(Libraries.composePreview)
//    implementation(Libraries.composeMaterial3)
//
//    testImplementation(Libraries.junit)
//    androidTestImplementation(Libraries.androidxJunit)
//    androidTestImplementation(Libraries.espresso)
//    androidTestImplementation(Libraries.uiTestJunit4)
//    debugImplementation(Libraries.uiTooling)
//    debugImplementation(Libraries.uiTestManifest)


}

插件依赖

上面只优化了dependencies这个闭包,build.gradle.kts依旧很多东西,既然写了一个插件,我们就用插件实现整个配置。

可以看到app的build.gradle.kts一共有三个闭包:pluginandroiddependencies,对应插件其实也是现实这三个配置,回到最开始的VersionPlugin中:

class VersionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target){
            //配置plugin
            //配置android
            //配置dependencies
        }
    }
}
1. 首先实现配置plugin

这个闭包就是引入插件,把原 Module 用到的插件搬过来即可,这里要去掉原先加入的自身插件:

//配置plugin
plugins.run {
    apply("com.android.application")
    apply("kotlin-android")
}
2. 然后实现配置android

配置这里有用到两个插件依赖,先把依赖添加到插件 Module 的build.gradle.kts里:

plugins {
    `kotlin-dsl`
}
dependencies {
    implementation("com.android.tools.build:gradle:7.3.1")
    implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0")
}

然后配置android,把 Module 的build.gradle.kts里的android部分搬过来,唯一需要注意的是,插件里没有kotlinOptions,需要自己写一个扩展:

//配置android
extensions.configure<ApplicationExtension> {
    applicationId = "com.asi.composingbuild"
    compileSdk=33
    defaultConfig {
        applicationId="com.asi.composingbuild"
        minSdk = 23
        targetSdk=33
        versionCode=1
        versionName="1.0"
        testInstrumentationRunner= "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary =true
        }
    }
    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }


    kotlinOptions{
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.1.1"
    }
    packagingOptions {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }
}

kotlinOptions扩展:

fun CommonExtension<*, *, *, *>.kotlinOptions(block: KotlinJvmOptions.() -> Unit) {
    (this as ExtensionAware).extensions.configure("kotlinOptions", block)
}
3. 实现配置dependencies
//配置dependencies
dependencies {
    kotlinProject()
    androidProject()
    composeProject()
    androidTest()
}
4. 依赖插件

把 app Module 的build.gradle.kts里的内容都删了,只依赖下刚完成的插件:

```
  plugins {
      id("version-plugin")
  }
  ```

是不是很清爽的感觉?

多个插件

如果是多 Module 的项目,每个 Module 的依赖会不一样,所以可以在 version-plugin 中编写多个plugin,然后注册id,在不同的 Module 里使用,修改某个依赖,只构建这个 Module 的依赖,达到隔离构建的目的。

复合构建

上面单一 Module 中单独的插件,依赖的库并没有达到隔离构建的目的,如果我们只是更改了composeUi版本,整个依赖都要重新编译。要实现隔离,需要更精细化的拆分,比如把compose部分单独出来。

新建一个ComposePlugin.kt,把原来插件中的关于compose的配置拷贝过来:

class ComposePlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            //配置compose
            extensions.configure<ApplicationExtension> {
                buildFeatures {
                    compose = true
                }
                composeOptions {
                    kotlinCompilerExtensionVersion = Versions.kotlinCompilerExtensionVersion
                }
            }
            dependencies {
                composeProject()
            }
        }
    }
}

插件写完需要注册:

gradlePlugin {
    plugins.register("versionPlugin") {
        id = "version-plugin"
        implementationClass = "com.example.plugin.VersionPlugin"
    }
    plugins.register("ComposePlugin") {
        id = "compose-plugin"
        implementationClass = "com.example.plugin.ComposePlugin"
    }
}

这里可以优化下写法:

gradlePlugin {
    plugins{
        register("versionPlugin") {
            id = "version-plugin"
            implementationClass = "com.example.plugin.VersionPlugin"
        }
        register("ComposePlugin") {
            id = "compose-plugin"
            implementationClass = "com.example.plugin.ComposePlugin"
        }
    }
}

在 app 模块里引入:

plugins {
    id("version-plugin")
    id("compose-plugin")
}

这样如果修改compose版本,并不会构建别的依赖。

国际惯例上源码https://github.com/yuexunshi/ComposingBuilds

关注我获取更多知识或者投稿

ba58f36acd0a090bbf4d77e5d791722e.jpeg

9c1b1943c57c9b7a5bb623b118d02d39.jpeg

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spanned.SPAN_COMPOSING 是一个常量,用于表示一个跨度对象(Span)正在处于组合文本输入的状态。它是用于处理复杂文本输入和编辑的 Android 文本样式类 Spanned 的一个标记常量。 当用户正在进行组合文本输入(如中文输入法的拼音输入)时,输入的文本可能会被拆分成多个部分,并且在用户最终确定输入之前,这些部分可能会不断变化。SPAN_COMPOSING 被用于标记这些处于组合输入状态的文本部分,以便在显示和处理这些文本时进行特殊处理。 在使用 Spanned 接口及其实现类(如 SpannableString 或 SpannableStringBuilder)处理文本时,可以使用 Spanned.SPAN_COMPOSING 来标记处于组合输入状态的文本部分。例如,你可以使用 setSpan() 方法将 Spanned.SPAN_COMPOSING 应用于文本的特定范围,以便在显示或处理文本时对这些部分进行特殊处理。 以下是一个使用 Spanned.SPAN_COMPOSING 的示例: ```java SpannableString spannableString = new SpannableString("Hello World"); spannableString.setSpan(new ForegroundColorSpan(Color.RED), 0, 5, Spanned.SPAN_COMPOSING); textView.setText(spannableString); ``` 在上面的示例中,"Hello" 这个单词将被标记为处于组合输入状态,并且会应用红色前景色。这样,当用户在输入过程中修改或删除这部分文本时,可以对其进行特殊处理,以提供更好的用户体验。 希望这个解释对你有所帮助!如果还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值