Android使用python多渠道高速打包

前言

在看到美团工程师提出python脚本打包多渠道apk,如获至宝,想想之前项目十几个渠道打包下来大概20来分钟再加上电脑配置不好,那就等的更久,现在采用python的方式打包分分钟搞定十几个渠道,感觉就是畅快!

常规的打包方式

项目开发中最常用到的打包方式是 Gradle的方式,该方式的具体步骤不在本文讲解中,通常用这种方式打一个包几分钟(具体根据项目大小、电脑配置而定),那么十几个,上百个呢,这样的操作是非常耗时的。所以我们需要用更加快速的打包方式—python脚本打包

python 打包原理

我们先事先准备一个apk文件改后缀apk为zip并解压,其目录如下:
apk解压目录图
其中有个META-INF 的目录,每个apk都会包含这样的一个目录。因为META-INF目录不会被重新签名,所以我们可以将渠道文件放到该目录下,然后在代码中读取该目录下的渠道文件的渠道信息就可以了。该原理就是利用了apk解压文件下的META-INF不会被重新签名这个空隙罢了。看到这里有人会问,那这和python有毛关系啊。
如果我们一个个的apk解压,添加渠道文件到META-INF那确实和python没有关系,如果某个项目有上百个渠道你又怎么办呢?现在都讲究自动化,这就是python的作用,将上面的操作交给python去自动完成吧!现在我们通过下面的步骤来详细了解其中步骤吧!

python脚本打包准备工作

  1. 签名好的apk
  2. python脚本文件
  3. 提供channal的文本文件
  4. 在代码中获取渠道名称

1. 准备好签名好的apk 这一步就不用说了吧。

2. python脚本文件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# coding=utf-8
import zipfile
import shutil
import os
if __name__ == '__main__':
# 空文件 便于写入此空文件到apk包中作为channel文件
src_empty_file = 'empty.txt'
# 创建一个空文件(不存在则创建)
f = open(src_empty_file, 'w')
f.close()
# 获取当前目录中所有的apk源包
src_apks = []
for file in os.listdir():
if os.path.isfile(file):
extension = os.path.splitext(file)[1][1:]
if extension in 'apk':
src_apks.append(file)
# 获取渠道列表
channel_file = 'channel.txt'
f = open(channel_file)
lines = f.readlines()
f.close()
for src_apk in src_apks:
# 获取文件名字(包括后缀)
src_apk_file_name = os.path.basename(src_apk)
# 分割文件名与后缀
temp_list = os.path.splitext(src_apk_file_name)
# name without extension
src_apk_name = temp_list[0]
# 后缀名,包含. 例如: ".apk "
src_apk_extension = temp_list[1]
# 创建生成目录,与文件名相关
output_dir = 'output-' + src_apk_name + '/'
# 目录不存在则创建
if not os.path.exists(output_dir):
os.mkdir(output_dir)
# 遍历渠道号并创建对应渠道号的apk文件
for line in lines:
# 获取当前渠道号,因为从渠道文件中获得带有\n,所有strip一下
target_channel = line.strip()
# 拼接对应渠道号的apk
target_apk = output_dir + src_apk_name + "-" + target_channel + src_apk_extension
# 拷贝建立新apk
shutil.copy(src_apk, target_apk)
# zip获取新建立的apk文件
zipped = zipfile.ZipFile(target_apk, 'a', zipfile.ZIP_DEFLATED)
# 初始化渠道信息
empty_channel_file = "META-INF/channel_{channel}".format(channel=target_channel)
# 写入渠道信息
zipped.write(src_empty_file, empty_channel_file)
# 关闭zip流
zipped.close()

不熟悉python的同学还需要自己去网上查查怎么运行python文件(1、安装python运行环境 2、运行python文件)
该段代码的作用很简单:1、遍历当前目录的apk文件 2、然后在apk的META-INF的的目录中生成与渠道列表相对应的渠道文件 3、输出各个渠道的apk文件

3. 准备渠道列表文件

渠道文件
像上图中这样一个渠道号占一行,整个文件保存为.txt文件格式,上文的python代码就需要读取该渠道文件中的渠道记录

4. 在代码中获取渠道名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public String getChannelId(Context context) {
//从apk包中获取
ApplicationInfo appInfo = context.getApplicationInfo();
String sourceDir = appInfo.sourceDir;
//默认放在meta-inf/里, 所以需要再拼接一下
String key = "META-INF/channel";
String ret = "";
ZipFile zipfile = null;
try {
zipfile = new ZipFile(sourceDir);
Enumeration<?> entries = zipfile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = ((ZipEntry) entries.nextElement());
String entryName = entry.getName();
if (entryName.startsWith(key)) {
ret = entryName;
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (zipfile != null) {
try {
zipfile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
String[] split = ret.split("_");
String channel = "";
if (split != null && split.length >= 2) {
channel = ret.substring(split[0].length() + 1);
}
return channel;
}

该方法在需要获取渠道信息的地方调用(调用之前需要在META-INF目录下生成渠道文件)

采用这样的方式打包优点是,高速,方便且不用在Manifest中定义的meta标签和gradle中的productFlavors渠道。