一.原理
把一個Android應用包當作zip文件包進行解壓,然後發現在簽名生成的目錄下(META-INF)添加一個空文件不需要重新簽名。利用這個機制,該文件的文件名就是渠道名。這種方式不需要重新簽名等步驟,非常高效。
二.方法
已經將美團的打包工具放到了tools下的test01文件中:
1、將要打包的apk放到PythonTool中
2、在PythonTool/info/channel.txt中寫入需要的渠道,一個渠道佔一行
3、雙擊執行PythonTool/MultiChannelBuildTool.py文件(需要Python環境),就會生成渠道包
4、獲取渠道信息:將JavaUtil文件中的ChannelUtil.java拷貝到工程,調用ChannelUtil.getChannel即可獲取渠道
package com.czt.util;
import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.preference.PreferenceManager;
import android.text.TextUtils;
public class ChannelUtil {
private static final String CHANNEL_KEY = "cztchannel";
private static final String CHANNEL_VERSION_KEY = "cztchannel_version";
private static String mChannel;
/**
* 返回市場。 如果獲取失敗返回""
* @param context
* @return
*/
public static String getChannel(Context context){
return getChannel(context, "");
}
/**
* 返回市場。 如果獲取失敗返回defaultChannel
* @param context
* @param defaultChannel
* @return
*/
public static String getChannel(Context context, String defaultChannel) {
//內存中獲取
if(!TextUtils.isEmpty(mChannel)){
return mChannel;
}
//sp中獲取
mChannel = getChannelBySharedPreferences(context);
if(!TextUtils.isEmpty(mChannel)){
return mChannel;
}
//從apk中獲取
mChannel = getChannelFromApk(context, CHANNEL_KEY);
if(!TextUtils.isEmpty(mChannel)){
//保存sp中備用
saveChannelBySharedPreferences(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();
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[] 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 saveChannelBySharedPreferences(Context context, String channel){
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
Editor editor = sp.edit();
editor.putString(CHANNEL_KEY, channel);
editor.putInt(CHANNEL_VERSION_KEY, getVersionCode(context));
editor.commit();
}
/**
* 從sp中獲取channel
* @param context
* @return 爲空表示獲取異常、sp中的值已經失效、sp中沒有此值
*/
private static String getChannelBySharedPreferences(Context context){
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
int currentVersionCode = getVersionCode(context);
if(currentVersionCode == -1){
//獲取錯誤
return "";
}
int versionCodeSaved = sp.getInt(CHANNEL_VERSION_KEY, -1);
if(versionCodeSaved == -1){
//本地沒有存儲的channel對應的版本號
//第一次使用 或者 原先存儲版本號異常
return "";
}
if(currentVersionCode != versionCodeSaved){
return "";
}
return sp.getString(CHANNEL_KEY, "");
}
/**
* 從包信息中獲取版本號
* @param context
* @return
*/
private static int getVersionCode(Context context){
try{
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
}catch(NameNotFoundException e) {
e.printStackTrace();
}
return -1;
}
}
工具下載地址:http://pan.baidu.com/share/link?shareid=1485267923&uk=4218015263#list/path=%2F
三.優缺點
優點:
這種打包方式速度非常快,900多個渠道不到一分鐘就能打完
缺點:
1、google如果哪天更改打包規則,使得在META-INF中建立空文件還需要重新打包,這種方式將不可用
2、一些不法的渠道商很容易通過工具修改渠道,如果一個渠道商,通過網絡劫持和篡改渠道的組合方式來獲取暴利,對於程序開發者來說可能會存在着巨大的經濟損失