我們先說說什麼情況會用到多渠道打包,一般是用於友盟不同渠道的統計分析。
要使用友盟渠道統計分析就要先設置,當前的渠道包屬於哪個渠道。
設置渠道方式有兩種
1.在AndroidManifest.xml配置
2.在代碼裏配置
第一種
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" />//UMENG_CHANNEL_VALUE表示在gradle設置的渠道
gradle代碼
// productFlavors {
// _360 {
// manifestPlaceholders = [UMENG_CHANNEL_VALUE: "_360"]
// }
// Online { // 官網
// manifestPlaceholders = [UMENG_CHANNEL_VALUE: "release"]
// }
//
// fir {
// manifestPlaceholders = [UMENG_CHANNEL_VALUE: "fir"]
// }
//
// xiaomi {
// manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
// }
//
// qq {
// manifestPlaceholders = [UMENG_CHANNEL_VALUE: "qq"]
// }
// baidu {
// manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
// }
// }
當我選擇打_360這個渠道包時,${UMENG_CHANNEL_VALUE}的值就爲_360
這樣意味着每打一個渠道就要重新編譯一下,然後就有了下文
剛開始我是對快速打包這塊不太care,因爲本身項目不大,編譯起來速度不算太慢,可項目越來越龐大,編譯的時間越來越長,剛開始30分鐘左右的時長還能接受,可是到前兩天,下午兩點開始打包,直到下午6點多才把全部渠道的包打完(我的天哪,幸好下午和CTO外出辦事了,不然要崩潰了)這越發使得我,想要加快的想要學會快速多渠道打包,今天終於把美團的打包方案玩明白了。
第二種:
在代碼裏配置
下面先說說美團多渠道打包的原理
- 由於傳統的打包方式每次修改渠道都需要重新的構建項目,時間都浪費構建上面了,美團提供了一種新的打包方案,將APK直接當做zip解壓目錄裏會有一個META-INF目錄而此目錄是不參與簽名校驗的。因此在META-INF目錄內添加不同的空文件,可以唯一標識一個渠道。採用這種方式,每打一個渠道包只需複製一個apk,在META-INF中添加一個使用渠道號命名的空文件即可。
1.第一步將渠道名寫入META-INF
2.第二步將META-INF文件中的渠道名取出,配置到友盟
實行步驟:
1.先配置python環境
2.編寫腳本渠道名寫入META-INF
3.運行腳本,打出不同渠道的apk包
4.在Java代碼中取出當前apk的渠道
OK到這裏工作就完成了,這時候你可能在想腳本怎麼編寫,哈哈不用你寫,早已有大神開源出來了,你只需要會用就行了
去下載下來就可以了,https://github.com/GavinCT/AndroidMultiChannelBuildTool
來我們親手操作一下
先新建一個項目:
Mainactivity代碼
public class MainActivity extends AppCompatActivity {
private TextView mTvChannal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvChannal = (TextView) findViewById(R.id.tv_channel);
String channal = ChannelUtil.getChannel(this);
mTvChannal.setText(channal);
}
@Override
protected void onResume() {
super.onResume();
// MobclickAgent.onResume(this);
}
@Override
protected void onPause() {
super.onPause();
// MobclickAgent.onPause(this);
}
}
ChannelUtil這個工具類是用於取出META-INF文件裏的渠道名
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;
}
}
運行結果:
可以看到渠道名爲空,是因爲我們還沒有運行腳本,將渠道名寫入META-INF文件
下面我們將沒有渠道的apk包複製到腳本文件的同級目錄,這裏我爲了方便用的是debug包
我是用mac電腦,Windows也是一樣的道理,先看截圖
info文件夾裏有兩個文件夾
channel.txt設置渠道名稱
現在我們運行腳本,在終端進入到腳本文件的所在目錄,在運行MuItiChannelBuildTool.py這個腳本
圈出來的文件夾裏面裏面就是,將渠道寫入META-INF文件的渠道包
現在我們運行某一個渠道包app-debug-91com.apk
可以看出渠道名已經被寫入apk裏了那麼到了這裏我們就大功告成了