使用python实现多渠道打包

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

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

这里以友盟统计为例。

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

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

或者在java代码中添加:

import com.umeng.analytics.AnalyticsConfig;

AnalyticsConfig.setChannel(channel);

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

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

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

实现步骤如下:

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

<meta-data android:name="UMENG_CHANNEL" android:value="${UMENG_CHANNEL_VALUE}" />

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

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文件(已签名,无渠道号)。

《使用python实现多渠道打包》

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

《使用python实现多渠道打包》

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

《使用python实现多渠道打包》

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

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

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

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

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

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

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

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

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

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

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).最后删除不再使用的空文件。

os.remove(empty_file)

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

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文件,如图所示。

《使用python实现多渠道打包》

我们进入META-INF文件夹。

《使用python实现多渠道打包》

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

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

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

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

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 "";
        }
    }

    原文作者:ruancoder
    原文地址: https://blog.csdn.net/ruancoder/article/details/51893879
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞