美文网首页
gradle android 配置 build 变体

gradle android 配置 build 变体

作者: lzyprime | 来源:发表于2022-08-19 23:00 被阅读0次

    λ:

    仓库地址: https://github.com/lzyprime/android_demos

    之前想复用data层, ui层分别用compose和传统view分别实现。所以通过gradle moudle组织工程:

    • core: 通用部分, 包括data层,viewModel,共用资源文件等
    • view: view实现
    • compose: compose实现

    但是实际体验之后,发现还是有很多弊端:

    • 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>/ 创建此源代码集可加入特定 buildType 专用的代码和资源。 比如常用的 debugrelease。在 android.buildTypes 中配置
    • src/<productFlavor>/ 创建此源代码集可加入特定产品变种专用的代码和资源。在 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
        // 追加规则, set += srcDir
        fun srcDir(srcDir: Any): Any
        // 追加规则 set += srcDirs
        fun srcDirs(vararg srcDirs: Any): Any
        // 覆盖规则 set = srcDirs
        fun setSrcDirs(srcDirs: Iterable<*>): Any
    }
    

    productFlavors 产品变种

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

    • 创建 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支持的可配置项时,会发现与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"
            }
        }
    }
    

    ApplicationDefaultConfigApplicationBuildType 追踪完继承关系会发现和 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, 并且维度会自动进行组合。由于创建了新的维度uiType, 所以会得到四种构建方式:debugView, debugCompose, releaseView, releaseCompose

    debugCompose为例,sourceSet会默认加入src/debugCompose, src/debug, src/compose, src/main

    可以在androidComponents.beforeVariants 中配置过滤规则:

    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 {
            ...
        }
        ...
    }
    

    但当我们创建buildType和productFlavors的同时, android gradle plugin 会通过 gradle dependency configurations 提供对应的依赖配置: <buildType>Implementation<productFlavor>Implementation。 但是对于buildType + productFlavors 的组合型构建变体没有自动创建,需要自己声明。

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

    这套机制在多版本分流可能用的到, 当然可以通过一堆变量去控制。 productFlavors的弊端就是多维度是要组合的,派生出一堆变体版本。并且所有代码在sync时都会检查,也有可能管理不当,使用其他变体的源码。

    而用变量控制,相当于没有创建变体,gradle之维护当前条件范围内的源码。而我的项目也不用同时出view和compose包。所以直接变量控制,屏蔽掉不关注的另一半

    ~λ:

    已经好长一段时间没有做真正意义上的android开发了。去年年底结束了上一个项目后,空了几周休息。之后接到了SDK项目,就是一堆安全相关的组件(安全键盘,扫描,网关等),远古级项目。sdk的包和gradle plugin估计稍微新点的项目直接引不了。也没有新的开发内容,只是作为客服的存在,解答使用者的问题。

    同时接到的还有一个 上报触达sdk, 和上面的sdk一样的状况。 不过这个还有新的功能还要开发。但是看已有的代码,风险问题一堆:文件读写不考虑多线程,树型结构压平直接递归,连个尾递归优化都没有,是真的敢啊。

    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开发,k8s运维。腾讯自己做容器服务框架, 我们负责把普通产品接进去,做适配器工作。熟悉完项目之后我把产品版本升级和部署等做自动化。 就是根据现有配置生成框架下配置,这东西为什么每次都要人工从头做,不就一个脚本的事? 还有部署过程,总之一通优化。结果这个项目也不做了,适配基本完成,总部收回自己维护就行。

    接着就是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边学新内容。

    真不想越放越费。感觉越来越难改变现状。

    相关文章

      网友评论

          本文标题:gradle android 配置 build 变体

          本文链接:https://www.haomeiwen.com/subject/ycgmgrtx.html