国内的Android开发者还是很苦逼的,由于众所周知的原因,google play无法在国内打开(翻墙的就不在考虑之内了),所以Android系的应用市场,群雄争霸。后果就是国内存在着有众多的应用市场,产品在不同的渠道可能有这不同的统计需求,为此Android开发人员需要为每个应用市场发布一个安装包,这里就引出了Android的多渠道打包。
首先我们说明一下什么是多渠道打包?
国内存在着众多的Android应用市场,为了统计不同安卓应用市场的下载量一个个性化统计需求,需要为每个应用市场的Android包设定一个可以区分应用市场的标识,这个为Android包设定应用市场标识的过程就是多渠道打包。
几种主流的多渠道打包方式,以及其优劣势
通过配置gradle脚本实现多渠道打包
这种打包方式是使用Android Studio的编译工具gradle配合使用的,其核心原理就是通过脚本修改AndroidManifest.xml中的mate-date内容,执行N次打包签名操作实现多渠道打包的需求,具体实现如下。
(一)在Androidmanifest.xml中定义mate-data标签
coding=utf-8
import zipfile
import shutil
import osdef delete_file_folder(src):
”’delete files and folders”’
if os.path.isfile(src):
try:
os.remove(src)
except:
pass
elif os.path.isdir(src):
for item in os.listdir(src):
itemsrc=os.path.join(src,item)
delete_file_folder(itemsrc)
try:
os.rmdir(src)
except:
pass
创建一个空文件,此文件作为apk包中的空文件
src_empty_file = ‘info/empty.txt’
f = open(src_empty_file,’w’)
f.close()在渠道号配置文件中,获取指定的渠道号
channelFile = open(‘./info/channel.txt’,’r’)
channels = channelFile.readlines()
channelFile.close()
print(‘-‘*20,’all channels’,’-‘*20)
print(channels)
print(‘-‘*50)获取当前目录下所有的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)遍历所以的apk文件,向其压缩文件中添加渠道号文件
for src_apk in src_apks:
src_apk_file_name = os.path.basename(src_apk)
print(‘current apk name:’,src_apk_file_name)
temp_list = os.path.splitext(src_apk_file_name)
src_apk_name = temp_list[0]
src_apk_extension = temp_list[1]apk_names = src_apk_name.split('-');
output_dir = 'outputDir'+'/'
if os.path.exists(output_dir):
delete_file_folder(output_dir)
if not os.path.exists(output_dir):
os.mkdir(output_dir)
# 遍历从文件中获得的所以渠道号,将其写入APK包中
for line in channels:
target_channel = line.strip()
target_apk = output_dir + apk_names[0] + "-" + target_channel+"-"+apk_names[2] + src_apk_extension
shutil.copy(src_apk, target_apk)
zipped = zipfile.ZipFile(target_apk, 'a', zipfile.ZIP_DEFLATED)
empty_channel_file = "META-INF/uuchannel_{channel}".format(channel = target_channel)
zipped.write(src_empty_file, empty_channel_file)
zipped.close()print(‘-‘*50)
print(‘repackaging is over ,total package: ‘,len(channels))
input(‘\npackage over…’)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
59
60
61
62
63
64
65
66
67
68
69
70
71
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
59
60
61
62
63
64
65
66
67
68
69
70
71(三)打包一个正常的apk包
(四)执行python脚本,多渠道打包
(五)Android代码中获取渠道号/**
* 渠道号工具类:解析压缩包,从中获取渠道号
*/
public class ChannelUtil {
private static final String CHANNEL_KEY = “uuchannel”;
private static final String DEFAULT_CHANNEL = “internal”;
private static String mChannel;
public static String getChannel(Context context) {
return getChannel(context, DEFAULT_CHANNEL);
}
public static String getChannel(Context context, String defaultChannel) {
if (!TextUtils.isEmpty(mChannel)) {
return mChannel;
}
//从apk中获取
mChannel = getChannelFromApk(context, CHANNEL_KEY);
if (!TextUtils.isEmpty(mChannel)) {
return mChannel;
}
//全部获取失败
return defaultChannel;
}
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
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
/**
* 从apk中获取版本信息
*
* @param context
* @param channelKey
* @return
*/
private static String getChannelFromApk(Context context, String channelKey) {
long startTime = System.currentTimeMillis();
//从apk包中获取
ApplicationInfo appinfo = context.getApplicationInfo();
String sourceDir = appinfo.sourceDir;
//默认放在meta-inf/里, 所以需要再拼接一下
String key = "META-INF/" + channelKey;
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 channel = "";
if (!TextUtils.isEmpty(ret)) {
String[] split = ret.split("_");
if (split != null && split.length >= 2) {
channel = ret.substring(split[0].length() + 1);
}
System.out.println("-----------------------------");
System.out.println("渠道号:" + channel + ",解压获取渠道号耗时:" + (System.currentTimeMillis() - startTime) + "ms");
System.out.println("-----------------------------");
} else {
System.out.println("未解析到相应的渠道号,使用默认内部渠道号");
channel = DEFAULT_CHANNEL;
}
return channel;
}
}
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
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
整个打包的流程就是这样了,打工工具可参考github上的项目:多渠道打包实现
优势:打包速度很快,很方便;
劣势:不够灵活,不能灵活的配置不同的渠道不同的业务逻辑;
问题:
项目中由于使用了友盟统计,以前是在meta-data中保存渠道信息,现在更改了方式之后需要手动执行渠道号的设置代码:
String channel = ChannelUtil.getChannel(mContext);
System.out.println(“启动页获取到的渠道号为:” + channel);
// 设置友盟统计的渠道号,原来是在Manifest文件中设置的meta-data,现在启动页中设置
AnalyticsConfig.setChannel(channel);
1
2
3
4
1
2
3
4
通过这种打包方式以前需要一个小时的打包工作现在只需要一分钟即可,极大的提高了效率,目前在实际的应用中尚未发现有什么问题,有这种需求的童鞋可以尝试一下。
总结
虽说我们总结了三种打包方式,但是其实通过gradle打包和使用第三方服务打包都是执行了N次的打包签名操作,时间上耗费太多,因此不太推荐,而美团的方式在效率上提高了很多,但是对于那种不同的渠道包执行不同的业务逻辑的需求就无能为例了,只能通过gradle配置,因此大家在选择多渠道打包方式的时候可以根据自身的需求来选择。
另外对产品研发技术,技巧,实践方面感兴趣的同学可以参考我的:
Android产品研发–>总结(持续更新)
Android产品研发(一)–>实用开发规范
Android产品研发(二)–>启动页优化
Android产品研发(三)–>基类Activity
Android产品研发(四)–>减小Apk大小