今天寫一篇文章來總結下android批量打渠道包美團版本。之前項目上一直用的是gradle 批量打包方式,那個速度啊真是令人發指,15個渠道得跑上半個小時,出去吃頓飯回來,還在跑。特別是趕上項目上線的話,如果給測試提交了正式版本1.0,突然發現有小bug要修復,修復后又得重新批量打包半個小時。。。。無語啦。。。真佩服以前的耐心。。。。好了今天來看下打包方案 - 美團多渠道打包方案。至于為撒是美團,應該是這個方案是美團的哪一位大神放出來的吧。
為什么要打渠道包
為什么要渠道打包,一個包不是挺好的嗎,一個包也可以發布到各個應用市場嘛?以前剛入門時候也是傻乎乎的這么想的。如果現在你老板提出這樣需求場景:亮仔呀,我想知道我們的APP在哪個應用市場渠道下載的最多,我們以后就重點推廣這個渠道,用錢砸到排名前面!!! 亮仔傻眼了!!—— 所以不同渠道打包主要用來做統計分析,特別是游戲應用,特別注意哪個渠道推廣的最有效。
下面是友盟平臺,APP統計各個渠道的分析圖:
從統計圖可以看出APP在各個渠道的用戶、活躍度、啟動次數、活動時長....還是比較詳細的,對運營團隊有著很大的指導作用哈。
怎么打渠道包
怎么渠道打包呢?亮仔就開始想了:這還不簡單,三步搞定:
1.我在Adminifest.xml文件里面配一個meta-data值,這個值寫死成某個渠道;
2.在用戶安裝了我們的APP后,我獲取這個寫死的渠道值然后上傳到后臺;
3.我挨個修改meta-data值,改成各個渠道然后編譯打包10分鐘搞定;
很快亮仔就開始上手,很快實現了老板提出的需求!!! 好景不長... 老板:亮仔啊,我們這個應用下載量不給力啊,推廣不夠啊,這樣我們把我們APP發到市場上所有渠道上,廣撒網捕魚嘛,也不多就100多個渠道吧!!! 亮仔慌了:我擦,我打一個包需要2分鐘,100個包 3個多小時啊 這一天光打包了.....
所以基于上面的場景,我們發現主要有兩個問題:
- 打包的本質是將渠道標識傳遞給后臺
這一步已經有第三方平臺幫我們做了,實現的思路應該也也差不多,我們集成友盟的渠道統計分析即可。沒有必要寫一套自己的渠道統計分析。友盟渠道統計接入傳送門>>
- 怎樣快速打包
為什么打包會花那么長時間?因為每個渠道打一次包,就要重新編譯一次,所以耗時長。其實只要想辦法將打好的一個包,替換里面的meta-data值即可。我們來看下美團多渠道打包是怎么做的:
1.首先你需要安裝python環境
不要被python環境搭建嚇到,其實就跟安裝一個普通的exe軟件差不多,下一步。。安裝后不需要配置什么環境變量之類。 [python下載傳送門 >>](https://www.python.org/downloads/)
2.項目中接入友盟統計
-
2.1 申請友盟的賬號
-
在Adminifest.xml中配置友盟ID和渠道標識
<!-- 友盟API Key -->
<meta-data
android:name="UMENG_APPKEY"
android:value="***************">
</meta-data>
<!--umeng 渠道 -->
<meta-data
android:name="UMENG_CHANNEL"
android:value="baidu" />
-
2.2 應用啟動時上傳渠道標識給友盟
ChannelUtil 是封裝的一個獲取渠道標識的工具類
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
String channel=ChannelUtil.getChannel(this, "default channel");//獲取渠道名
AnalyticsConfig.setChannel(channel);//調用umeng api設置umeng渠道
}
}
ChaneUtil工具類直接偷來的~~
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;
}
}
3.運行打包腳本打包
-
3.1 下載打包腳本
下載解壓 -
3.2 配置渠道
-
3.3 拷貝apk包到PythonTool目錄下(與py同級)
-
3.4 運行py腳本 MultiChannelBuildTool.py即可打包完成。
(生成的渠道apk包在output_** 目錄下)
打包完成
ok,寫完啦!剛開始練習寫總結文章,有講的不清楚的,歡迎指正!!
最新打包方案,還沒有嘗試過,有興趣的可以看看(傳送門)[https://github.com/mcxiaoke/packer-ng-plugin]