按需配置打包思路

Posted by Ann on November 19, 2021

最近做的项目需要以产品线为划分按需打包,记录下思路

背景

   在差异化打包之前,SDK会向所有接入媒体提供相同的二进制包,在其中可能包含接入媒体App用不到的业务代码;同时在实际针对媒体接入过程中,在媒体侧的要求与限制下,包大小、广告交互等小部分的差异不影响整体的标准化与规范化。在实际中如何解决媒体差异性是本技术方案需要探讨和解决的问题。 ### 方案调研与对比 - 明确目标
- 在多媒体场景下尽可能复用全量代码是战略目标。实际情况考虑中要考虑代码、资源、依赖(组件叠加版本号)。 - 初步方案
build variants详细学习,明确哪些能控制,哪些不能控制 - 方案详细对比
- build variants
    - build variants是as和gradle提供的用于区分不同打包类型(比如像free版、pro版)等的一套代码、资源、签名管理方案,适用范围相对较窄,对于少量变体来讲足够管理。
- 多library发布
    - 对于feed这种一级组件来说,可以通过多library发布来支持按需接入,但feed组件内部多媒体定制化逻辑依然无法得到解决。同时该方案面临后期library爆炸的最大问题,对于媒体接入是个严重负担。比如悬浮球library、插屏广告library。
- target
    - target方案是团队自研用于管理不同媒体分包的技术方案,支持代码、资源、签名(虽然SDK不涉及)、依赖等的管理,适用范围广,支持更多媒体。 - 方案对比 |方案维度|代码选择|资源选择|依赖选择|支持双端|代码可读性|易扩展| |---|:----|:-----:|:-----:|:-----:|:-----:|:-----:| |build variants|✅|✅|❌|❌|❌|❌| |多Lib|✅|✅|✅|✅|❌|❌| |target|✅|✅|✅|✅|✅|✅|

Demo

  • Step 1

gradle文件下增加产品线配置

buildscript {
    ext {
        targetApp = "ayapp"
    }
}
  • Step 2

在对应的产品线文件夹下写下动态配置,以ayapp.target.gradle为例

buildscript {
    ext {
        // 网络库
        NETWORK_TYPE = 'external'
        // eventbus
        BUILD_WITH_EVENTBUS = 0
        // native/sailor/swan/mixed-sailor/mixed-swan
        WEBVIEW_PROVIDER = 'sailor'
        // default/bdvideoplayer/mixed
        VIDEOPLAYER_PROVIDER = 'bdvideoplayer'
        // none/default
        ADCONFIG_PLAT_PROVIDER = 'none'
        // builtin/bddownload/mixed
        DOWNLOAD_PROVIDER = 'bddownload'
        // 图片库
        IMAGEVIEW_PROVIDER = 'fresco'
        // default/elastic
        EXECUTOR_PROVIDER = 'elastic'
        // 激励视频Lp
        BUILD_WITH_REWARD_LP = 1
        // 通用激励视频样式
        BUILD_WITH_COMMON_REWARD_AD = 0
        // RUNTIME 版本有冲突
        LIB_NADCORE_ARCH_RUNTIME_VERSION = '1.0.9'
    }
}
  • Step 3

使用方根据关键字判断是否需要打包指定仓库,如webview仓库gradle:

通过{$}读取配置值。

android {
    sourceSets {
        main.java.srcDirs 'src/main/java'
        main.java.srcDirs 'src/main/webview/common'
        switch ("${WEBVIEW_PROVIDER}") {
            case 'swan':
                // swan和sailor都使用LightFactory
            case 'sailor':
                main.java.srcDirs 'src/main/webview/light'
                break
            case 'mixed-swan':
            case 'mixed-sailor':
                main.java.srcDirs 'src/main/webview/mixed'
                main.java.srcDirs 'src/main/webview/light'
                main.java.srcDirs 'src/main/webview/native'
                break
            case 'native':
                main.java.srcDirs 'src/main/webview/native'
                break
            default:
                throw new IllegalStateException("Unsupported WEBVIEW_PROVIDER:${property('WEBVIEW_PROVIDER')}")
                break
        }
    }
}

那JAVA层面如何进行配置呢? 需要在编译时将配置参数打进BuildConfig内。 如下所示:

android.libraryVariants.all { variant ->
    // webview实现编译配置
    def PACKAGE = "com.baidu.nadcore.webview."
    switch ("${WEBVIEW_PROVIDER}") {
        case 'swan':
            // swan和sailor都使用LightFactory
        case 'sailor':
            variant.buildConfigField "${PACKAGE}BrowserContainer.Factory", "BROWSERCONTAINER_FACTORY", "new ${PACKAGE}LightFactory()"
            break
        case 'mixed-swan':
        case 'mixed-sailor':
            variant.buildConfigField "${PACKAGE}BrowserContainer.Factory", "BROWSERCONTAINER_FACTORY", "new ${PACKAGE}MixedFactory()"
            break
        case 'native':
            variant.buildConfigField "${PACKAGE}BrowserContainer.Factory", "BROWSERCONTAINER_FACTORY", "new ${PACKAGE}NativeFactory()"
            break
        default:
            throw new IllegalStateException("Unsupported WEBVIEW_PROVIDER:${property('WEBVIEW_PROVIDER')}")
            break
    }
}

至此,通过gardle配置就可以实现分包打包了。如何根据不同产品线来打包呢?大致思路是:分产品线打出不一样的aar(后缀不同),在不同宿主依赖不同aar。
注:这里并不是版本号不一致。如:
好看产品线依赖:nad-sdk-haokan:1.0.0, 后续功能升级,依赖:nad-sdk-haokan:2.0.0
手百产品线依赖:nad-sdk-baidu:1.0.0, 后续功能升级,依赖:nad-sdk-baidu:2.0.0

  • Step 4

自动化打包脚本, 需要实现一些自动打包脚本,如提代码触发CI流程时,自动触发脚本进行哥产品线target打包,保证修复bug在多产品线上都生效(一处升级版本号,多target一起升级)。

举个例子:

# 当前脚本工作目录
CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)"

function fail() {
    echo "$*"
    exit 0
}

# 读取定义环境变量
if [[ -z "${TARGET_APP}" ]]; then
    fail_with_non_zero "未设置TARGET_APP环境变量,立即退出!"
fi

cat <<EOF >target.local_properties.gradle
buildscript {
    ext {
        targetApp = "${TARGET_APP}"
    }
}
EOF


# 先编译
./gradlew tasks

SDK_RELATIVE_DIR= ***

# 替换宿主target名称
gradle_module_file="${CURRENT_DIR}"/....gradle
${sed_bin} -i "s@\"__TARGET_APP__\"@\"${TARGET_APP}\"@" "${gradle_module_file}"
git update-index --assume-unchanged ....gradle

if [[ "${AGILE_COMPILE_BRANCH}" =~ release ]]; then
    # 发布relase aar
    TASK_NAME=publishReleasePublicationToReleaseRepository
    VERSION_PROPERTY="${VERSION}"
else
    # 发布debug aar
    TASK_NAME=publishDebugPublicationToDebugRepository
    VERSION_PROPERTY="${VERSION}-dev.${safe_branch_for_version}.${AGILE_PIPELINE_BUILD_NUMBER}"
fi
echo "" >> "${VERSION_FILE}"
echo "publishVersion=${VERSION_PROPERTY}" >>"${VERSION_FILE}"

build_success=1
# 执行指定打包task
./gradlew "${TASK_NAME}"
# shellcheck disable=SC2181
if [[ $? -ne 0 ]]; then
    echo "${TARGET_APP}发布失败,请RD检查" >>"${TEMP_LOG_FILE}"
    build_success=0
else
    msg="👍 产品线 ${TARGET_APP} 发布成功,版本号: ${VERSION_PROPERTY}"
    fi
    set -e
    (cd "${SDK_RELATIVE_DIR}"; git diff)

if [ ! ${build_success} -eq 1 ]; then
    # 编译失败,设置退出码
    exit ${build_success}
fi