Android自定义gradle插件保存打包信息

背景

公司的Android项目是在google play发布,由于项目组的历史原因,测试负责jenkins的打包操作(无论是测试阶段还是打release包),因为每次发版的负责人可能不一样,并且项目采用的是指定flavors去打不同国家(渠道)包,这就很难避免因为人为因素导致的打包出错(debug还是release,菲律宾还是越南版本等等)。所以这个插件的出发点是做到无论是谁打的包,是谁负责推到gp发布,都可以直接拿到发布apk的打包信息,通过打包信息去判断是否为最终发布版本。结合项目主要考虑插入以下信息来保证唯一性:

  • 国家版本,即我们熟知的flavors渠道号
  • buildType,即debug还是relase等等
  • 包名,在项目中不是必选项,因为上传gp如果包名不一致会上传失败
  • 版本号
  • git提交信息,包括打包分支和最近的提交commit id,格式 branch :commitId
  • 自定义额外信息,做扩展用

以上信息都可以自定义是否写入,默认写入。

流程

梳理了需要插入的打包信息后,接下来就是需要确定怎么插入。一开始想用最简单的方式,直接在zip的comment字段插入信息,直接读取就好了,但是发现项目已经用了v2签名,这种方法不适用;沿着这种思路可以考虑用美团的walle,但考虑到上传gp的稳定性和没有生成文件的直观方便,最终还是选择在META-INF插入自定义文件,同时拿到生成包后写一个python脚本去读。

grale插件

Talk is cheap,show me the code.这里介绍的是本地插件,没有push到maven。

1.定义常量

class InsertMetaInfConstants {

//定义gradle扩展名 
static final String EXTENSION_NAME = "insertmetainf"

//指定生成文件路径
static final String GENERATE_INSERT_PATH = 'generated/insertmetainf/'

}

2.gralde扩展参数配置

class InsertMetaInfExtension {

/**
 * 设置meta-inf文件名字
 */
String metaInfName = null

/**
 * 是否写入flavor,默认写入
 */
boolean insertFlavor= true

/**
 * 是否写入build type,默认写入
 */
boolean insertBuildType= true

/**
 * 是否写入applicaitionId,默认写入
 */
boolean insertApplicaitionId = true

/**
 * 是否写入versionName,默认写入
 */
boolean insertVersionName = true

/**
 * 是否写入versionCode,默认写入
 */
boolean insertVersionCode = true

/**
 * 是否写入该分支最后git的commit信息,默认写入
 */
boolean insertGitCommit = true

/**
 * 额外信息
 */
String[] insertExtraInfo = null

}

3.自定义gralde plugin

import com.android.build.gradle.*
import org.gradle.api.Plugin
import org.gradle.api.Project

class InsertMetaInfPlugin implements Plugin<Project> {

@Override
void apply(Project project) {
    project.extensions.create(InsertMetaInfConstants.EXTENSION_NAME, InsertMetaInfExtension)

    InsertMetaInfTask insertMetaInfTask = project.task("insertMetaInfTask", type: InsertMetaInfTask)

    def resDir = new File(project.buildDir, InsertMetaInfConstants.GENERATE_INSERT_PATH)
    def destDir = new File(resDir, 'META-INF/')

    boolean isLibrary = project.plugins.hasPlugin(LibraryPlugin)
    if (isLibrary) {
        project.extensions.getByType(LibraryExtension).sourceSets({
            main.resources.srcDirs += resDir
        })
    } else if (project.plugins.hasPlugin(TestPlugin)) {
        project.extensions.getByType(TestExtension).sourceSets({
            main.resources.srcDirs += resDir
        })
    } else if (project.plugins.hasPlugin(AppPlugin)) {
        project.extensions.getByType(AppExtension).sourceSets({
            main.resources.srcDirs += resDir
        })
    }
    insertMetaInfTask.setDestDir(destDir)

    project.afterEvaluate {
        project.tasks.findAll { task ->
            task.name.startsWith('generate') && task.name.endsWith('Resources')
        }.each { t ->
            t.dependsOn insertMetaInfTask
        }
    }
}
}

4.自定义gralde task

import com.android.build.gradle.internal.tasks.DefaultAndroidTask
import org.gradle.api.invocation.Gradle
import org.gradle.api.tasks.TaskAction

import java.util.regex.Matcher
import java.util.regex.Pattern

class InsertMetaInfTask extends DefaultAndroidTask {

def insertApplicaitionId = null

def insertVersionName = null

def insertVersionCode = null

def insertFlavor = null

def insertBuildType = null

InsertMetaInfTask() {
    setVariantName("InsertMetaInfTask")
}

private File destDir

void setDestDir(File destDir) {
    this.destDir = destDir
}

@TaskAction
def doExecute() {
    println("+-----------------------------------------------------------------------------+")
    println("|                      Insert Meta Inf Plugin START                            |")
    println("+-----------------------------------------------------------------------------+")
    InsertMetaInfExtension insertMetaInfExtension = project[InsertMetaInfConstants.EXTENSION_NAME]

    destDir.mkdirs()

    String metaInfName = insertMetaInfExtension.metaInfName
    if (null == metaInfName || metaInfName.length() <= 0) {
        throw new RuntimeException("metaInfName can not be empty!\n")
    }
    def vfile = new File(destDir, metaInfName)
    StringBuilder sb = new StringBuilder()
    //初始化默认的打包信息
    initCurrentPackageInfo()
    insertTask(sb, insertMetaInfExtension)

    def metaInfStr = sb.toString()
    vfile.text = metaInfStr

    println(
            "\nMETA-INF properties generated in ${destDir.getAbsolutePath()}${File.separator}$metaInfName: \n\n$metaInfStr")

    println("+-----------------------------------------------------------------------------+")
    println("|                       Insert Meta Inf Plugin END                             |")
    println("+-----------------------------------------------------------------------------+")
}

/**
 * 开始写入操作
 */
def insertTask(StringBuilder sb, InsertMetaInfExtension extension) {
    //写入基本信息
    writeBaseInfo(sb, extension)
    //写入额外信息,有的话
    writeExtraInfo(sb, extension)
}

def writeExtraInfo(StringBuilder sb, InsertMetaInfExtension extension) {
    String[] extraInfo = extension.insertExtraInfo;
    if(extraInfo != null && extraInfo.length > 0) {
        for(String info : extraInfo) {
            sb.append(info).append('\n')
        }
    }
}

def writeBaseInfo(StringBuilder sb, InsertMetaInfExtension insertMetaInfExtension) {
    getJenkinsBuildCode()
    if (insertMetaInfExtension.insertFlavor) {
        sb.append("flavor=").append(insertFlavor).append('\n')
    }
    if(insertMetaInfExtension.insertBuildType) {
        sb.append("buildType=").append(insertBuildType).append('\n')
    }
    if (insertMetaInfExtension.insertApplicaitionId) {
        sb.append("packageName=").append(insertApplicaitionId).append('\n')
    }
    if (insertMetaInfExtension.insertVersionName) {
        sb.append("versionName=").append(insertVersionName).append('\n')
    }
    if (insertMetaInfExtension.insertVersionCode) {
        sb.append("versionCode=").append(insertVersionCode).append('\n')
    }
    if (insertMetaInfExtension.insertGitCommit) {
        sb.append("gitCommit=").append(getGitBranch() + " : " + getLastCommitId()).append('\n')
    }
}

/**
 * 获取打包分支
 * @return
 */
def getGitBranch() {
    return 'git symbolic-ref --short -q HEAD'.execute().text.trim()
}

/**
 * 获取最近的git commit id
 * @return
 */
def getLastCommitId() {
    return 'git rev-parse --short HEAD'.execute().text.trim()
}

def initCurrentPackageInfo() {
    def currFlavor = getCurrentFlavor()
    def test = project.extensions.findByName("android")
    test.productFlavors.all { flavor ->
        if (flavor.name.equalsIgnoreCase(currFlavor)) {
            insertApplicaitionId = flavor.getApplicationId()
            insertVersionCode = flavor.getVersionCode()
            insertVersionName = flavor.getVersionName()
        }
    }
}

def getCurrentFlavor() {
    Gradle gradle = project.getGradle()
    String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
    Pattern pattern
    if (tskReqStr.contains("assemble")) {
        pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
    } else {
        pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
    }
    Matcher matcher = pattern.matcher(tskReqStr)
    if (matcher.find()) {
        insertFlavor = matcher.group(1).toLowerCase()
        insertBuildType = matcher.group(2).toLowerCase()
        return insertFlavor
    } else {
        println "NO MATCH FOUND"
        return ""
    }
}

/**
 * 获取Jenkins Build 号
 * @return
 */
def getJenkinsBuildCode() {
    ext.env = System.getenv()
    ext.buildNumber = env.BUILD_NUMBER?.toInteger()
    def test = "$buildNumber"
    println("test buildNumber: " + test)
    return
}
}

5.使用插件

//导入插件路径
apply plugin: 'com.***.insertmetainf'

insertmetainf {
    metaInfName 'packageInfo.properties'
    insertVersionCode false
    insertExtraInfo "test1=gg", "test2=pp"
}

6.生成文件信息例子

flavor=ph
buildType=debug
packageName=com.***
versionName=1.1.5
gitCommit=master : d113b9ab
test1=gg
test2=pp

pyhton读取脚本

读取就比较简单了,直接用zip去获取apk里面的META-INF/packageInfo.properties文件:

#coding:utf-8

'''
Auth:bottleTan
Date:2019-08-12

This is a tool for read apk meta-inf/packageInfo.properties file
for more details, please contact 564961680@qq.com
'''

import sys
import os
import zipfile

target_file = 'META-INF/packageInfo.properties'

def read(apk_path):

z = zipfile.ZipFile(apk_path, 'r')
try:
    print("========================================================")
    print(z.read(target_file))
    print("========================================================")
except Exception:
    raise RuntimeError(target_file + ' is not exists or other exception.')

if __name__ == '__main__':
    command = sys.argv
    if len(command) < 2:
        raise ValueError("command line params must have 2 parameters at least.")

apkPath = command[1]
if not os.path.exists(apkPath):
    raise IOError(apkPath + " is not exists,please check.")

print(apkPath)
read(apkPath)

至此,整个流程还是比较简单的,代码也是相对清晰。源码基本如上,就不放到github上了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容