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” />
或者在java代码中添加:
[java] view plain copy print?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] }
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()
(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)
(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]
(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()
(5).最后删除不再使用的空文件。
[python] view plain copy print?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)
#创建空文件,用来存放渠道号
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 “”;
}
}
出处:http://blog.csdn.net/ruancoder/article/details/51893879
对于渠道号的统计,可以使用第三方统计工具,如友盟,也可以在请求接口时将渠道号传递到后台自行统计。
这里以友盟统计为例。
可以选择在清单文件中添加渠道号,假如渠道号为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
相关文章推荐
- Android 使用Python实现多渠道打包
- 使用gradle的productFlavors实现Android项目多渠道打包
- 使用gradle的productFlavors实现Android项目多渠道打包
- Android QA专用,Python实现不一样的多渠道打包工具
- 使用Python脚本语言实现自动多渠道打包心得
- 使用python实现多渠道打包
- Android使用Gradle实现多渠道打包
- 使用ANT编译--实现外部传参(多渠道替换)、批处理打包------第二步什么是ANT,如何使用ANT命令为Android项目打包
- Python实现Android Apk 加固及多渠道打包
- 使用gradle的productFlavors实现Android项目多渠道打包
- Android 使用gradle的productFlavors实现多渠道打包
- Android QA专用,Python实现不一样的多渠道打包工具
- android 多渠道打包---使用python 3.3.2
- Android快速使用Gradle实现多渠道打包
- 使用gradle的productFlavors实现Android项目多渠道打包
- Android——使用Python脚本进行多渠道打包-秒打100个包!
- Android使用兰贝壳儿实现多渠道打包
- android中,使用ant多渠道打包时出现Perhaps JAVA_HOME does not point to the JDK问题的解决方法
- [置顶] Android 多渠道打包:使用Gradle和Android Studio
- Android Studio使用教程(六):Gradle多渠道打包