Android 组件化实践——自动发布组件、根据编译环境自动依赖远程组件

概览

文章目录

前言

最近在内网为Android项目配置了maven私服仓,想要把组件化的使用更进一步,即各组件发布到远端私服仓库,项目内从私服仓库依赖组件。

远端依赖组件的好处,就是提高编译效率。随着组件越来越多,每次发版时需要同时编译20+个组件,非常耗时。而有些组件是没有改动的,没必要参与编译增加编译时间,这时候使用远程依赖,可以极大的提高编译效率。

用远程依赖组件来提高编译效率,实际上是把各组件的编译流程尽量提前,放在其他合适的时机去编译发布到私服仓,待发版时直接远程依赖编译好的输出文件即可。

本文的主要目的是根据gitlab-ci持续集成流程、项目内部发版流程,设计出一套合适的自动发布/依赖远程组件的流程, 其中涉及到持续集成相关的知识,可以参考之前发布的Android持续集成实践系列文章

系列文章

为Android组件化项目搭建Maven私服

Android 组件化实践------自动发布组件、根据编译环境自动依赖远程组件

正文开始

要做什么?

首先看标题就很清晰,我们要做两件事,自动发布组件到私服仓、自动从私服仓依赖远程组件。

先说结论:

  1. 发布组件和依赖远程组件,需要发布release和debug两个版本的组件。
    想象一下,假如现在有个base组件,放着所有的基础库封装,所有业务组件都依赖它。base中有这么个逻辑,根据BuildConfig#DEBUG字段的值来使用测试环境/正式环境的base_url。这时候如果只发布release的base组件,你在测试环境远程依赖base组件时,base_url就错误的使用了正式环境的url。
  2. 分别在合适的时机发布release或debug版本的组件
    首先明确一点,执行发布命令,会编译+发布,所以发布时的编译也是耗时的;另外一个APP版本的完整开发流程,可能是发布N多个debug版本+发布1~3个发布候选版+发布一个正式版,debug和release的发布频率是不一样的,debug更多,所以如果同时发布release和debug,必然造成其中一个版本的编译浪费。
自动发布组件到私服仓

项目里使用了gitlab持续集成流程,可以在持续集成流程再加入一个发布组件的stage,在合适的条件下分别触发debug/release组件发布

自动发布 debug 组件

debug发布的是最频繁的,通常的做法是发布快照-SNAPSHOT版本,但是这样处理又有弊端,之前的代码管理相当于是失效了。之前合作开发,其他同事提交代码后,我需要从远端仓库拉取后,新的代码才会在我本地生效,而直接使用快照版本,不用拉取最新代码,我本地编译时就会从远端获取最新代码的组件来引用,这样使开发环境管理起来很混乱。

基于以上,合适的时机为:当提交的代码中组件版本号更改,且当前分支在开发分支,触发自动发布debug组件

  1. gitlab-ci配置发布组件阶段

    gitlab-ci.yml:

    image: inovex/gitlab-ci-android
    
    #######################
    #gitlab-runner 配置文件#
    #######################
    
    variables:
      GRADLE_OPTS: "-Dorg.gradle.daemon=false"
    before_script:
      #  配置 jdk 17/11/8
      - export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
      ...
      #  获取权限
      ...
      - chmod +x ./publish-module
      
    stages:
      - publishModule #定义发布组件阶段,在所有阶段之前
      - build
      - reinforceAndChannel
      - deploy
      
    # gradle.properties文件变动时,编译前自动发布debug module
    publish_debug_module:
      stage: publishModule
      only:
        changes:
          - gradle.properties #组件版本号在此文件定义,此文件更改就触发发布组件stage
        refs:
          - /^develop(_[A-Za-z0-9]+)?$/ #只在开发分支
      script:
        - echo "gradle.properties文件变动,执行发布debug module逻辑"
        - export publishMode="debug"
        - ./publish-module
      tags:
        - android
    ...
    

    publish-module文件:

    dos 复制代码
    #!/usr/bin/env bash
    
    ############################
    #自动编译时自动发布debug module #
    ############################
    
    #任何命令失败时停止执行
    set -e
    
    # 修改 FORCE_USE_DEPENDENCY_MODE 为 local
    echo 'start exec publish-module.sh ...'
    
    echo "publishMode is ${publishMode} ..."
    publishExec='publishDebugPublicationToMavenRepository'
    if [ "$publishMode" = "release" ]; then
      publishExec=publishReleasePublicationToMavenRepository
    fi
    echo "publishExec is ${publishExec} ..."
    
    echo '修改 gradle.properties 中 FORCE_USE_DEPENDENCY_MODE 为 local ...'
    sed -i "s/FORCE_USE_DEPENDENCY_MODE=.*$/FORCE_USE_DEPENDENCY_MODE=local/;" gradle.properties
    
    echo '修改成功,发布 module ...'
    ./gradlew $publishExec
    
    echo '发布成功,修改 gradle.properties 中 FORCE_USE_DEPENDENCY_MODE 为 remote ...'
    sed -i "s/FORCE_USE_DEPENDENCY_MODE=.*$/FORCE_USE_DEPENDENCY_MODE=remote/;" gradle.properties
    echo '修改成功'
    
    echo 'end exec publish-module.sh ...'

    发布代码maven-publish.gradle:

    java 复制代码
    apply plugin: "maven-publish"
    apply from: "$rootDir/gradle/maven-utils.gradle"
    
    afterEvaluate {
        publishing {
            publications {
                ...
                debug(MavenPublication) {
                    if (isAndroidEnv(project)) {
                        from project.components.debug
                    } else {
                        from project.components.java
                    }
                    pom {
                        ...
                        url = NEXUS_DEBUG_URL
                        withXml {
                            ...
                        }
                    }
                }
            }
    
            repositories {
                maven {
                    allowInsecureProtocol true
                    //根据编译命令区分debug/release,上传组件到不同的仓库
                    if (isReleaseBuild()) {
                        url = NEXUS_URL
                    } else {
                        url = NEXUS_DEBUG_URL
                    }
                    ...
                }
            }
        }
    }

    gradle.properties:

    groovy 复制代码
    #配置是否全局强制开启依赖本地组件,优先级高于module内的配置.
    #会影响使用 projectCompat 引入的所有依赖
    #[remote]:使用远程依赖;[local]:使用本地依赖;[none]:不使用全局强制依赖模式
    FORCE_USE_DEPENDENCY_MODE=none
  2. 发布 debug 组件

    修改版本号提交代码,触发gitlab-ci发布组件stage

自动发布 release 组件

从发布候选版(rc)开始,发版都切换到融合(assemble)分支上了,融合分支只处理rc发版,这样保证发布候选版的稳定。所以发布 release的时机,就是创建merge_request从开发分支合并到融合分支时,如果提交的代码包含有版本号的变更,自动触发持续集成的发布组件流程,发布release组件。

  1. gitlab-ci配置发布组件阶段

    gitlab-ci.yml:

    image: inovex/gitlab-ci-android
    
    #######################
    #gitlab-runner 配置文件#
    #######################
    
    variables:
      GRADLE_OPTS: "-Dorg.gradle.daemon=false"
    before_script:
      #  配置 jdk 17/11/8
      - export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
      ...
      #  获取权限
      ...
      - chmod +x ./publish-module
      
    stages:
      - publishModule #定义发布组件阶段,在所有阶段之前
      - build
      - reinforceAndChannel
      - deploy
      
    # gradle.properties文件变动时,编译前自动发布release module
    publish_release_module:
      stage: publishModule
      only:
        changes:
          - gradle.properties
        refs:
          - merge_requests
        variables:
          - $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^assemble(_[A-Za-z0-9]+)?$/
      script:
        - echo "gradle.properties文件变动,执行发布release module逻辑"
        - export publishMode="release"
        - ./publish-module
      tags:
        - android
    ...
    

    publish-module文件:

    dos 复制代码
    #!/usr/bin/env bash
    
    ############################
    #自动编译时自动发布debug module #
    ############################
    
    #任何命令失败时停止执行
    set -e
    
    # 修改 FORCE_USE_DEPENDENCY_MODE 为 local
    echo 'start exec publish-module.sh ...'
    
    echo "publishMode is ${publishMode} ..."
    publishExec='publishDebugPublicationToMavenRepository'
    if [ "$publishMode" = "release" ]; then
      publishExec=publishReleasePublicationToMavenRepository
    fi
    echo "publishExec is ${publishExec} ..."
    
    echo '修改 gradle.properties 中 FORCE_USE_DEPENDENCY_MODE 为 local ...'
    sed -i "s/FORCE_USE_DEPENDENCY_MODE=.*$/FORCE_USE_DEPENDENCY_MODE=local/;" gradle.properties
    
    echo '修改成功,发布 module ...'
    ./gradlew $publishExec
    
    echo '发布成功,修改 gradle.properties 中 FORCE_USE_DEPENDENCY_MODE 为 remote ...'
    sed -i "s/FORCE_USE_DEPENDENCY_MODE=.*$/FORCE_USE_DEPENDENCY_MODE=remote/;" gradle.properties
    echo '修改成功'
    
    echo 'end exec publish-module.sh ...'

    发布代码maven-publish.gradle:

    java 复制代码
    apply plugin: "maven-publish"
    apply from: "$rootDir/gradle/maven-utils.gradle"
    
    afterEvaluate {
        publishing {
            publications {
                ...
                release(MavenPublication) {
                    if (isAndroidEnv(project)) {
                        from project.components.release
                    } else {
                        from project.components.java
                    }
                    pom {
                        ...
                        url = NEXUS_URL
                        withXml {
                            ...
                        }
                    }
                }
            }
    
            repositories {
                maven {
                    allowInsecureProtocol true
                    //根据编译命令区分debug/release,上传组件到不同的仓库
                    if (isReleaseBuild()) {
                        url = NEXUS_URL
                    } else {
                        url = NEXUS_DEBUG_URL
                    }
                    ...
                }
            }
        }
    }

    gradle.properties:

    #配置是否全局强制开启依赖本地组件,优先级高于module内的配置.
    #会影响使用 projectCompat 引入的所有依赖
    #[remote]:使用远程依赖;[local]:使用本地依赖;[none]:不使用全局强制依赖模式
    FORCE_USE_DEPENDENCY_MODE=none
    
  2. 发布 release 组件

    修改版本号提交代码,创建merge_request,触发gitlab-ci发布组件stage

自动依赖远程组件

发布成功后,打包时自动依赖远程组件,实现方式是:

  1. 在gradle中封装组件依赖方法projectCompat,根据全局配置参数FORCE_USE_DEPENDENCY_MODE来处理是依赖本地源码还是远程aar。
    app的build.gradle依赖组件:

    ...
    dependencies {
        ...
        addNewComponent projectCompat(':component:home')
        ...
    }
    

    /gradle/ext.gradle中封装projectCompat方法:

    ...
    //region 组件依赖工具方法,根据配置自动处理本地或远程依赖
    //根据是否为远程依赖设置依赖远程库还是本地库
    ext.projectCompat = { name ->
        def realName = name.substring(name.lastIndexOf(":") + 1)
        def realDependency
        if (isMaven(name)) {
            realDependency = "$NEXUS_PROJ_GROUP_ID:${realName}:${mavenVersion(name)}"
        } else {
            realDependency = project(name)
        }
        println("projectCompat>>name=${name}>>realName=${realName}>>realDependency=$realDependency")
        return realDependency
    }
    //判断当前组件是否为远程依赖
    ext.isMaven = { name ->
        if (FORCE_USE_DEPENDENCY_MODE == "local")
            return false
        else if (FORCE_USE_DEPENDENCY_MODE == "remote")
            return true
        Properties properties = new Properties()
        def file = new File("$rootDir${name.replace(":", "/")}/gradle.properties")
        if (file.exists()) {
            InputStream inputStream = file.newDataInputStream()
            properties.load(inputStream)
            def str = properties.getProperty('USE_REMOTE_DEPENDENCY')
            if (str == null) {
                return false
            } else {
                return Boolean.parseBoolean(str)
            }
        }
        return false
    }
    
    //读取各module中gradle.property文件中库版本
    ext.mavenVersion = { name ->
        println("mavenVersion::${name}")
        def str = NEXUS_PROJ_VERSION
        if (str == null) {
            throw Exception(file.path + "    NEXUS_PROJ_VERSION == null")
        } else {
            return str
        }
    
    }
    //endregion
    ...
    
  2. 由于发布组件时,debug/release是发布到不同的仓库了,所以在project根目录的build.gradle文件内,需要根据编译命令动态切换debug/release仓库地址。
    project根目录build.gradle:

    ...
    allprojects {
        apply from: "$rootDir/gradle/maven-utils.gradle"
        repositories {
            ...
            //本地私服仓-自有组件,根据编译命令动态切换debug/release仓库
            maven {
                allowInsecureProtocol true
                if (isReleaseBuild()) {
                    url NEXUS_URL
                } else {
                    url NEXUS_DEBUG_URL
                }
            }
    
            ...
        }
    }
    ...
    
  3. 在持续集成的打包流程中,执行编译命令前通过命令修改FORCE_USE_DEPENDENCY_MODEremote,指定发版编译时,使用远程组件依赖
    gitlab-ci.yml:

    ...
    # 利用标签打测试包(beta),格式:"x.x.x-beta.x", debug 包,面向测试人员,api 是测试环境
    build_tag_beta:
      stage: build
      only:
        - /^[\d]+\.[\d]+\.[\d]+-beta\.[\d]+$/
      script:
        - sed -i "s/FORCE_USE_DEPENDENCY_MODE=.*$/FORCE_USE_DEPENDENCY_MODE=remote/;" gradle.properties # 修改 FORCE_USE_DEPENDENCY_MODE 为 remote
        - ./gradlew assembleDebug
      artifacts:
        paths:
          - app/build/outputs/apk/debug/*.apk
      tags:
        - android
    ...
    
开发流程总结
  1. 开发阶段时,develop分支正常开发提交代码。
  2. 开发结束需要发布测试版时:
    1. 修改gradle.properties中的版本号字段NEXUS_PROJ_VERSION提交到仓库,触发自动发布debug组件的流程
    2. 打标签x.x.x-beta.x,触发自动发版流程,自动修改FORCE_USE_DEPENDENCY_MODE开启强制依赖远程组件,之后自动编译发版
  3. 测试结束需要发布发布候选版时:
    1. 创建develop合并到assemble分支的merge_request,触发自动发布release组件流程
    2. 合并merge_request,打标签x.x.x-rc.x触发自动发布发布候选版
  4. 发布候选版测试结束需要正式发版时
    1. 创建assemble合并到master分支的merge_request,等待编译完成后合并
    2. 在master分支打标签x.x.x触发自动发版流程
相关推荐
Eastsea.Chen24 分钟前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年8 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
Kkooe9 小时前
GitLab|GitLab报错:Restoring PostgreSQL database gitlabhq_production...
gitlab
建群新人小猿10 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神11 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛11 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法12 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter13 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快14 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl15 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5