欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android国内海外apk多渠道打包了解一下?

程序员文章站 2022-04-01 10:37:17
本文主要讲解一个代码工程需要同时需要打包国内外apk包,国外一般只在google play发布,国内发布的平台会更多一些,包括华为、小米、应用宝、vivo等应用市场平台需求整理国内和海外版本差异一般比较大,有一部分代码都是不一样的,比如国外一般使用google推送,而国内一般使用个推等第三方推送平台。国内的版本,功能都是一致,一般只是渠道号不一致,以保证数据统计需求,而其它功能都是一致的,那么主要需要解决以下几个问题:国内版本支持一次打包多个渠道不一样的apk,它们之间的代码逻辑是一样的一次工....

本文主要讲解一个代码工程需要同时需要打包国内外apk包,国外一般只在google play发布,国内发布的平台会更多一些,包括华为、小米、应用宝、vivo等应用市场平台

需求整理

国内和海外版本差异一般比较大,有一部分代码都是不一样的,比如国外一般使用google推送,而国内一般使用个推等第三方推送平台。国内的版本,功能都是一致,一般只是渠道号不一致,以保证数据统计需求,而其它功能都是一致的,那么主要需要解决以下几个问题:

  • 国内版本支持一次打包多个渠道不一样的apk,它们之间的代码逻辑是一样的
  • 一次工程,支持打外国内、海外两类apk,它们之间的代码逻辑是不一样的
  • 国内和海个任一apk间需要区分渠道号
  • 实现一个自动化打包的脚本,批量生成apk版本、混淆规则的mapping文件

实现效果

打包输出结果

以下是打包的输出文件,可直接用于输出提测,文件结构如下:

├── cn
│   ├── app_2.7.0.0_2070000_test_release_20200916-1636-mapping.txt
│   └── apks
│       ├── app-v2.7.0.0-c360.apk
│       ├── app-v2.7.0.0-hw.apk
│       ├── app-v2.7.0.0-lggCn.apk
│       ├── app-v2.7.0.0-oppo.apk
│       ├── app-v2.7.0.0-vivo.apk
│       ├── app-v2.7.0.0-xm.apk
│       └── app-v2.7.0.0-yyb.apk
└── gp
    ├── app_2.7.0.1_2070001_test_online_20200916-1649-mapping.txt
    └── apks
        └── app_2.7.0.1_2070001_test_online_20200916-1649.apk

实现步骤

如何实现代码多渠道

实现代码多渠道依赖通过在build.gradle中配置productFlavors属性,以下定义了两个渠道,分别为cn和gp

android {
    ...
    productFlavors {
        //定义国内渠道
        cn {}
        //定义google play渠道
        gp {}
    }
}

定义在两个渠道后可以在cn和gp两个渠道中编写差异化的代码,效果如下:
Android国内海外apk多渠道打包了解一下?

定义在cn和gp对应的同包名类名下定义相关的类并实现

class PushManager {

    //不同的渠道实现不同的代码
    fun init() {
        ...
    }

在调试代码时,可设置当前的生效渠道并运行,打包后面会提到
Android国内海外apk多渠道打包了解一下?

海外apk打包

海外渠道定义为gp,且编译类型分为debug、release、online
Android国内海外apk多渠道打包了解一下?

那么在打海外线上包就可执行以下命令

gradlew assembleGpOnline

但是这样打包的输出文件其实在build目录了,我们就需要实现以下几个点:

  • 在apk命名加入版本号、日期等信息
  • 将apk文件拷贝到指定输出目录,类似“…/app_build_output”
  • 拷贝混淆规则文件
    具体的实现如下
applicationVariants.all { variant ->

        variant.outputs.all { output ->
            if (variant.buildType.name != "debug") {
                variant.assembleProvider.get().doLast {
                    def outputFile = output.outputFile
                    def targetDir = "$rootDir/../app_build_output/${project.android.defaultConfig.versionName}_${project.android.defaultConfig.versionCode}_${variant.buildType.name}/${productFlavors.name[0]}"

                    def targetFileNamePrefix

                    def date = new Date()

                    def formattedData = date.format('yyyyMMdd-HHmm')

                    def fileNamePrefix = "${project.name}-${variant.baseName}"
                    project.logger.error("fileNamePrefix: ${fileNamePrefix}")


                    if (outputFile != null && outputFile.name.endsWith('.apk')) {
                        project.logger.error("outputFile: ${outputFile}")

                        targetFileNamePrefix = "Notta_${project.android.defaultConfig.versionName}_${project.android.defaultConfig.versionCode}_${getGitBranch()}_${variant.buildType.name}_${formattedData}"

                        project.logger.error("outputFileName: ${targetFileNamePrefix}")
                        def flavorName=productFlavors.name[0]
                        if (flavorName=="gp"){
                            project.copy {
                                from "${output.outputFile}"
                                into "${targetDir}/apks"
                                rename("${fileNamePrefix}.apk", "${targetFileNamePrefix}.apk")
                                project.logger.error("Copy ${fileNamePrefix}.apk to ${targetDir}/${targetFileNamePrefix}.apk")
                            }
                        }else {
                            // skip cn channel
                        }

                        def detailDir="${flavorName}${variant.buildType.name.capitalize()}"
                        project.copy {
                            // copy mapping.txt
                            from "${project.buildDir}/outputs/mapping/${detailDir}/mapping.txt"
                            into "${targetDir}"
                            rename("mapping.txt", "${targetFileNamePrefix}-mapping.txt")

                            project.logger.error("Copy mapping.txt to ${targetDir}/${targetFileNamePrefix}_mapping.txt")
                        }

                        project.copy {
                            from "${project.buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
                            into "${targetDir}"
                            rename("R.txt", "${targetFileNamePrefix}-R.txt")

                            project.logger.error("Copy R.txt to ${targetDir}/${targetFileNamePrefix}_R.txt")
                        }
                    }
                }
            }
        }
    }

国内apk打包

国内版本的打包可以直接使用美团的walle
官网地址为:https://github.com/Meituan-Dianping/walle

如何打包

在工程根目录下配置

buildscript {
    ...
    dependencies {
        ...
        classpath "com.meituan.android.walle:plugin:1.1.7"
        
    }
}

然后在app目录下配置

apply plugin: 'walle'
walle {
    // 指定渠道包的输出路径
    apkOutputFolder = new File(project.rootProject.buildDir, "apks")
    // 定制渠道包的APK的文件名称
    apkFileNameFormat = '${packageName}-${buildType}-v${versionName}-${channel}.apk';
    // 渠道配置文件
    channelFile = new File("${project.getProjectDir()}/channel")
}

其中channel文件定义了相关的渠道
Android国内海外apk多渠道打包了解一下?

最后在包时,执行以下指令即可完成打包

gradlew assembleCnOnlineChannels

如何获取各个应用对应的渠道名?

而对应的渠道信息获取方式如下:

ChannelInfo channelInfo= WalleChannelReader.getChannelInfo(this.getApplicationContext());
if (channelInfo != null) {
   String channel = channelInfo.getChannel();
   Map<String, String> extraInfo = channelInfo.getExtraInfo();
}
// 或者也可以直接根据key获取
String value = WalleChannelReader.get(context, "buildtime");

插入额外信息

walle中的channelFile只支持渠道写入,如果想插入除渠道以外的其他信息,请在walle配置中使用configFile

walle {
    // 渠道&额外信息配置文件,与channelFile互斥
    configFile = new File("${project.getProjectDir()}/config.json")
}

统一打包脚本

以上我们已经定义了国内和海外版本打包的具体的细节,但是在发版本打包时使用都其实希望操作越简单越好,那么接下来就简单包装一下:

预期目标

只需要输入两个类似的指令打国内和海外版本:“gradlew cnOnline"和"gradlew gpOnline”

海外打包

海外打包指定仅是给assembleGpOnline指令取了个别名

task gpOnline(dependsOn:['assembleGpOnline']){

}

国内打包

国内版本打包需要调整一下,主要原因是因为walle打包的文件输出目录是在根目录下的"./build/apks"在重新编译时会被覆盖,因此我们需要将相关的文件拷到"…/app_build_output"目录下去,具体如下:

task cnOnline(dependsOn:['assembleCnOnlineChannels']){
    doLast {
        cnArtifact("online")
    }
}
def cnArtifact(buildType){
    println("cnBuildTask start")
    def targetDir = "$rootDir/../app_build_output/${project.android.defaultConfig.versionName}_${project.android.defaultConfig.versionCode}_${buildType}/cn"
    def apkDirs="${project.rootDir}/build/apks"
    println("target dir is ${targetDir}")
    println("apkDirs  is ${apkDirs}")
    project.copy {
        // copy mapping.txt
        from apkDirs
        into "${targetDir}/apks"
    }

}

总结

本篇文章的核心思路是:

  • 使用productFlavors配置代码式的多渠道,因此定义了cn和gp两个渠道来分别编译国内和海外的代码
  • 国内版本多应用市场的打包借力于walle进行批量输出

欢迎大家点赞加关注,相互交流学习。

本文地址:https://blog.csdn.net/wsx1048/article/details/108742967