Android 多渠道打包

我想,每当发布新版本时,大家都会对那么多发布渠道感到头疼不已,本文主要介绍三种Android的多渠道打包方式。下面的例子我以 TalkingData 统计为例子。

1. Gradle 配置 Flavor

1.1 在 AndroidManifest.xml 中配置 meta-data 标签:

 
   
   

1.2 在项目的 build.gradle 里配置 productFlavors,定义渠道号:
productFlavors {
  yyb {}  
  bdsjzs {}  
  wdj {}  
  xiaomi {}  
  sjzs360 {} 
  productFlavors.all {  
      flavor -> flavor.manifestPlaceholders = [TD_CHANNEL_VALUE: name] 
   }
}

上面定义了一些渠道(比如:豌豆荚,360,小米等),TD_CHANNEL_VALUE 就是你在 meta-data 标签中配置的值。
同时,需要注意的是,你需要在 defaultConfig 中配置一个默认的渠道名称:
manifestPlaceholders = [TD_CHANNEL_VALUE: "default_channel_name"]

该原理就是在打包过程中更换 meta-data 标签中的内容。

优势:方便理货,可以根据自身的需求配置不同的渠道执行不同的逻辑(比如:小米渠道有一个独立的方法,其他渠道没有);
劣势:打包速度慢(打包时是对每个渠道都进行一次完整的打包过程)。

2.第三方打包工具

第三方的打包工具原理跟 Gradle 一样,也是去修改 AndroidManifest.xml 中的 meta-data 标签的内容,然后执行N次打包签名的操作实现多渠道打包的。

优势:简单方便,几乎不用自身做什么工作;
劣势:打包速度过慢;

3.美团的多渠道打包方案

多渠道打包原理可以参考美团Android自动化之旅—生成渠道包这篇文章。Demo 可以查看 快速多渠道打包。

3.1 实现原理

直接解压apk,解压后的根目录会有一个META-INF
目录,如下图所示:


Android 多渠道打包_第1张图片

如果在META-INF目录内添加空文件,可以不用重新签名应用。因此,通过为不同渠道的应用添加不同的空文件,可以唯一标识一个渠道。

这样,每打一个渠道包只需复制一个apk,在 META-INF 中添加一个使用渠道号命名的空文件即可。这种打包方式速度非常快,900多个渠道不到一分钟就能打完。

2.2 多渠道实现步骤

2.2.1 android 代码中获取渠道号
/**
 * 渠道号工具类:解析压缩包,从中获取渠道号
 */
public class ChannelUtil {
 private static final String CHANNEL_KEY = "uuchannel";
 private static final String DEFAULT_CHANNEL = "internal";
 private static String mChannel;

 public static String getChannel(Context context) {
     return getChannel(context, DEFAULT_CHANNEL);
 }

 public static String getChannel(Context context, String defaultChannel) {
     if (!TextUtils.isEmpty(mChannel)) {
         return mChannel;
     }
     //从apk中获取
     mChannel = getChannelFromApk(context, CHANNEL_KEY);
     if (!TextUtils.isEmpty(mChannel)) {
        return mChannel;
     } //全部获取失败
   return defaultChannel;
 }
/**
 * 从apk中获取版本信息
 *
 * @param context
 * @param channelKey
 * @return
 */
 private static String getChannelFromApk(Context context, String channelKey) {
 long startTime = System.currentTimeMillis();
 //从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();
         if (entryName.startsWith(key)) {
             ret = entryName; break;
         }
      }
 } catch (IOException e) {
       e.printStackTrace();
 } finally {
       if (zipfile != null) {
           try {
                 zipfile.close();
           } catch (IOException e) {
                 e.printStackTrace();
           }
       }
}
String channel = ""; 
if (!TextUtils.isEmpty(ret)) {
     String[] split = ret.split("_");
     if (split != null && split.length >= 2) {
         channel = ret.substring(split[0].length() + 1);
      }
    System.out.println("-----------------------------");
    System.out.println("渠道号:" + channel + ",解压获取渠道号耗时:" + (System.currentTimeMillis() - startTime) + "ms"); 
    System.out.println("-----------------------------");
 } else { 
    System.out.println("未解析到相应的渠道号,使用默认内部渠道号");
    channel = DEFAULT_CHANNEL;
 } 
return channel;
}

然后在代码中初始化调用TalkingData设置渠道号:

String channelName = ChannelUtil.getChannel(mContext);
TCAgent.init(mContext,Constants.APP_KEY_TD,channelName );
2.2.2 编写渠道号文件

修改 info/channel.txt(一行代表一个渠道):

yyb
bdsjzs
wdj
xiaomi
...
2.2.3 编写 python 脚本

实现解压 apk 文件,遍历渠道文件 为 META-INF 目录添加新文件,重新压缩 apk 文件等逻辑:

# coding=utf-8
import zipfile
import shutil
import os

def delete_file_folder(src):
 '''delete files and folders'''
 if os.path.isfile(src):
   try:
     os.remove(src)
   except:
     pass 
elif os.path.isdir(src):
   for item in os.listdir(src):
     itemsrc=os.path.join(src,item)
     delete_file_folder(itemsrc)
   try:
     os.rmdir(src)
   except: 
      pass

# 创建一个空文件,此文件作为apk包中的空文件 
 src_empty_file = 'info/empty.txt'
 f = open(src_empty_file,'w')
 f.close()

# 在渠道号配置文件中,获取指定的渠道号
 channelFile = open('./info/channel.txt','r')
 channels = channelFile.readlines()
 channelFile.close()
 print('-'*20,'all channels','-'*20)
 print(channels)print('-'*50)

# 获取当前目录下所有的apk文件
 src_apks = [];
 for file in os.listdir('.'):
   if os.path.isfile(file):
     extension = os.path.splitext(file)[1][1:]
       if extension in 'apk': 
          src_apks.append(file)

# 遍历所以的apk文件,向其压缩文件中添加渠道号文件
for src_apk in src_apks:
 src_apk_file_name = os.path.basename(src_apk)
 print('current apk name:',src_apk_file_name)
 temp_list = os.path.splitext(src_apk_file_name)
 src_apk_name = temp_list[0]
 src_apk_extension = temp_list[1]
 apk_names = src_apk_name.split('-');
 output_dir = 'outputDir'+'/'
 if os.path.exists(output_dir):
   delete_file_folder(output_dir)
 if not os.path.exists(output_dir):
   os.mkdir(output_dir)

 # 遍历从文件中获得的所以渠道号,将其写入APK包中
 for line in channels:
   target_channel = line.strip()
   target_apk = output_dir + apk_names[0] + "-" + target_channel+"-"+apk_names[2] + src_apk_extension
   shutil.copy(src_apk, target_apk)
   zipped = zipfile.ZipFile(target_apk, 'a', zipfile.ZIP_DEFLATED)
   empty_channel_file = "META-INF/uuchannel_{channel}".format(channel = target_channel)
   zipped.write(src_empty_file, empty_channel_file)
   zipped.close()

print('-'*50)
print('repackaging is over ,total package: ',len(channels))
input('\npackage over...')
2.2.4 打包一个签名 APK 包

将APK 包放在 python 脚本的同级目录

2.2.5 执行python 脚步,多渠道打包
python Android.py

最后就会在 同级目录下生成 outputDir 目录,里面就是各个渠道的 APK 。

优势:打包速度很快,很方便;
劣势:不够灵活,不能灵活的配置不同的渠道不同的业务逻辑;

总结
三种打包方式中,各有各的优势,大家在选择多渠道打包方式的时候可以根据自身的需求来选择

你可能感兴趣的:(Android 多渠道打包)