您的位置:首页 > 编程语言 > Python开发

Android QA专用,Python实现不一样的多渠道打包工具

2017-01-17 10:50 351 查看

相对于美团打包方案,我为什么要写这个工具?

除了
Gradle
的多渠道打包,目前最流行的应该是美团(美团Android自动化之旅—生成渠道包美团Android自动化之旅—适配渠道包Walle)使用
Python
直接添加渠道文件的打包方式了,速度真是杠杠的!但是,这里有一个问题:需要一个已签名无渠道号的
APK
,那么问题来了,这个
APK
哪里来的?懂行的朋友该说了,
Gradle
随便打个
Release
包不完事了嘛。是的,我也是这么想的。但是领导说
QA
打包的时候需要改一部分配置文件代码(例如APP版本、后台环境、数据库版本bulabulabula),这样会带来潜在的问题。能不能直接通过命令行的形式打包,即
QA
不需要改动任何代码,只需要设置不同的参数就好了。小脑瓜一转,有了。
Gradle
可以。吭哧吭哧半天,做好了。然后
QA
说:敲命令行多麻烦,还可能出错。能不能直接写个界面,我点点按钮就可以了。So,就有了这个
Python
写的工具。我们暂时叫它
QA打包工具


写在前面

为什么要有这个
QA打包工具
,这个工具存在的唯一目的就是根据
QA
的选择,执行命令行打包,实际还是
Gradle
打包。如果和您预期不同或者您已经一眼看穿这小把戏。左拐出门,走好了您馁。如果您对
Gradle
配置参数打包或者对
Python
如何实现感兴趣,请继续~

效果图



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设置参数后的打包命令

打所有渠道包

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
一个是
Android
,真正实现了两不沾。破解也很简单,用文件连接。

Gradle
Build
时,会在
app/build/generated/source/buildconfig/渠道/release/包名/
下生成
BuildConfig.java
文件,就是这个文件实现了两者的通信。

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
包。具体可以看下我另一篇文章Android package属性、package name和Application ID三者的联系及区别

设置完参数之后,在
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
(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
即可
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: