多渠道打包配置和打包脚本修改

2,374 阅读5分钟

之前的文章 《创造 | 一个强大的 Android 自动化打包脚本》 介绍了 Android 自动打包脚本的配置。其实之前的脚本里还有些多渠道打包的配置实现方式并不好,比如使用 32 位还是 64 位 NDK 的问题。最近我对这个配置的打包做了更新。此外,因为 Google Play 检测出我在应用里面使用了友盟的 SDK,违反了谷歌的开发者政策,所以我决定将海外版本的应用的崩溃信息统计切换到谷歌的 Firebase,因此也需要做多渠道的配置。

QQ截图20221119112054.png

1、针对不同 NDK 的区分

首先,针对使用不同 NDK 的配置,我将 Gradle 配置文件修改位通过外部传入参数的形式进行设置,具体的脚本如下,

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    defaultConfig {
        if (project.hasProperty("build_ndk_type") && build_ndk_type == "ndk_32") {
            println(">>>>>>>> NDK option: using 32 bit version")
            ndk {abiFilters 'armeabi-v7a', 'x86'}
        } else if (project.hasProperty("build_ndk_type") && build_ndk_type == "ndk_32_64") {
            println(">>>>>>>> NDK option: using 32 and 64 bit version")
            ndk {abiFilters 'armeabi-v7a', 'x86', 'arm64-v8a', 'x86_64'}
        } else {
            // default is 64 bit version
            print(">>>>>>>> NDK option: using 64 bit version")
            ndk {abiFilters 'arm64-v8a', 'x86_64'}
        }
    }
}

这样,就可以通过打包命令的参数动态指定使用哪种形式的 NDK,

./gradlew resguardNationalDebug -Pbuild_ndk_type=ndk_32

2、针对海内和海外不同依赖的调整

这方面做了两个地方的调整。一个是因为对 Debug 和 Release 版本或者不同的 Flavor,Gradle 会生成不同的依赖命令,于是针对不同的渠道可以使用如下的命令进行依赖,

// apm
nationalImplementation "com.umeng.umsdk:common:$umengCommonVersion"
nationalImplementation "com.umeng.umsdk:asms:$umengAsmsVersion"
nationalImplementation "com.umeng.umsdk:apm:$umengApmVersion"
internationalImplementation 'com.google.firebase:firebase-analytics'
internationalImplementation platform("com.google.firebase:firebase-bom:$firebaseBomVersion")
internationalImplementation 'com.google.firebase:firebase-crashlytics'
// debugtools
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
debugImplementation 'com.github.Shouheng88:uetool-core:1.0'
debugImplementation 'com.github.Shouheng88:uetool-base:1.0'
releaseImplementation 'com.github.Shouheng88:uetool-no-op:1.0'
debugImplementation "com.facebook.stetho:stetho:$stethoVersion"
debugImplementation "com.iqiyi.xcrash:xcrash-android-lib:$xCrashVersion"

这里针对了 national 和 international 两个 flavor 分别使用了 nationalImplementation 和 internationalImplementation 两个依赖命令。此外,针对一些只在 Debug 环境中使用的依赖,这里使用了 debugImplementation 声明为只在 Debug 包里使用。

另一个调整是,因为比如如果我们只在 Debug 环境中或者个别渠道中使用某些依赖的话,那么当打 Release 包或者其他渠道的时候就可能出现依赖找不到的情况。这种情况的一种处理措施是像 leakcanary 一样,声明一个 no-op 的依赖,只包含必要的类文件而不包含具体的实现。此外,也可以通过下面的方式解决。

首先在项目中添加一个 module 或者使用已有的 module,然后已 CompileOnly 的形式引用上述依赖,

// apm
compileOnly "com.umeng.umsdk:common:$umengCommonVersion"
compileOnly "com.umeng.umsdk:asms:$umengAsmsVersion"
compileOnly "com.umeng.umsdk:apm:$umengApmVersion"
compileOnly 'com.google.firebase:firebase-analytics'
compileOnly platform("com.google.firebase:firebase-bom:$firebaseBomVersion")
compileOnly 'com.google.firebase:firebase-crashlytics'
// debugtool
compileOnly "com.facebook.stetho:stetho:$stethoVersion"
compileOnly "com.iqiyi.xcrash:xcrash-android-lib:$xCrashVersion"

这样是否使用某个依赖就取决于主 module. 然后,对需要引用到的类做一层包装,主 module 不直接调用依赖中的类,而是调用我们包装过的类。

object UmengConfig {

    /** Config umeng library. */
    fun config(application: Application, isDev: Boolean) {
        if (!AppEnvironment.DEPENDENCY_UMENG_ANALYTICS) {
            return
        }
        if (!isDev) {
            UMConfigure.setLogEnabled(isDev)
            UMConfigure.init(application, UMConfigure.DEVICE_TYPE_PHONE, "")
            MobclickAgent.setPageCollectionMode(MobclickAgent.PageMode.AUTO)
        }
    }
}

这样,主 module 只需要在 application 里面调用 UmengConfig 的 config 方法即可。这里我们可以通过是否为 Debug 包来决定是否调用 Umeng 的一些方法,所以,这种方式可以保证打包没问题,只要 Release 版本调用不到 Umeng SDK 的类也不会出现类找不到的异常。此外,也可以通过如下方式

public class AppEnvironment {

    public static final boolean DEPENDENCY_UMENG_ANALYTICS;
    public static final boolean DEPENDENCY_STETHO;
    public static final boolean DEPENDENCY_X_CRASH;

    static {
        DEPENDENCY_UMENG_ANALYTICS  = findClassByClassName("com.umeng.analytics.MobclickAgent");
        DEPENDENCY_STETHO           = findClassByClassName("com.facebook.stetho.Stetho");
        DEPENDENCY_X_CRASH          = findClassByClassName("xcrash.XCrash");
    }

    private static boolean findClassByClassName(String className) {
        boolean hasDependency;
        try {
            Class.forName(className);
            hasDependency = true;
        } catch (ClassNotFoundException e) {
            hasDependency = false;
        }
        return hasDependency;
    }
}

即通过能否找到某个类来判断当前环境中是否引用了指定的依赖,如果没有指定的依赖,直接跳过某些类的调用即可。

用上面的方式即可以解决 Android 中的各种多渠道打包问题。

3、通过外部参数指定打包版本

这个比较简单,和配置 NDK 的形式类似,只需要通过判断指定的属性是否存在即可,

if (project.hasProperty("version_code")) {
    println(">>>>>>>> Using version code: " + version_code)
    versionCode = version_code.toInteger()
} else {
    versionCode = rootProject.ext.versionCode
}
if (project.hasProperty("version_name")) {
    println(">>>>>>>> Using version name: " + version_name)
    versionName = version_name
} else {
    versionName = rootProject.ext.versionName
}

这样配置之后打包的传参指令为,

./gradlew assembleNationalDebug -Pbuild_ndk_type=ndk_32 -Pversion_code=121 -Pversion_name=hah

这样打包的时候就无需修改 gradle 脚本,直接通过传参的形式打包即可,做到了真正的自动化。

4、打包脚本 autopackage 的一些更新

经过上述配置之后,我对 autopackage 打包脚本也相应地做了一些调整。

1、调用脚本的时候也支持外部传入参数,比如

python run.py -s config/config_product.yml -v 324 -n 3.8.1.2

用来指定打包的配置文件、版本号以及版本名称。其次对打包脚本的 NDK 和 Flavor 配置做了调整,本次使用枚举来声明,含义更加准确,

def assemble(bit: BitConfiguration, flavor: FlavorConfiguration) -> ApkInfo:
    '''Assemble APK with bit and flavor and copy APK and mapping files to destination.''' 
    # ./gradlew assembleNationalDebug -Pbuild_ndk_type=ndk_32 -Pversion_code=322 -Pversion_name=3.8.0
    assemble_command = "cd %s && gradlew clean %s -Pbuild_ndk_type=%s" \
        % (config.gradlew_location, flavor.get_gradlew_command(), bit.get_gradlew_bit_param_value())
    if len(build_config.version_code) != 0:
        assemble_command = assemble_command + " -Pversion_code=" + build_config.version_code
    if len(build_config.version_name) != 0:
        assemble_command = assemble_command + " -Pversion_name=" + build_config.version_name
    logi("Final gradlew command is [%s]" % assemble_command)
    os.system(assemble_command)
    info = _find_apk_under_given_directory(bit, flavor)
    _copy_apk_to_directory(info)
    _copy_mapping_file_to_directory(info, flavor)
    return info

2、对 YAML 文件解析做了简化,调用方式将更加便捷,

class GlobalConfig:
    def parse(self):
        self._configurations = read_yaml(build_config.target_script)
        self.publish_telegram_token = self._read_key('publish.telegram.token')

    def _read_key(self, key: str):
        '''Read key from configurations.'''
        parts = key.split('.')
        value = self._configurations
        for part in parts:
            value = value[part.strip()]
        return value

3、生成 Git log 使用了标准的 Git 指令

首先,获取当前最新的 Git tag 使用了 Git 自带的指令,

git describe --abbrev=0 --tags

该指令可以以简单的形式输出最新的 tag 的名称。

此外,拿到了上述 tag 之后我们就可以自动获取提交到上一次提交之间的所有提交记录的信息。获取提交记录的指令也使用了 Git 自带的指令,

git log %s..HEAD --oneline

上述方式可以以更见简洁的代码实现自动生成当前版本 Git 变更日志的功能。

4、对 diff 输出的结果的展示进行了美化

之前发送邮件的时候使用的是纯文本,因为邮件系统的文字并不是等宽的,所以,导致了展示的时候格式化非常难看。本次使用了等宽的字体并且以 html 的形式发送邮件,相对来说输出的结果可视化程度更好了一些,

QQ截图20221119121158.png

以上就是脚本的更新,仓库地址是 github.com/Shouheng88/… 有兴趣自己参考。