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

Android 使用Python实现多渠道打包实践

2017-01-15 15:18 489 查看
每次发布新版本时,app会发布到国内各大应用市场,为了统计不同应用市场的推广效果,我们会为每一个apk添加唯一的标识(渠道号),方便进行统计。

对于渠道号的统计,可以使用第三方统计工具,如友盟,也可以在请求接口时将渠道号传递到后台自行统计。

这里以友盟统计为例。

可以选择在清单文件中添加渠道号,假如渠道号为wandoujia:

[html] view plain copy print?<meta-data android:name=“UMENG_CHANNEL” android:value=“wandoujia” />


<meta-data android:name="UMENG_CHANNEL" android:value="wandoujia" />


或者在java代码中添加:

[java] view plain copy print?import com.umeng.analytics.AnalyticsConfig; AnalyticsConfig.setChannel(channel);


import com.umeng.analytics.AnalyticsConfig;

AnalyticsConfig.setChannel(channel);


由于在发版时,渠道号较多,所以需要采用自动化的方式,根据渠道列表自动生成对应的渠道包。

在Eclipse开发工具盛行的年代,一般使用Ant实现批量打包。缺陷是每打一个包,都要将工程编译,签名,效率很低。

AndroidStudio推出之后,有了替代方案,使用gradle批量打包。

实现步骤如下:

1.在AndroidManifest.xml中添加渠道占位符

[html] view plain copy print?<meta-data android:name=“UMENG_CHANNEL” android:value=“{UMENG_CHANNEL_VALUE}"</span><span> </span><span class="tag">/></span><span>  </span></span></li></ol><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="html" style="display: none;"><meta-data android:name="UMENG_CHANNEL" android:value="{UMENG_CHANNEL_VALUE}” />

2.在module的gradle文件中添加渠道号

[html] view plain copy print?productFlavors { wandoujia {} qihoo360 {} } productFlavors.all { flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name] }


productFlavors {
wandoujia {}
qihoo360 {}
}
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}


3.点击工具栏的Build,选择Generate Signed APK,然后选中需要打包的渠道即可。

使用gradle打包,是通过修改AndroidManifest文件来实现的。每打一个渠道包,需要重新签名。这种方式现在比较流行,效率一般,当渠道号过多时略显吃力。

接下来进入本文的重点,使用python实现多渠道打包。使用这种方式,分分钟打一千个包不再是梦。

该方案出自美团分享的解决方案:

http://tech.meituan.com/mt-apk-packaging.html

实现思路:

如果能直接修改apk的渠道号,而不需要再重新签名能节省不少打包的时间。解压apk,解压后的根目录会有一个META-INF目录。如果在META-INF目录内添加空文件,可以不用重新签名应用。因此,通过为不同渠道的应用添加不同的空文件,可以唯一标识一个渠道。

思路已经很清晰了,在META-INF目录添加一个空文件,文件名即渠道号,如channel_wandoujia。然后在java代码中对文件进行遍历,找到该渠道文件,读出文件名,即获取到了渠道号。

美团并没有给出具体的Python代码,这里我们将其逐步实现。

首先看一下最终实现的效果,在同一个路径里,有一个python文件,一个渠道列表文件(渠道号之间以换行符分隔),一个初始apk文件(已签名,无渠道号)。



windows环境下可双击channel.py文件,或者在命令行切换到当前路径,输入python channel.py,即可执行。此时会出现一个release文件夹。



打开release文件夹,里面就是我们根据渠道列表生成的不同渠道包。



接下来,我们来实现channel.py。

(1).创建空文件,用来存放渠道号。

[python] view plain copy print?empty_file = ‘temp’
f = open(’temp’, ‘w’)
f.close()


empty_file = 'temp'
f = open('temp', 'w')
f.close()


(2).指定当前目录中的初始apk文件,apk文件名可自行定义,此处为myapp.apk。

创建存放渠道包的目录,目录名称可自行定义,此处为release。

[python] view plain copy print?apk_file = ‘myapp.apk’
release_dir = ’release/’
if not os.path.exists(release_dir):
os.mkdir(release_dir)


apk_file = 'myapp.apk'
release_dir = 'release/'
if not os.path.exists(release_dir):
os.mkdir(release_dir)


(3).生成新apk的文件名,包含“channel”占位符。

[python] view plain copy print?temp_array = os.path.splitext(apk_file)
new_apk_file_name = release_dir + temp_array[0] + “_{channel}” + temp_array[1]


temp_array = os.path.splitext(apk_file)
new_apk_file_name = release_dir + temp_array[0] + "_{channel}" + temp_array[1]


(4).遍历渠道列表,根据渠道号生成相应的apk文件。

此处生成的渠道文件名格式为channel_xxx,可自行定义。

[python] view plain copy print?f = open(‘channel.txt’)
channel_list = f.readlines()
f.close()

for channel in channel_list:
channel = channel.strip()
new_apk = new_apk_file_name.format(channel = channel)
shutil.copy(apk_file, new_apk)
f = zipfile.ZipFile(new_apk, ’a’, zipfile.ZIP_DEFLATED)
channel_file = ”META-INF/channel_{channel}”.format(channel = channel)
f.write(empty_file, channel_file)
f.close()


f = open('channel.txt')
channel_list = f.readlines()
f.close()

for channel in channel_list:
channel = channel.strip()
new_apk = new_apk_file_name.format(channel = channel)
shutil.copy(apk_file, new_apk)
f = zipfile.ZipFile(new_apk, 'a', zipfile.ZIP_DEFLATED)
channel_file = "META-INF/channel_{channel}".format(channel = channel)
f.write(empty_file, channel_file)
f.close()


(5).最后删除不再使用的空文件。

[python] view plain copy print?os.remove(empty_file)


os.remove(empty_file)


附上channel.py的完整代码,在python3上测试可完美运行。

[python] view plain copy print?import zipfile import shutil import os#创建空文件,用来存放渠道号
empty_file = ’temp’
f = open(’temp’, ‘w’)
f.close()

# 当前目录中的初始apk文件
apk_file = ’myapp.apk’
# 创建存放渠道包的目录
release_dir = ’release/’
if not os.path.exists(release_dir):
os.mkdir(release_dir)

#生成新apk的文件名
temp_array = os.path.splitext(apk_file)
new_apk_file_name = release_dir + temp_array[0] + “_{channel}” + temp_array[1]

#当前目录中的渠道列表
f = open(’channel.txt’)
channel_list = f.readlines()
f.close()

#遍历渠道列表
for channel in channel_list:
#删除换行符
channel = channel.strip()
#生成新apk文件名
new_apk = new_apk_file_name.format(channel = channel)
#拷贝出新apk
shutil.copy(apk_file, new_apk)
#打开新apk文件
f = zipfile.ZipFile(new_apk, ’a’, zipfile.ZIP_DEFLATED)
#生成渠道文件名
channel_file = ”META-INF/channel_{channel}”.format(channel = channel)
#写入渠道空文件
f.write(empty_file, channel_file)
#关闭文件
f.close()

#最后删除空文件
os.remove(empty_file)


import zipfile
import shutil
import os


#创建空文件,用来存放渠道号
empty_file = 'temp' f = open('temp', 'w') f.close()

# 当前目录中的初始apk文件
apk_file = 'myapp.apk'

# 创建存放渠道包的目录
release_dir = 'release/'
if not os.path.exists(release_dir):
os.mkdir(release_dir)

#生成新apk的文件名
temp_array = os.path.splitext(apk_file) new_apk_file_name = release_dir + temp_array[0] + "_{channel}" + temp_array[1]

#当前目录中的渠道列表
f = open('channel.txt')
channel_list = f.readlines()
f.close()

#遍历渠道列表
for channel in channel_list:
#删除换行符
channel = channel.strip()
#生成新apk文件名
new_apk = new_apk_file_name.format(channel = channel)
#拷贝出新apk
shutil.copy(apk_file, new_apk)
#打开新apk文件
f = zipfile.ZipFile(new_apk, 'a', zipfile.ZIP_DEFLATED)
#生成渠道文件名
channel_file = "META-INF/channel_{channel}".format(channel = channel)
#写入渠道空文件
f.write(empty_file, channel_file)
#关闭文件
f.close()

#最后删除空文件
os.remove(empty_file)

代码实现完毕,我们来验证一下成果。任意找一个渠道包,如myapp_wandoujia.apk,打开apk文件,如图所示。



我们进入META-INF文件夹。



会发现,其中多了一个channel_wandoujia的文件,大小为0。

到此,渠道包已生成完毕。接下来,我们需要在Java代码中将渠道号读取出来。

美团已公布getChannel()方法的实现,但其中有一个bug。entryName.startsWith(“mtchannel”),应修改为entryName.startsWith(“META-INF/mtchannel”)。

这里给出已在项目中使用的getChannel()方法。

[java] view plain copy print?private String getChannel() {
ApplicationInfo appinfo = getApplicationInfo();
String sourceDir = appinfo.sourceDir;
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(“META-INF/channel”)) {
ret = entryName;
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (zipfile != null) {
try {
zipfile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (!StringUtils.isEmpty(ret)) {
String[] split = ret.split(”_”);
if (split != null && split.length >= 2) {
return ret.substring(split[0].length() + 1);
} else {
return “”;
}
} else {
return “”;
}
}


private String getChannel() {
ApplicationInfo appinfo = getApplicationInfo();
String sourceDir = appinfo.sourceDir;
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("META-INF/channel")) {
ret = entryName;
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (zipfile != null) {
try {
zipfile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (!StringUtils.isEmpty(ret)) {
String[] split = ret.split("_");
if (split != null && split.length >= 2) {
return ret.substring(split[0].length() + 1);
} else {
return "";
}
} else {
return "";
}
}


出处:http://blog.csdn.net/ruancoder/article/details/51893879
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: