美團多渠道打包方案小記

我們先說說什麼情況會用到多渠道打包,一般是用於友盟不同渠道的統計分析。

要使用友盟渠道統計分析就要先設置,當前的渠道包屬於哪個渠道。

設置渠道方式有兩種

   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裏了那麼到了這裏我們就大功告成了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章