为什么要多渠道打包?
众所周知,由于Google play在国内无法使用,国内Android应用市场群雄纷争,存在着众多的应用市场,在不同的市场可能有着不同的统计需求,需要为每个应用市场的Android包设定一个可以区分应用市场的标识,为此Android开发人员需要为每个应用市场发布一个安装包(渠道包),这就是为什么Android需要多渠道打包。
使用gradle打包
配置AndroidManifest.xml
在Androidmanifest.xml中定义mate-data标签:
1 2 3 4 5 6 7 8
| <manifest xmlns:Android="http://schemas.Android.com/apk/res/Android" package="com.package.name"> <application>
<meta-data android:name="UMENG_CHANNEL" android:value="xiaomi" />
</application> </manifest>
|
如果不使用多渠道打包方法,那就需要我们手动一个一个去修改value中的值,比如xiaomi,360,qq,wandoujia等等。
使用多渠道打包的方式,就需要把上面的value配置成下面的方式:
1
| <meta-data android:name="UMENG_CHANNEL" android:value="${UMENG_CHANNEL_VALUE}" />
|
其中${UMENG_CHANNEL_VALUE}中的值就是你在gradle中自定义配置的值。
在build.gradle设置productFlavors
1 2 3 4 5 6 7 8 9 10 11 12 13
| productFlavors { wandoujia { manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"] }
xiaomi{ manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"] }
qq { manifestPlaceholders = [UMENG_CHANNEL_VALUE: "qq"] } }
|
其中[UMENG_CHANNEL_VALUE: “wandoujia”]就是对应${UMENG_CHANNEL_VALUE}的值。但是这种写法比较繁琐,可以简写为:
1 2 3 4 5 6 7 8 9 10 11
| android { productFlavors { wandoujia{} xiaomi{} qq{} }
productFlavors.all { flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name] } }
|
其中name的值对相对应各个productFlavors的选项值,这样就达到自动替换渠道值的目的了。这样生成apk时,选择相应的Flavors来生成指定渠道的包就可以了,生成的apk会自动帮你加上相应渠道的后缀,非常方便和直观。
一次生成所有渠道包
我们可以使用Android Studio中下方底栏的命令行工具Terminal,输入命令:
这样就可以自动打包了,如果渠道数较多的话,将会比较耗时。
带签名的渠道包
需要在build.gradle进行相关签名的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| signingConfigs{ release { storeFile file("keystore路径") storePassword "***" keyAlias "***" keyPassword "***" } }
buildTypes { release { runProguard false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } }
|
修改导出包的apk名称
我们打包有非常多的渠道包,所以我们可以根据渠道自定义apk的名称,方便知道哪个apk对应的是哪个渠道:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| android {
applicationVariants.all { variant ->
variant.outputs.each { output ->
output.outputFile = new File(
output.outputFile.parent,
"xxxx(apk的名字)-${variant.buildType.name}-${defaultConfig.versionName}-${variant.productFlavors[0].name}.apk".toLowerCase())
}
}
}
|
最后打包完成之后,apk文件就会生成在项目的build\outputs\apk下。
使用python脚本打包
问题由来
随着渠道数量越来越多,gradle打包方式就暴露出速度慢、不灵活的特点,那有没有一种高效、快速的方案呢?答案是肯定的,就是广为流传的美团自动化多渠道打包方案。
简单介绍
主要的原理是在META-INF目录内添加空文件的方式,实现批量快速打包Android应用。Android应用安装包apk文件其实是一个压缩文件,可以将后缀修改为zip直接解压。解压安装文件后会发现在根目录有一个META-INF目录。
如果在META-INF目录内添加空文件,可以不用重新签名应用。因此,通过为不同渠道的应用添加不同的空文件,可以唯一标识一个渠道。
下面的python代码用来给apk添加空的渠道文件,渠道名的前缀为mtchannel_:
1 2 3 4
| import zipfile zipped = zipfile.ZipFile(your_apk, 'a', zipfile.ZIP_DEFLATED) empty_channel_file = "META-INF/mtchannel_{channel}".format(channel=your_channel) zipped.write(your_empty_file, empty_channel_file)
|
添加完空渠道文件后的目录,META-INFO目录多了一个名为mtchannel_meituan的空文件:
接下来就可以在Java代码中读取空渠道文件名了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public static String getChannel(Context context) { ApplicationInfo appinfo = context.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("mtchannel")) { ret = entryName; break; } } } catch (IOException e) { e.printStackTrace(); } finally { if (zipfile != null) { try { zipfile.close(); } catch (IOException e) { e.printStackTrace(); } } }
String[] split = ret.split("_"); if (split != null && split.length >= 2) { return ret.substring(split[0].length() + 1);
} else { return ""; } }
|
采用这种方式,每打一个渠道包只需复制一个apk,在META-INF中添加一个使用渠道号命名的空文件即可。所以这种打包方式速度非常快,900多个渠道不到一分钟就能打完。
如何使用
说的这么厉害,怎么来使用呢?
配置Paython环境
我们既然需要使用脚本打包,那么当然要有可以运行python脚本的运行环境。所以我们第一步是要配置python运行环境。 自己去官网下载安装即可。官网地址:https://www.python.org/
编写脚本命令
python脚本网上有很多,使用方法也不尽相同,参考如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| # coding=utf-8
import zipfile import shutil import os import sys import time if __name__ == '__main__': SOURCE_ROOT = '.\\app\\build\\outputs\\apk\\'
startTime = time.time() #调用gradle命令行打release包 os.system('gradlew.bat clean') os.system('gradlew.bat assembleProduceRelease') apkFile = SOURCE_ROOT + 'app-produce-release.apk' apk = 'app-produce-release' # 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') TARGET_ROOT = SOURCE_ROOT + 'release_channel\\' if os.path.exists(TARGET_ROOT): shutil.rmtree(TARGET_ROOT) os.mkdir(TARGET_ROOT) # print lines[0] for line in lines: channel = line destfile = TARGET_ROOT + '%s-%s.apk' % (apk, channel) shutil.copy(apkFile, destfile) zipped = zipfile.ZipFile(destfile, 'a') channelFile = "META-INF\\{channelname}".format(channelname="appchannel_" + channel) zipped.write(emptyFile, channelFile) zipped.close() os.remove('.\\xxx.txt')
# windows os.system('zipalign_batch.bat %s' % TARGET_ROOT) endTime = time.time() print (endTime - startTime)
|
首先脚本会执行clean命令,有时会出现Build failed的错误,原因是./app/build文件夹无法删除,这种情况没有关系,不会影响后续打包的进行。clean完毕我们首先会打一个正常生产环境的安装包,然后使用python完成的渠道包的批量打包,最后的zipalign_batch.bat批处理是进行apk的优化对齐。
关于优化对齐有兴趣的同学可以自行了解,zipalign_batch.bat内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @echo off echo begin running echo %1%
set SIGNED_APK_ROOT=%1% set ZIPALOGNED_APK_ROOT=%1zipaligned% echo %SIGNED_APK_ROOT% echo %ZIPALOGNED_APK_ROOT%
if exist %ZIPALOGNED_APK_ROOT% ( rd /s /q %ZIPALOGNED_APK_ROOT% echo delete root )
md %ZIPALOGNED_APK_ROOT%
for /f "delims=" %%i in ('dir %SIGNED_APK_ROOT%*.apk /b') do ( .\zipalign -v 4 %SIGNED_APK_ROOT%%%i %ZIPALOGNED_APK_ROOT%\%%i )
echo end running
|
注意:需要用到zipalign.exe这个文件,和python脚本以及zipalign_batch.bat批处理文件一同放在项目根目录下即可。
zipalign.exe下载
填写渠道列表
如上面脚本命令里打开的android_channels.txt所示,这个就是渠道列表啦,只需把需要的渠道依次换行添加进去就行啦!就像这样:
注意:android_channels.txt(渠道列表文件)跟上面提到的channel.py(脚本文件)、zipalign.exe(对齐执行文件)、zipalign_batch.bat(对齐批处理文件)都放在项目根目录就好啦!
执行脚本进行打包
同理还是在Studio中下方底栏的命令行工具Terminal中输入命令:
即可,也就是输入你的脚本文件的名称啦!
等待BULID SUCCESSFUL后,在./app/build/outputs/apk/release_channel/下就是打好的渠道包啦,其中zipaligned文件夹下的是zipalign对齐优化后的渠道包,也就是我们要的渠道包啦!
获取渠道包名称
说了这么多,我该如何知道这个包是那个渠道的呢?其实上面已经有啦,就是getChannel()方法,在我们需要使用渠道的地方调用即可。
1 2 3 4
| String channelCode = ChannelUtil.getChannel(); Log.i("Channel", "渠道号:" + channelCode); AnalyticsConfig.setChannel(channelCode);
|
好啦,还没有体验过的小伙伴赶紧使用体验一下吧!
参考文章:
美团Android自动化之旅—生成渠道包
详解高速神器python脚步打包android apk,超级快!!(打包系列教程之六)
Android Studio 使用Gradle多渠道打包
Android产品研发(五)–>多渠道打包