Android QA专用,Python实现不一样的多渠道打包工具详情
相对于美团打包方案,我为什么要写这个工具?
除了gradle的多渠道打包,目前最流行的应该是美团使用
python直接添加渠道文件的打包方式了,速度真是杠杠的!但是,这里有一个问题:需要一个已签名无渠道号的
apk,那么问题来了,这个
apk哪里来的?懂行的朋友该说了,
gradle随便打个
release包不完事了嘛。是的,我也是这么想的。但是领导说
qa打包的时候需要改一部分配置文件代码(例如app版本、后台环境、版本bulabulabula),这样会带来潜在的问题。能不能直接通过命令行的形式打包,即
qa不需要改动任何代码,只需要设置不同的参数就好了。小脑瓜一转,有了。
gradle可以。吭哧吭哧半天,做好了。然后
qa说:敲命令行多麻烦,还可能出错。能不能直接写个界面,我点点按钮就可以了。so,就有了这个
python写的工具。我们暂时叫它
qa打包工具。
写在前面
写在前面
为什么要有这个
qa打包工具,这个工具存在的唯一目的就是根据
qa的选择,执行命令行打包,实际还是
gradle打包。如果和您预期不同或者您已经一眼看穿这小把戏。左拐出门,走好了您馁。如果您对
gradle配置参数打包或者对
python如何实现感兴趣,请继续~
效果图
效果图
first blood 多渠道配置
first blood 多渠道配置
第一滴血总让人满怀期待,完后只剩空虚寂寞冷。千篇一律的东西。这里以百度和豌豆荚为例,直接贴代码。
android { productflavors { wandoujia {} baidu {} productflavors.all { flavor -> flavor.manifestplaceholders = [umeng_channel_value: name] } } // 重命名生成的apk文件 applicationvariants.all { variant -> variant.outputs.each { output -> def outputfile = output.outputfile if (outputfile != null && outputfile.name.endswith('.apk') && variant.productflavors.size() > 0) { file outputdirectory = new file(outputfile.parent); def filename = "(" + variant.productflavors[0].name + ")test_${defaultconfig.versionname}_${releasetime()}.apk" output.outputfile = new file(outputdirectory, filename) } } } } def releasetime() { return new date().format("yyyymmddhhmmss", timezone.gettimezone("utc")) }
gradle设置参数后的打包命令
gradle设置参数后的打包命令
打所有渠道包
gradlew assemblerelease -pserver_type=1 -pis_debug=false -pminifyenabled=false
打指定渠道包(以百度为例)
gradlew assemblebaidurelease -pserver_type=1 -pis_debug=false -pminifyenabled=false
gradlew asseblexxxx是默认的打包方式,
-p后面是我们自己配置的各种参数。
gradle参数配置
gradle参数配置
首先想一个问题,
gradle命令中的参数怎么就莫名其妙变成了我们程序可访问的参数。一个是
gradle一个是
android,真正实现了两不沾。也很简单,用文件连接。
gradle在
build时,会在
app/build/generated/source/buildconfig/渠道/release/包名/下生成
buildconfig.java文件,就是这个文件实现了两者的通信。
gradle设置及获取参数(划重点)
gradle设置及获取参数(划重点)
代码是最好的语言表述者。
android { defaultconfig { applicationid "com.yikousamo.test" versioncode 1 versionname "1.0.0" minsdkversion 14 targetsdkversion 21 // 服务器类型 buildconfigfield 'int', 'server_type', '1' // 是否开启调试,默认开启 buildconfigfield 'boolean', 'is_debug', 'true' // 是否开启混淆,默认不混淆 buildconfigfield 'boolean', 'minifyenabled', 'false' } buildtypes { if (project.hasproperty('server_type') && project.hasproperty('is_debug') && project.hasproperty('minifyenabled')){ release { buildconfigfield 'int', 'server_type', server_type buildconfigfield 'boolean', 'is_debug', is_debug buildconfigfield 'boolean', 'minifyenabled', minifyenabled minifyenabled boolean.parseboolean(minifyenabled) zipalignenabled false shrinkresources false signingconfig signingconfigs.gliv proguardfiles getdefaultproguardfile('proguard-android.txt'), 'proguard-rules.pro' } } debug { minifyenabled false zipalignenabled false shrinkresources false // 包名增加后缀,不同apk可以在相同设备上安装 applicationidsuffix ".debug" signingconfig signingconfigs.gliv proguardfiles getdefaultproguardfile('proguard-android.txt'), 'proguard-rules.pro' } }
这里配置了三个可以参数,分别是
server_type,
is_debug,
minifyenabled,
defaultconfig里是对应的默认值。可以依据自己实际需求增加更多的参数。道理一样,不再赘述。
判断有没有指定参数的方法为
project.hasproperty('xxx'),下面就是设置到对应的参数中。这里有个小技巧,
debug设置
applicationidsuffix相当于改包名,这样在同一部手机上可以同时存在
debug和
release包。
设置完参数之后,在
build时,在
buildconfig中会生成对应的代码。
package com.yikousamo.test; public final class buildconfig { public static final boolean debug = false; public static final string application_id = "com.yikousamo.test"; public static final string build_type = "release"; public static final string flavor = "baidu"; public static final int version_code = 1; public static final string version_name = "1.0.0"; // fields from build type: release public static final int db_version = 1; public static final boolean is_debug = true; public static final boolean minifyenabled = false; public static final int server_type = 1; // fields from default config. }
注意下这里的包名是
app的包名。这也就意味着我们可以在代码中引用这个类。例如,在
baseapplication中设置引用
public static boolean isdebug = buildconfig.is_debug。其余依据业务需要,同理。到这里就已经完全完成了多渠道打包各种参数的配置。接下来是
python实现命令行打包。
python打包工具实现思路
python打包工具实现思路
前文说过,
python(3.5.0版本)在这里唯一的作用是用界面替代
qa输入命令行。
python执行命令行的方式有三种:
os.system("cmd") subprocess.popen commands.getstatusoutput
作为
python新手,三种方式的优劣我就不妄加评价了。凑合着用,反正在我眼里
都是垃圾。这里采用第二种产生子进程的方式执行cmd命令。界面实现采用的
tkinter。代码很简单,也没有多少行。直接放大了。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # 一口仨馍 import subprocess from tkinter import * from tkinter import messagebox from tkinter.ttk import combobox from tkinter.filedialog import askdirectory import os root = tk() root.title("android渠道包") root.geometry('500x340') # 是x 不是* rootpath = stringvar() # 项目根目录 frm = frame(root) #第一个字母直接大写,省去uppercase channels = ['baidu', 'wandoujia'] un_special_channel = '所有渠道包' def get_frame(): frame = frame(frm, pady=3) frame.pack() return frame def get_entry(frm): entry = entry(frm, width=12) entry.pack() return entry def get_combobox(frm, value): combobox = combobox(frm, width=9) combobox["state"] = "readonly" # 只读 combobox['values'] = value # 设置下拉列表的值 combobox.current(0) # 设置下拉列表默认显示的值,0为 numberchosen['values'] 的下标值 combobox.pack() return combobox def get_label(frm, text): label(frm, text=text, font=(17), width=14,anchor ='w', justify='left').pack(side=left) def select_path(): path_ = askdirectory() rootpath.set(path_) # 选择根目录 frm_choose_root_dir = get_frame() rootpathentry = entry(frm_choose_root_dir, textvariable=rootpath, width=18) rootpathentry.pack(side=left) button(frm_choose_root_dir, text="项目根目录", width=12, command=select_path).pack() frm_choose_root_dir.pack(side=top) # servertype frm_server_type = get_frame() get_label(frm_server_type, 'servertype:') servertypecombox = get_combobox(frm_server_type, (0, 1, 2, 3)) # versioncode frm_version_code = get_frame() get_label(frm_version_code, 'versioncode:') versioncodeentry = get_entry(frm_version_code) # versionname frm_version_name = get_frame() get_label(frm_version_name, 'versionname:') versionnameentry = get_entry(frm_version_name) # isdebug frm_is_debug = get_frame() get_label(frm_is_debug, 'isdebug:') isdebugcombobox = get_combobox(frm_is_debug, (true, false)) # dbversion frm_db_version = get_frame() get_label(frm_db_version, 'dbversion:') dbversionentry = get_entry(frm_db_version) # 混淆 frm_minifyenabled = get_frame() get_label(frm_minifyenabled, '混淆:') minifyenabledcombobox = get_combobox(frm_minifyenabled, (true, false)) # 指定渠道 frm_special_release = get_frame() get_label(frm_special_release, '渠道:') channels.insert(0, un_special_channel) specifyreleasecombobox = get_combobox(frm_special_release, tuple(channels)) def click_confirm(): if(rootpathentry.get().strip() == "" or versioncodeentry.get().strip() == "" or versionnameentry.get().strip() == "" or dbversionentry.get().strip() == ""): messagebox.askokcancel('提示', '干哈~不填完咋么打包~') return do_gradle() def do_gradle(): # 切换到项目根目录 os.chdir(rootpathentry.get()) # 获取当前工作目录 print(os.getcwd()) if specifyreleasecombobox.get() == un_special_channel: do_all_release() else: do_specify_release(specifyreleasecombobox.get()) # 打指定渠道包 def do_specify_release(channel): cmd = 'gradlew assemble'+channel+'release' \ ' -pserver_type=' + servertypecombox.get() + \ ' -pversion_code=' + versioncodeentry.get() + \ ' -pversion_name=' + versionnameentry.get() + \ ' -pdb_version=' + dbversionentry.get() + \ ' -pis_debug=' + isdebugcombobox.get().lower() + \ ' -pminifyenabled=' + minifyenabledcombobox.get().lower() subprocess.popen(cmd, shell=true, stdout=subprocess.pipe) # 打所有的渠道包 def do_all_release(): cmd = 'gradlew assemblerelease' \ ' -pserver_type=' + servertypecombox.get() + \ ' -pversion_code=' + versioncodeentry.get() + \ ' -pversion_name=' + versionnameentry.get() + \ ' -pdb_version=' + dbversionentry.get() + \ ' -pis_debug=' + isdebugcombobox.get().lower() + \ ' -pminifyenabled=' + minifyenabledcombobox.get().lower() subprocess.popen(cmd, shell=true, stdout=subprocess.pipe) button(root, text='确定', width=12, command=lambda: click_confirm()).pack(side=bottom) frm.pack() root.mainloop()
多渠道验证
多渠道验证
假设现在已经打了一个豌豆荚的包。那么怎么验证是否真的改变了
anroidmanifest.xml中
umeng_channel对应的值呢?也许你需要
apktool。
apktool可以反编译得到程序的源代码、图片、xml配置、语言资源等文件。这里我们只关心
anroidmanifest.xml。
反编译步骤:
下载apktool 将需要反编译的apk文件放到该目录下,打开命令行界面 ,定位到apktool文件夹 输入命令:java -jar apktool_2.2.1.jar decode test.apk 之后发现在文件夹下多了个test文件夹,查看anroidmanifest.xml即可