Python 脚本构建Android APK 自动加固、打渠道包并上传服务器

Python 脚本构建Android APK 自动加固、打渠道包并上传服务器

常规流程

  • 打出原始apk

  • 使用乐固工具加固并打出响应渠道包

  • 将生成的渠道包上传对应服务器,生成推广链接

    因为每一步都需要人工介入,尤其当渠道较多时,相当耗时,且有出错的可能,所以考虑以脚本替代人工执行步骤。

准备工作

  • qshell 用于上传七牛服务器的命令行工具
  • VasDolly多渠道打包工具,同时支持基于V1签名和V2签名进行多渠道打包
  • apksigner.jar 用于为apk进行签名,支持v1+v2签名,这里有个坑,一开始我选择使用的是java包内的jarsigner,后发现只支持v1签名。apksigner.jar位置在android sdk的libs中,可以考虑将其复制到当前工作目录下

脚本编写

初始化

# 初始化环境
def init():
    command = './qshell account'
    result = os.popen(command).read()
    if ('error' in result):
        command = './qshell account <Your AccessKey> <Your SecretKey> <Your Account Name>'
        os.popen(command).read()
    if not os.path.exists(ORIGIN_APK):
        raise RuntimeError('找不到原始 apk 文件')
    # 删除临时 apk 文件
    if os.path.exists(TEMP_APK):
        os.remove(TEMP_APK)
    # 删除上传失败日志文件
    if os.path.exists(QINIU_FAILED_LIST):
        os.remove(QINIU_FAILED_LIST)
    # 初始化清缓存文件
    fp = open(REFRESH_CONFIG,'w')
    fp.write(QINIU_URL+ORIGIN_APK) 
    fp.close() 
    os.makedirs(OUTPUT_PATH)

首先我们进行qshell的设置,设置当前用户的AccessKey, SecretKey和Name, Name是用户可以任意取的名字,表示当前在本地记录的账户的名称。

ORIGIN_APK是打包出来的原始apk

TEMP_APK、QINIU_FAILED_LIST、REFRESH_CONFIG 都是运行生成的结果,在下一次运行初期进行一次清理。

上传临时APK文件到七牛中

# 上传临时APK文件到七牛
def uploadApp():
    command = './qshell rput --overwrite %s %s %s ' % (
        QINIU_BUCKET, ORIGIN_APK, ORIGIN_APK)
    uploadResult = os.popen(command).read()
    if 'success' not in uploadResult:
        raise RuntimeError('上传临时APK文件到七牛失败:'+uploadResult)
    refresh()
# 刷新七牛缓存
def refresh():
    command = './qshell cdnrefresh -i %s' % (REFRESH_CONFIG)
    os.popen(command).read()

将临时文件上传七牛,这样我们能拿到改apk的链接,方便下一步的加固工作

QINIU_BUCKET:七牛空间名称,可以为公开空间或私有空间

使用乐固进行加固并签名

# 使用乐固加固
def legu():
    cred = credential.Credential(TENCENT_KEY, TENCENT_SECRET)
    httpProfile = HttpProfile()
    httpProfile.endpoint = "ms.tencentcloudapi.com"

    clientProfile = ClientProfile()
    clientProfile.httpProfile = httpProfile
    client = ms_client.MsClient(cred, "ap-shanghai", clientProfile)

    req = models.CreateShieldInstanceRequest()
    params = '''{
        "AppInfo":{
            "AppUrl":"%s",
            "AppMd5":"%s"
            },
        "ServiceInfo":{
            "ServiceEdition":"basic",
            "SubmitSource":"api"
            }
        }''' % (QINIU_URL+ORIGIN_APK, md5_file(ORIGIN_APK))
    req.from_json_string(params)
    #调用腾讯 API乐固接口加固
    resp = client.CreateShieldInstance(req)
    itemId = resp.ItemId
    if not itemId:
        raise RuntimeError('创建乐固实例失败:'+resp)

    req = models.DescribeShieldResultRequest()
    params = '{"ItemId":"%s"}' % (itemId)
    req.from_json_string(params)
    #下载加固结果
    for num in range(20):
        time.sleep(120)
        print("查看乐固加固结果,第%d次尝试中..."  % num)
        resp = client.DescribeShieldResult(req)
        if resp.TaskStatus != 2:
            break
    else:
        raise RuntimeError('加固超时')
    downloadURL = resp.ShieldInfo.AppUrl
    appMD5 = resp.ShieldInfo.ShieldMd5
    if downloadURL:
        urllib.urlretrieve(downloadURL, TEMP_APK)

    if md5_file(TEMP_APK) != appMD5:
        raise RuntimeError('下载加固结果失败')
    # 签名
    command = sign_command(TEMP_APK)
    os.popen(command).read()
    
# 计算文件的 md5值
def md5_file(name):
    m = md5()
    a_file = open(name, 'rb')  # 使用二进制格式读取文件内容
    m.update(a_file.read())
    a_file.close()
    return m.hexdigest()
    
#使用apksigner进行签名
def sign_command(file):
    return 'java -jar apksigner.jar sign --ks %s --ks-key-alias ALIAS --ks-pass pass:PASS --key-pass pass:PASS --out %s %s'% (SIGN_KEY, file,file)

使用乐固API接口进行加固,完成加固后下载至本地并进行md5的验证,确保包的一致性,而后在本地对其进行签名

TENCENT_KEY、TENCENT_SECRET 用于乐固的腾讯key&&secret

ALIAS别名 PASS密码 SIGN_KEY秘钥所在位置

打渠道包

# 打渠道包
def channel():
    command = 'java -jar VasDolly.jar put -c %s %s %s' % (
        CHANNEL_CONFIG, TEMP_APK, OUTPUT_PATH)
    result = os.popen(command).read()
    searchObj = re.search(r'total (\d+) channel apk', result, re.M | re.I)
    total = int(searchObj.group(1))

    channelCount = len(open(CHANNEL_CONFIG, 'rU').readlines())
    if total != channelCount:
        raise RuntimeError('生成渠道包数量异常:应生成%s,实际生成%s' % (channelCount, total))
    rename()

# 重命名
def rename():
    files = os.listdir(OUTPUT_PATH)
    for file in files:
        f = os.path.join(OUTPUT_PATH, file)
        #os.popen(sign_command(f))
        os.rename(f, f.replace('-'+TEMP_APK, '.apk'))

不同于以往先打渠道包而后进行签名的方式,VasDolly可以让我们在不破坏签名的情况下进行渠道打包。

这边也有个坑,因为原先采用的打渠道包方式是umeng,umeng会直接修改manifest的channel字段,所以我们在包内获取渠道名的方式都是直接读这个字段。VasDolly不会去修改这个字段,因而需要改变项目内所有获取渠道名的方式,这边可以参考VasDolly的说明进行修改。

这样我们的加固及打渠道都完成了,渠道包会输出在你指定的OUTPUT_PATH。

CHANNEL_CONFIG 包含所有渠道名称 换行分隔

上传到七牛
def deploy():
    uploadCmd = './qshell qupload2 --overwrite --rescan-local --src-dir=%s --bucket=%s --failure-list %s --check-hash --thread-count 10' % (
        QINIU_UPLOAD_PATH, QINIU_BUCKET, QINIU_FAILED_LIST)
    os.popen(uploadCmd).read()
    if os.path.getsize(QINIU_FAILED_LIST):
        raise RuntimeError('上传到七牛失败,失败文件列表见:' + QINIU_FAILED_LIST)
    #清缓存
    fp = open(REFRESH_CONFIG,'w')
    files = os.listdir(OUTPUT_PATH)
    fp.writelines([QINIU_URL+QINIU_UPLOAD_SUBPATH+file+'\n'  for file in files])
    fp.close() 
    refresh()
    
# 刷新七牛缓存
def refresh():
    command = './qshell cdnrefresh -i %s' % (REFRESH_CONFIG)
    os.popen(command).read()

这样就完成了整个加固、打渠道包、上传服务器的过程。

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

推荐阅读更多精彩内容