gradle android 配置 build 變體

λ:

倉庫地址: https://github.com/lzyprime/android_demos

之前想復用data層, ui層分別用compose和傳統view分別實現。所以通過gradle moudle組織工程:

  • core: 通用部分, 包括data層,viewModel,共用資源文件等
  • view: view實現
  • compose: compose實現

但是實際體驗之后,發(fā)現還是有很多弊端:

  • Hilt依賴注入跨moudle的問題
  • Application, Manifest文件維護兩份(view, compose),但大部分邏輯相同
  • gralde 依賴聲明,module 存在相同依賴,管理繁瑣

初衷本來只是隔離ui層實現和部分資源文件,所以改為通過sourceSet實現:

val useCompose by project.extra(false) 
android {
    sourceSets {
        getByName("main") {
            if (useCompose) {
                kotlin.srcDir("src/ui/compose")
                res.srcDir("src/ui/compose/res")
            } else {
                res.srcDir("src/ui/view/res")
                kotlin.srcDir("src/ui/view")
            }
        }
    }
}

android 配置 build 變體

  • buildTypes
  • dependencies
  • productFlavors
  • sourceSets

實現 配置,源碼,資源文件 多版本控制

sourceSet 源碼集

sourceSet 是 gradle 本身就提供的接口,用來組織項目源碼。 gradle sourceSets

// build.gradle
plugins {
    id 'java'
}

sourceSets {
  main {
    java {
      exclude 'some/unwanted/package/**'
    }
  }
}

android gradle plugin 自己也有一個sourceSet, 目的很簡單,就是先塞一些默認行為:android sourceSet 默認源碼集。 kts版本的api相比groovy要少一部分,沒有exclude 等操作

  • src/main/ 此源代碼集包含所有 變體 共用的代碼和資源。
  • src/<buildType>/ 創(chuàng)建此源代碼集可加入特定 buildType 專用的代碼和資源。 比如常用的 debug,release。在 android.buildTypes 中配置
  • src/<productFlavor>/ 創(chuàng)建此源代碼集可加入特定產品變種專用的代碼和資源。在 android.productFlavors 配置
// build.gradle.kts
android {
    ...
    sourceSets { // NamedDomainObjectContainer<out AndroidSourceSet>
        getByName("main") { // AndroidSourceSet
            ...
        }
    }
}

NamedDomainObjectContainer:buildTypes,sourceSets都是此類型。kv容器。

@Incubating
interface AndroidSourceSet : Named {

    /** Returns the name of this source set. */
    override fun getName(): String

    /** The Java source for this source-set */
    val java: AndroidSourceDirectorySet
    /** The Java source for this source-set */
    fun java(action: AndroidSourceDirectorySet.() -> Unit)
    
    ... 
    ...

    fun setRoot(path: String): Any
}

@Incubating
interface AndroidSourceDirectorySet : Named {

    override fun getName(): String
    // 追加規(guī)則, set += srcDir
    fun srcDir(srcDir: Any): Any
    // 追加規(guī)則 set += srcDirs
    fun srcDirs(vararg srcDirs: Any): Any
    // 覆蓋規(guī)則 set = srcDirs
    fun setSrcDirs(srcDirs: Iterable<*>): Any
}

productFlavors 產品變種

如果用 productFlavors 分離ui不同版本:

  • 創(chuàng)建 src/view, src/compose 源碼集
  • uiType為維度, 添加 view, compose 變種
android {
    ...
    flavorDimensions += "uiType" // 變種維度
    productFlavors { // NamedDomainObjectContainer<out ProductFlavorT>
        create("view") { // ApplicationProductFlavor
            dimension = "uiType"
            applicationIdSuffix = ".view"
            versionNameSuffix = "-view"
        }
        create("compose") {
            dimension = "uiType"
            applicationIdSuffix = ".compose"
            versionNameSuffix = "-compose"
        }
    }
}

當查看productFlavors支持的可配置項時,會發(fā)現與android.defaultConfig, andoird.buildTypes中內容很像。defaultConfig實際上屬于productFlavors,提供所有變體的默認配置。buildType也可視作一個變種維度flavorDimensions, 并且默認有debugrelease兩個變體

interface ApplicationProductFlavor : ApplicationBaseFlavor, ProductFlavor

interface ApplicationBaseFlavor : BaseFlavor, ApplicationVariantDimension
interface ProductFlavor : Named, BaseFlavor, ExtensionAware, HasInitWith<BaseFlavor>
interface BaseFlavor : VariantDimension, HasInitWith<BaseFlavor>

// ==> 
interface ApplicationProductFlavor : BaseFlavor, VariantDimension, ApplicationVariantDimension

buildTypes

// app build.gradle.kts

android {
    defaultConfig { // ApplicationDefaultConfig
        ...
    }
    buildTypes { // NamedDomainObjectContainer<out BuildTypeT>
        getByName("release") { // ApplicationBuildType
            isMinifyEnabled = true
        }

        getByName("debug") {
            applicationIdSuffix = ".debug"
            isDebuggable = true
        }

        create("staging") {
            initWith(getByName("debug"))
            applicationIdSuffix = ".debugStaging"
        }
    }
}

ApplicationDefaultConfig, ApplicationBuildType 追蹤完繼承關系會發(fā)現和 ApplicationProductFlavor 的基本一致

interface ApplicationDefaultConfig : ApplicationBaseFlavor, DefaultConfig {}

interface ApplicationBaseFlavor : BaseFlavor, ApplicationVariantDimension
interface BaseFlavor : VariantDimension, HasInitWith<BaseFlavor>
interface ApplicationVariantDimension : VariantDimension

interface DefaultConfig : BaseFlavor {}

// 所以跟到最后,有用的基類:
interface ApplicationDefaultConfig:BaseFlavor, VariantDimension, ApplicationVariantDimension,
interface ApplicationBuildType : BuildType, ApplicationVariantDimension

interface BuildType : Named, VariantDimension, ExtensionAware, HasInitWith<BuildType>

// 所以跟到最后,有用的config基類:
interface ApplicationBuildType : VariantDimension, ExtensionAware, ApplicationVariantDimension

變種構建

所謂的變種最后都會被提交成一個task, 并且維度會自動進行組合。由于創(chuàng)建了新的維度uiType, 所以會得到四種構建方式:debugView, debugCompose, releaseView, releaseCompose。

debugCompose為例,sourceSet會默認加入src/debugCompose, src/debug, src/compose, src/main

可以在androidComponents.beforeVariants 中配置過濾規(guī)則:

variantBuilder 攜帶了 android 塊中配置的內容

android {
    ...
}
androidComponents {
    beforeVariants { variantBuilder -> // ApplicationVariantBuilder
        if (variantBuilder.productFlavors.containsAll(listOf("uiType" to "view"))) {
            variantBuilder.enabled = false
        }
    }
}

dependencies 依賴管理

源碼管理解決后,就是依賴部分:

  • 公共依賴的庫
  • 只有view需要的庫
  • 只有compose需要的庫

自然可以通過 useCompose 變量判斷:

val useCompose: String by rootProject.extra
dependencies {
    if (useCompose) {
        ...
    } else {
        ...
    }
    ...
}

但當我們創(chuàng)建buildType和productFlavors的同時, android gradle plugin 會通過 gradle dependency configurations 提供對應的依賴配置: <buildType>Implementation, <productFlavor>Implementation。 但是對于buildType + productFlavors 的組合型構建變體沒有自動創(chuàng)建,需要自己聲明。

val composeDebugImplementation by configurations
// configurations {
//    composeDebugImplementation {}
// }
dependencies {
    releaseImplementation("....")
    composeImplementation("....")
    composeDebugImplementation("....")
}

這套機制在多版本分流可能用的到, 當然可以通過一堆變量去控制。 productFlavors的弊端就是多維度是要組合的,派生出一堆變體版本。并且所有代碼在sync時都會檢查,也有可能管理不當,使用其他變體的源碼。

而用變量控制,相當于沒有創(chuàng)建變體,gradle之維護當前條件范圍內的源碼。而我的項目也不用同時出view和compose包。所以直接變量控制,屏蔽掉不關注的另一半

~λ:

已經好長一段時間沒有做真正意義上的android開發(fā)了。去年年底結束了上一個項目后,空了幾周休息。之后接到了SDK項目,就是一堆安全相關的組件(安全鍵盤,掃描,網關等),遠古級項目。sdk的包和gradle plugin估計稍微新點的項目直接引不了。也沒有新的開發(fā)內容,只是作為客服的存在,解答使用者的問題。

同時接到的還有一個 上報觸達sdk, 和上面的sdk一樣的狀況。 不過這個還有新的功能還要開發(fā)。但是看已有的代碼,風險問題一堆:文件讀寫不考慮多線程,樹型結構壓平直接遞歸,連個尾遞歸優(yōu)化都沒有,是真的敢啊。

class Node {
    val leafs = listOf<Node>(....)
    val val : Config
    val isLeaf = false
}

fun rd(Node node): List<Config> {
    if(!node->isLeaf) {
        ... rd() ...
    }
}

當然既然是考古,那一定是 java 項目,各種context和handle胡亂持有,IDE各種警告會有內存泄漏風險。 也不會加@Nullable @Nonull標簽 .... 真是一點都不想干

之后項目不需要這么多人,客服工作可以兼職。所以我又解放了一陣。 公司一直沒什么正經android項目,與其做這種玩意兒不如回后端。所以又做了一個月后端,go開發(fā),k8s運維。騰訊自己做容器服務框架, 我們負責把普通產品接進去,做適配器工作。熟悉完項目之后我把產品版本升級和部署等做自動化。 就是根據現有配置生成框架下配置,這東西為什么每次都要人工從頭做,不就一個腳本的事? 還有部署過程,總之一通優(yōu)化。結果這個項目也不做了,適配基本完成,總部收回自己維護就行。

接著就是QQ-TIM, 涉及保密問題不變多說,只能說同上邊兩個sdk沒什么不同,考古,龐大,問題風險多。沒有什么有價值的需求。至于架構設計也并沒有多么出色。代碼插樁加了一條if(xxx != null)的判斷就降了不少crash率,自己品。 不過應該也快收回了。

鑒于這種情況,我只能自己提需求練手, 自己寫個IM。不然水平越放越完蛋。

  • 后端kotlin ktor, rust 兩個版本,功能一致,就想練練rust。
  • 客戶端
    • 與后端通信部分使用ktor client做成跨端sdk (之后也嘗試rust跨端)
    • android app: view 和 compose 實現ui
    • flutter
  • github repo:

寫的很慢,不斷的調整方案。目前勉強寫完登錄,還一個socket都沒有。

這是最后的掙扎吧:每天堅持刷leetcode每日一題,邊寫IM邊學新內容。

真不想越放越費。感覺越來越難改變現狀。

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容