打包系列教程目录:

纯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方式的打包需要做一下准备(本节涉及的所有文件我在最后都会提供给大家下载):

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

1.改动签名好的apk

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

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

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

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

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

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

说完了apk的区别设置后,我们先来聊聊python打包的实现思路。我们先获取一个打包好的apk,并把后缀改为zip,解压如下:
详解高速神器python脚步打包android apk,超级快!!(打包系列教程之六)_第3张图片
这是一个已经签名打包的apk,解压后我们可以看到apk中包含了一个名称为META-INF的文件夹,其实python打包的奥秘就在于此了,因为每个apk都会包含这样一个名称为META-INF文件夹,所以我们可以利用python脚步在该文件夹下创建一个空的文件,这个文件的名称就命名为channel_xxx.txt,该文件并没有任何内容,仅作为渠道标志,比如现在是华为渠道,那么该文件的名称就是channel_huawei.txt,如果现在的渠道是xiaomi,那么该文件的名称就是channel_xiaomi.txt,为了验证以上的说法我们先来看看已经利用python打包好的apk:
详解高速神器python脚步打包android apk,超级快!!(打包系列教程之六)_第4张图片
然后我们打开其中几个apk看看META-INF文件夹下是否有对应的渠道文件:
详解高速神器python脚步打包android apk,超级快!!(打包系列教程之六)_第5张图片

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

xiaomihuaweiyingyongbao360mobilewandoujiaanzhuo_marketbaidu91marketanzhi_marketgoogleplay
大家可能已经猜到了,没错,python脚步在开始时会去读取这个文件,根据这个文件的渠道名称去进行for循环,然后把每个渠道名称以channel_xxx结尾作为文件的名称。我们不妨看看python脚步的源码 :
#coding=utf-8import zipfileimport shutilimport osimport sysif __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')    #mac    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) {try{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,以及相应的工具类,我们使用的项目结构如下:

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

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

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

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

python channel.py app-debug4zj.apk
详解高速神器python脚步打包android apk,超级快!!(打包系列教程之六)_第8张图片
成功后我们在看看该目录下多出一个release的文件夹(没有经过zipalign优化的apk),里面还有一个zipalign的文件夹(经过zipalign优化的apk),如下:

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

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

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

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

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

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

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

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

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

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



更多相关文章

  1. Android 文件路径详解
  2. Android的APK包里的文件类型都是什么?
  3. 教你如何在 Android 使用多线程下载文件
  4. Android 文件操作详解及简单实例
  5. Android Studio 友盟api实现apk多渠道打包
  6. Android Picasso 图片加载库基础使用详解
  7. Android下 读写文件

随机推荐

  1. android 常用数据库字段描述
  2. Android eMMC Booting[wiki百科]
  3. android 开发中的文件操作
  4. 分享45个android实例源码,很好很强大
  5. android intent filter浏览器应用的设置,
  6. 有关Android 平台解析XML
  7. 第五篇 ImageSwitcher篇
  8. kotlin入门系列二---基本语法
  9. 手动修改android模拟器system.img
  10. android EventBus 3.0 官方的混淆配置