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触发自动发版流程
相关推荐
恋猫de小郭3 小时前
Android Studio 正式版 10 周年回顾,承载 Androider 的峥嵘十年
android·ide·android studio
aaaweiaaaaaa6 小时前
php的使用及 phpstorm环境部署
android·web安全·网络安全·php·storm
工程师老罗8 小时前
Android记事本App设计开发项目实战教程2025最新版Android Studio
android
画船听雨眠aa10 小时前
gitlab云服务器配置
服务器·git·elasticsearch·gitlab
pengyu12 小时前
系统化掌握 Dart 编程之异常处理(二):从防御到艺术的进阶之路
android·flutter·dart
消失的旧时光-194312 小时前
android Camera 的进化
android
基哥的奋斗历程13 小时前
Openfga 授权模型搭建
android·adb
Pakho love1 天前
Linux:文件与fd(被打开的文件)
android·linux·c语言·c++
勿忘初心911 天前
Android车机DIY开发之软件篇(九) NXP AutomotiveOS编译
android·arm开发·经验分享·嵌入式硬件·mcu
lingllllove1 天前
PHP中配置 variables_order详解
android·开发语言·php