2016年9月23日星期五

使用Gradle快速构建Android多渠道包

首先,多渠道的实现方式采用了美团多渠道打包的方式,参看http://tech.meituan.com/mt-apk-packaging.html

这种打包方式优点很明显:打包速度快的飞起,渠道越多优势越大;也有缺点,只适合多渠道包区别只有渠道号的不同,如果多个渠道需要自定义包名、应用名、资源、功能等的,还是需要使用多个productFlavor 的方式。
幸运的是公司的产品不需要这么麻烦,因此研究了下使用gradle实现美团多渠道打包的方法

基本流程


现将流程分解下:
  1. 调用assemble任务生成apk
  2. 将apk解压
  3. 在apk的META-INFO目录下创建空文件:channel_{channelName}
  4. 解压后的目录重新打包为apk

第一步,没什么好说的,标准as项目生成的gradle脚本就够用了

第二步,apk解压到本地,解压文件可以使用如下自定义task:
copy {
    from zipTree(srcFile)
    into file(upzipDir)
}
第三步,根据渠道号,在解压后的apk目录下的META-INFO文件夹下创建空文件

第四步,重新打包apk:
task("zip_" + variant.name + "_" + channelName, type: Zip)
    from upzipDir
    archiveName = targetArchiveName
    destinationDir targetout
}
基本流程如上,下面给出具体实现:

具体实现


首先自定义的构建输出目录在gradle.properties文件中配置:
# app/gradle.properties
targetout=targetout
然后渠道号采用读取json配置文件的方法,配置文件放在app项目根目录下,名称为,channels.json,示例如下:
{
  "channels": [
    {
      "channelName": "xiaomi"
    },
    {
      "channelName": "yingyongbao"
    },
    {
      "channelName": "taobao"
    }
  ]
}

下面是主要的脚本文件:
apply plugin: 'com.android.application'
android {
    .......

    buildType {
        debug {
            minifyEnabled false
        }
        release {
            minifyEnabled true
            zipAlignEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    // 自定义的构建结果输出目录,构建前删除目录并重建
    File target = new File(rootDir, project.targetout);
    target.deleteDir()
    target.mkdir()

    // 读取json配置文件,转换为Channels类,类定义在脚本末尾
    def channelsMap = new groovy.json.JsonSlurper().parse(file("channels.json"))
    Channels channels = new Channels(channelsMap)

    applicationVariants.all { variant ->
        variant.assemble << {

            variant.outputs.each { output ->

                // 默认构建apk文件输出的路径,解压目录放在同级目录下
                File oriFile = output.outputFile
                String oriPath = oriFile.getAbsolutePath();
                if (oriPath.endsWith(".apk")) {
                    oriPath = oriPath.substring(0, oriPath.length() - 4)
                }
                File upzipDir = new File(oriPath);

                // 解压apk文件
                copy {
                    from zipTree(oriFile)
                    into file(upzipDir)
                }

                // 挨个处理多渠道
                channels.channels.each { channel ->
                    // 读取渠道名称
                    def channelName = channel.channelName

                    // 组装渠道文件名称,并创建空文件
                    def channelFileName = "channel_" + channelName
                    def dir = new File(upzipDir, "META-INF")

                    File channelFile = new File(dir, channelFileName)
                    channelFile.createNewFile()

                    // 定义重新压缩的apk的文件名称
                    String targetArchiveName = applicationId + "-v" + versionName + "-" + channelName + "-" + variant.buildType.name + ".apk"

                    // 重新压缩渠道包
                    task("zip_" + variant.name + "_" + channelName, type: Zip) {
                        from oriPath
                        archiveName = targetArchiveName
                        destinationDir target
                    }.execute()

                    // 删除之前创建的渠道空文件,便于下一个渠道包的生成
                    channelFile.delete()
                }

            }
        }
    }
}

// 单个渠道属性定义
class Channel {
    String channelName;
}

// 渠道列表属性定义
class Channels {
    List<channel> channels;

    @Override
    String toString() {
        return "channels:" + channels
    }
}
以上为多渠道打包脚本的实现,下面就是渠道号的获取了

代码中获取渠道号


获取渠道号的代码片段:
try {
    final ZipFile z = new ZipFile(context.getApplicationInfo().sourceDir);
    Enumeration e = z.entries();
    while (e.hasMoreElements()) {
        final ZipEntry entry = e.nextElement();
        if (entry.getName().startsWith("META-INF/channel_")) {
            channelId = entry.getName().split("_")[1];
            Log.e("tag", "渠道号: " + channelId);
            break;
        }
    }
} catch (IOException e) {
    e.printStack();
}

以上为整个过程,大家有更好的实现方案、更优雅的代码实现,欢迎留言共享,共同学习

参考:


gradle解压:http://mrhaki.blogspot.com/2012/06/gradle-goodness-unpacking-archive.html

json解析:https://dzone.com/articles/groovy-unmarshalling-json-to-a-specific-object
http://groovy-lang.org/json.html



没有评论:

发表评论

Android logcat

Log等级 Android log 等级在 android/log.h 中定义如下: typedef   enum   android_LogPriority {    /** For internal use only. */ ANDROID_LOG_UNKNOWN =...