使用python实现多渠道打包

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

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

这里以友盟统计为例。

可以选择在清单文件中添加渠道号,假如渠道号为wandoujia:
  1. <meta-data android:name=“UMENG_CHANNEL” android:value=“wandoujia” />  
<meta-data android:name="UMENG_CHANNEL" android:value="wandoujia" />

或者在java代码中添加:
  1. import com.umeng.analytics.AnalyticsConfig;  
  2.   
  3. AnalyticsConfig.setChannel(channel);  
import com.umeng.analytics.AnalyticsConfig;

AnalyticsConfig.setChannel(channel);

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

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

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

实现步骤如下:

1.在AndroidManifest.xml中添加渠道占位符
  1. <meta-data android:name=“UMENG_CHANNEL” android:value={UMENG_CHANNEL_VALUE}"</span><span>&nbsp;</span><span class="tag">/&gt;</span><span>&nbsp;&nbsp;</span></span></li></ol></div><pre name="code" class="html" style="display: none;">&lt;meta-data android:name="UMENG_CHANNEL" android:value="{UMENG_CHANNEL_VALUE}” />
    2.在module的gradle文件中添加渠道号

    1. productFlavors {  
    2.         wandoujia {}  
    3.         qihoo360 {}  
    4.     }  
    5.     productFlavors.all {  
    6.         flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]  
    7.     }  
    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 ?
    1. empty_file = ‘temp’  
    2. f = open(’temp’‘w’)   
    3. f.close()  
    empty_file = 'temp'
    f = open('temp', 'w') 
    f.close()

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

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

    [python] view plain copy
    print ?
    1. apk_file = ‘myapp.apk’  
    2. release_dir = ’release/’  
    3. if not os.path.exists(release_dir):  
    4.     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 ?
    1. temp_array = os.path.splitext(apk_file)  
    2. 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 ?
    1. f = open(‘channel.txt’)  
    2. channel_list = f.readlines()  
    3. f.close()  
    4.   
    5. for channel in channel_list:  
    6.     channel = channel.strip()  
    7.     new_apk = new_apk_file_name.format(channel = channel)  
    8.     shutil.copy(apk_file, new_apk)  
    9.     f = zipfile.ZipFile(new_apk, ’a’, zipfile.ZIP_DEFLATED)  
    10.     channel_file = ”META-INF/channel_{channel}”.format(channel = channel)  
    11.     f.write(empty_file, channel_file)  
    12.     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 ?
    1. os.remove(empty_file)  
    os.remove(empty_file)

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

    [python] view plain copy
    print ?
    1. import zipfile  
    2. import shutil  
    3. import os  
    4.   
    5. #创建空文件,用来存放渠道号  
    6. empty_file = ’temp’  
    7. f = open(’temp’‘w’)   
    8. f.close()  
    9.   
    10. # 当前目录中的初始apk文件  
    11. apk_file = ’myapp.apk’  
    12. # 创建存放渠道包的目录  
    13. release_dir = ’release/’  
    14. if not os.path.exists(release_dir):  
    15.     os.mkdir(release_dir)  
    16.   
    17. #生成新apk的文件名  
    18. temp_array = os.path.splitext(apk_file)  
    19. new_apk_file_name = release_dir + temp_array[0] + “_{channel}” + temp_array[1]  
    20.   
    21. #当前目录中的渠道列表  
    22. f = open(’channel.txt’)  
    23. channel_list = f.readlines()  
    24. f.close()  
    25.   
    26. #遍历渠道列表  
    27. for channel in channel_list:  
    28.     #删除换行符  
    29.     channel = channel.strip()  
    30.     #生成新apk文件名  
    31.     new_apk = new_apk_file_name.format(channel = channel)  
    32.     #拷贝出新apk  
    33.     shutil.copy(apk_file, new_apk)  
    34.     #打开新apk文件  
    35.     f = zipfile.ZipFile(new_apk, ’a’, zipfile.ZIP_DEFLATED)  
    36.     #生成渠道文件名  
    37.     channel_file = ”META-INF/channel_{channel}”.format(channel = channel)  
    38.     #写入渠道空文件  
    39.     f.write(empty_file, channel_file)  
    40.     #关闭文件  
    41.     f.close()  
    42.   
    43. #最后删除空文件  
    44. 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()方法。

  1. private String getChannel() {  
  2.         ApplicationInfo appinfo = getApplicationInfo();  
  3.         String sourceDir = appinfo.sourceDir;  
  4.         String ret = ”“;  
  5.         ZipFile zipfile = null;  
  6.         try {  
  7.             zipfile = new ZipFile(sourceDir);  
  8.             Enumeration<?> entries = zipfile.entries();  
  9.             while (entries.hasMoreElements()) {  
  10.                 ZipEntry entry = ((ZipEntry) entries.nextElement());  
  11.                 String entryName = entry.getName();  
  12.                 if (entryName.startsWith(“META-INF/channel”)) {  
  13.                     ret = entryName;  
  14.                     break;  
  15.                 }  
  16.             }  
  17.         } catch (IOException e) {  
  18.             e.printStackTrace();  
  19.         } finally {  
  20.             if (zipfile != null) {  
  21.                 try {  
  22.                     zipfile.close();  
  23.                 } catch (IOException e) {  
  24.                     e.printStackTrace();  
  25.                 }  
  26.             }  
  27.         }  
  28.         if (!StringUtils.isEmpty(ret)) {  
  29.             String[] split = ret.split(”_”);  
  30.             if (split != null && split.length >= 2) {  
  31.                 return ret.substring(split[0].length() + 1);  
  32.             } else {  
  33.                 return “”;  
  34.             }  
  35.         } else {  
  36.             return “”;  
  37.         }  
  38.     }  
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 "";
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值