相关文章推荐
沉着的抽屉  ·  python for循环 ...·  1 月前    · 
没读研的消炎药  ·  Python ...·  1 年前    · 
打酱油的豆浆  ·  ASP.NET Core ...·  1 年前    · 
独立的小狗  ·  pg数据库转义字符-掘金·  1 年前    · 

 打包系列教程目录:

纯ant命令行打包android apk之图文从原理角度完全详解android打包过程(打包系列教程之一)

用ant的build.xml构建自动化打包android apk 完全详解(打包系列教程之二)

Android 多渠道打包之混淆文件ProGuard技术详解-特别篇(打包系列教程之三)

android studio gradle 多渠道打包之完全详解(打包系列教程之四)

android studio gradle 多版本多apk打包(打包系列教程之五)

详解高速神器python脚步打包android apk,超级快!!(打包系列教程之六)

今天终于要来给大家介绍python多渠道打包啦,我也是很激动,当初虽然有gradle这样方便的打包方式,但是一旦渠道数量多了起来,gradle打包的时间也会成为一个瓶颈,之前打20个渠道左右,用gradle打包的话大概要花上20多分钟,如果以后渠道增加到上百个那就真的呵呵了!不过现在即使再多的渠道包也没关系啦,有python在都是秒秒钟搞定的时,python打包是美团工程师的杰作,在此十分感谢哈!用python脚步打包的话,打20个渠道左右的包大概只要花上不到5分钟的时间,十分的快啊!这酸爽的感觉,太刺激了。 接下来我们就开始吧,python方式的打包需要做一下准备(本节涉及的所有文件我在最后都会提供给大家下载):

1.改动签名好的apk

这个apk里面的渠道配置信息跟gradle多渠道打包的配置有些不一样哈。之前我们是需要在AndroidManifest.xml文件中渠道信息,现在用python打包的话就不用啦,我们直接在启动的activity文件中设置就行了,设置代码如下:

//动态设置渠道信息
String channel= ChannelUtil.getChannel(this);
AnalyticsConfig.setChannel(channel);

ChannelUtil.java类是一个获取渠道信息的类,这个类到时会提供给大家,而AnalyticsConfig则是友盟提供的设置渠道类,我们在友盟官网可以看到这样的介绍:

因此我们使用的就是第2种设置渠道的方式。嗯,这就是唯一与gradle打包的不同点,同时要注意,这个签名的apk不需要提前设置任何渠道,所以在gradle配置文件中无需使用productFlavors属性来设置渠道名称。

2.Python打包的实现思路详解

说完了apk的区别设置后,我们先来聊聊python打包的实现思路。我们先获取一个打包好的apk,并把后缀改为zip,解压如下:

确实如我们上面所说的一样,每个apk的META-INF文件夹中都含有一个渠道名称的文件。那这个渠道名称的文件是如何创建的呢,确实我们自己通过as打包apk时是不可能含有该渠道名称文件的,而这也正是python的功劳了,我们把已经签名好的apk,通过python脚步的for循环语句去解压我们已经打包签名好的apk,然后在每个apk的META-INF文件夹中通过python脚步去创建一个渠道名称的文件,创建完成后在重新还原成apk输出,就这样渠道名称文件就被设置到apk中啦。那么这个python循环是依据什么开始的呢,还记得我们开头提到过的channel.txt文件嘛?该文件内容如下:

xiaomi
huawei
yingyongbao
360mobile
wandoujia
anzhuo_market
baidu
91market
anzhi_market
googleplay
大家可能已经猜到了,没错,python脚步在开始时会去读取这个文件,根据这个文件的渠道名称去进行for循环,然后把每个渠道名称以channel_xxx结尾作为文件的名称。我们不妨看看python脚步的源码 :
#coding=utf-8
import zipfile
import shutil
import os
import sys
if __name__ == '__main__':
    apkFile = sys.argv[1]
    apk = apkFile.split('.apk')[0]
    # print apkFile
    emptyFile = 'xxx.txt'
    f = open(emptyFile, 'w')
    f.close()
    with open('./android_channels.txt', 'r') as f:
        contens = f.read()
    lines = contens.split('\n')
    os.mkdir('./release')
    #print lines[0]
    for line in lines:
        channel = 'channel_' + line
        destfile = './release/%s_%s.apk' % (apk, channel)
        shutil.copy(apkFile, destfile)
        zipped = zipfile.ZipFile(destfile, 'a')
        channelFile = "META-INF/{channelname}".format(channelname=channel)
        zipped.write(emptyFile, channelFile)
        zipped.close()
    os.remove('./xxx.txt')
    os.system('chmod u+x zipalign_batch.sh')
    os.system('./zipalign_batch.sh')
    #windows
    #os.system('zipalign_batch.bat')
代码并不太复杂(我也只是懂一些python的基础哈,也还在慢慢学习中),我们可以看到一开始会去读取android_channels.txt的渠道文件,然后创建一个release的文件夹,然后就进入for循环了,后面我们就不过多讨论了,大概明白意思就行。通过上面的分析我们也大概明白了channel_xxx.txt文件是如何被写入每个apk的,同时也知道了android_channels.txt这个文件的作用。但是在apk中写入 channel_xxx.txt渠道名称的文件有什么用呢,这个就是ChannelUtil.java工具类的作用了,还记得我们的渠道是怎么设置的嘛?
//动态设置渠道信息
String channel= ChannelUtil.getChannel(this);
AnalyticsConfig.setChannel(channel);
没错,在应用启动时,ChannelUtil.java工具类会去读取META-INF文件下的channel_xxx.txt文件的名称,并通过拆分去掉channel_字符串,从而获取到渠道名称,最后就可以通过友盟api的接口发送友盟服务器了。 ChannelUtil.java源码如下:
package com.zejian.application.utils;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.text.TextUtils;
import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
 * Created by WuZeJian
 * Time:2015/12/1 17:57
 * Email:shinezejian@163.com
 * Description:打包渠道工具类
public class ChannelUtil {
	private static final String CHANNEL_KEY = "channel";
	private static final String CHANNEL_DEFAULT = "offical";
	private static final String PREF_KEY_CHANNEL = "pref_key_channel";
	private static final String PREF_KEY_CHANNEL_VERSION = "pref_key_channel_version";
	private static String mChannel;
	 * 返回市场。  如果获取失败返回""
	 * @param context
	 * @return
	public static String getChannel(Context context){
		return getChannel(context, CHANNEL_DEFAULT);
	 * 返回市场。  如果获取失败返回defaultChannel
	 * @param context
	 * @param defaultChannel
	 * @return
	public static String getChannel(Context context, String defaultChannel) {
		//内存中获取
		if(!TextUtils.isEmpty(mChannel)){
			return mChannel;
		//sp中获取
		mChannel = getChannelFromSP(context);
		if(!TextUtils.isEmpty(mChannel)){
			return mChannel;
		//从apk中获取
		mChannel = getChannelFromApk(context, CHANNEL_KEY);
		if(!TextUtils.isEmpty(mChannel)){
			//保存sp中备用
			saveChannelInSP(context, mChannel);
			return mChannel;
		//全部获取失败
		return defaultChannel;
	 * 从apk中获取版本信息
	 * @param context
	 * @param channelKey
	 * @return
	private static String getChannelFromApk(Context context, String channelKey) {
		//从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();
                LogUtils.d("APK entry name --------> " + entryName);
                if (entryName.startsWith(key)) {
                    ret = entryName;
                    break;
        } catch (IOException e) {
            LogUtils.e(e);
        } finally {
            if (zipfile != null) {
                try {
                    zipfile.close();
                } catch (IOException e) {
                		LogUtils.e(e);
        String[] split = ret.split("_");
        String channel = "";
        if (split != null && split.length >= 2) {
        		channel = ret.substring(split[0].length() + 1);
        return channel;
	 * 本地保存channel & 对应版本号
	 * @param context
	 * @param channel
	private static void saveChannelInSP(Context context, String channel){
		SharedPreferencedUtils.setString(context, PREF_KEY_CHANNEL, channel);
		SharedPreferencedUtils.getInteger(context, PREF_KEY_CHANNEL_VERSION, getVersionCode(context));
	 * 从sp中获取channel
	 * @param context
	 * @return 为空表示获取异常、sp中的值已经失效、sp中没有此值
	private static String getChannelFromSP(Context context){
		int currentVersionCode = getVersionCode(context);
		if(currentVersionCode == -1){
			//获取错误
			return "";
		int versionCodeSaved = SharedPreferencedUtils.getInteger(context, PREF_KEY_CHANNEL_VERSION, -1);
		if(versionCodeSaved == -1){
			//本地没有存储的channel对应的版本号
			//第一次使用  或者 原先存储版本号异常
			return "";
		if(currentVersionCode != versionCodeSaved){
			return "";
		return SharedPreferencedUtils.getString(context, PREF_KEY_CHANNEL, "");
	public static int getVersionCode(Context context) {
			return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
		}catch(NameNotFoundException e) {
			LogUtils.e(e);
		return -1;
好了,到此python打包的思路基本讲完了,我们先来小结:首先我们先准备一个打包签名好的apk,然后通过python脚步去解压该apk,并在apk的META-INF目录下创建一个名称channel_xxx.txt的渠道名称文件,然后再重新打包成apk,这个 
channel_xxx.txt的渠道名称文件在apk应用启动时会被一个名称为ChannelUtil.java的工具类读取,该工具通过META-INF目录下的channel_xxx.txt获取到渠道名称并设置给友盟,这样就完成了友盟渠道信息的记录。 

3.Python打包实战记录(记得集成友盟统计哈)

说了这么多还是赶紧来实战吧,首先我们要配置key,以及相应的工具类,我们使用的项目结构如下:

然后我们打包出一个签名好的apk,并把它放到和python脚步同一个目录下:

然后打开我们的命令终端,cd到该目录下,输入如下命令,回车。

python channel.py app-debug4zj.apk
成功后我们在看看该目录下多出一个release的文件夹(没有经过zipalign优化的apk),里面还有一个zipalign的文件夹(经过zipalign优化的apk),如下:

就这样我们的apk都打包好啦,至于zipalign优化有什么作用,我在第一篇文章有详细的说明,大家可以移步看看哈。这里我就不重复了。还有等会我会把工具提供给大家如果是mac系统的话,python已经默认安装好的了哈,如果是window系统就先要安装python环境哈。还有这里提供两种zipalign_batch.bat(window平台用)和zipalign_batch.sh(mac平台使用)脚本。同时要记得配置好zipalign的环境变量哈(该工具在androidSdk/build-tools目录下)。最后还有一点要注意的是channel.py脚本文件最后的代码设置如下:

4.使用友盟渠道统计验证一下打包结果

说了这么多,操作也讲解了,最后到底靠不靠谱,还是得用友盟来检测一下对吧。我们还是使用前篇文章的测试设备vivoxplay3s ,上次测试完后现在的初始数据如下

我们依次安装baidu,91market,anzhi_market,googleplay_market,yingyongbao,测试结果如下:

看来还是很靠谱的嘛,最重要的是速度,速度,速度啊。赶紧去试试吧哈。 本篇资料下载:

http://download.csdn.net/detail/javazejian/9446843

今天终于要来给大家介绍python多渠道打包啦,我也是很激动,当初虽然有gradle这样方便的打包方式,但是一旦渠道数量多了起来,gradle打包的时间也会成为一个瓶颈,之前打20个渠道左右,用gradle打包的话大概要花上20多分钟,如果以后渠道增加到上百个那就真的呵呵了!不过现在即使再多的渠道包也没关系啦,有python在都是秒秒钟搞定的时,python打包是美团工程师的杰作,在此十分感谢哈! Kivy是一套Python下的跨平台开源应用开发框架,官网,我们可以用 它来将Python程序打包为安卓的apk安装文件。以下是在windows环境中使用。 安装和配置的过程中会下载很多东西,确保你能够稳定地访问外网,另外推荐一个视频教程:Youtube 2. 步骤 第一当然是安装了Python,我的版本是Python 2.7.13,然后就是安装Kivy包: 官网有详细的...
适用于Androidpython python-for-androidAndroidPython应用程序的打包工具。 您可以创建自己的Python发行版(包括所需的模块和依赖项),并将其与自己的代码捆绑在APK中。 功能包括: 不同的应用程序后端,包括Kivy,PySDL2和带有Python网络服务器的WebView。 自动支持大多数纯Python模块,并内置支持许多其他模块,包括流行的依赖项,例如numpy和sqlalchemy。 多个架构目标,适用于在任何给定设备上优化的APK。 有关文档和支持,请参阅: 网站: : 邮件列表: : forum/kivy- 或 。 按照安装并开始创建APK快速说明:使用以下命令安装python-for-android: pip install python-for-android (对于develop分支: pip install git+https://github.com/kivy/python-for-android.git ) 测试安装适用于: p4a --version 要构建任何实际的应
是一个开源的 Python 框架,用于快速开发应用,实现各种当前 流行的用户界面,比如多点触摸等等。且Kivy 可以运行于 Windows, Linux, MacOS, Android, iOS 等当前绝大部分主流桌面/移动端操作系统。 周日在配置Kivy时,教程繁多繁琐,让自己有些找不着北,挨个试后,经常在某 处卡壳,屡屡碰壁, 希望自己接下来的
Kivy跨平台开源框架之Android打包里写自定义目录标题.一、 自己搭建Buildozer环境二、 使用docker容器 在网上找到三种python打包android包的方式: 1、可以使用 python-for-androidandroid 创建一个包。 2、可以使用 Buildozer 工具自动完整个过程。 3、可以使用 Kivy Launcher 打包,这样不用编译就能运行 Kivy 应用。 尝试了其中其中buildozer 的方式,还有一种是尝试下载docker镜像的方式。其中第一次尝
官方教程:https://docs.beeware.org/en/latest/tutorial/tutorial-0.html 本教程使用的系统为Windows11,python版本为3.7 需要科学上网 只能使用纯python模块,不能使用numpy等包含c的模块 只适用于一些小型程序,大程序不建议使用这个开发 也可以打包为Windows安装程序和iOS程序,但不在本文的介绍范围,不过也大同小异 教程中建议使用python3.7或者更高版本,所以我使用conda创建了一个python3.7的
要将 Python 文件打包 apk 文件,您可以使用 Kivy 框架。Kivy 是一个跨平台的 GUI 库,可以使用 Python 开发桌面应用程序、移动应用程序和游戏。 首先,您需要安装 Kivy。您可以使用 pip 命令安装 Kivy: pipinstall kivy 然后,使用 Kivy 创建一个 Python 文件,并在文件中编写代码。例如,以下是一个简单的 Kivy 程序: fro...
Python的Tkinter是一个GUI编程库,它提供了一组工具和组件,可以用于创建图形用户界面。Tkinter是Python标准库的一部分,因此它可以在大多数Python安装中使用。 Tkinter提供了许多组件,例如按钮、标签、文本框、滚动条等等,这些组件可以用于创建各种类型的GUI应用程序。Tkinter还提供了布局管理器,可以帮助您轻松地组织和排列组件。 使用Tkinter创建GUI应用程序的基本步骤如下: 1. 导入Tkinter模块 2. 创建主窗口 3. 添加组件 4. 设置组件属性 5. 组织和排列组件 6. 启动主循环 在Tkinter中,每个组件都有一个唯一的标识符,称为“名称”。您可以使用名称来引用组件并设置其属性。例如,您可以使用以下代码创建一个标签: label = tkinter.Label(root, text="Hello, World!") 在这里,“root”是主窗口的名称,“text”是标签的属性之一。您可以使用以下代码设置标签的文本颜色: label.config(foreground="red") Tkinter还提供了许多事件,例如单击按钮、按下键盘等等。您可以使用这些事件来响应用户的操作。例如,您可以使用以下代码创建一个按钮,并在单击时显示一条消息框: button = tkinter.Button(root, text="Click me!") button.bind("<Button-1>", lambda event: tkinter.messagebox.showinfo("Message", "Hello, World!")) 在这里,“bind”方法将按钮与事件绑定在一起,“<Button-1>”表示单击左键,lambda函数用于创建一个匿名函数,该函数在单击按钮时显示消息框。 总之,Tkinter是一个功能强大的GUI编程库,可以帮助您轻松地创建各种类型的GUI应用程序。如果您想深入了解Tkinter的使用,请查看官方文档或参考其他资源。