我们先说说什么情况会用到多渠道打包,一般是用于友盟不同渠道的统计分析。
要使用友盟渠道统计分析就要先设置,当前的渠道包属于哪个渠道。
设置渠道方式有两种
1.在AndroidManifest.xml配置
2.在代码里配置
第一种
//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外出办事了,不然要崩溃了)这越发使得我,想要加快的想要学会快速多渠道打包,今天终于把美团的打包方案玩明白了。
第二种:
在代码里配置
下面先说说美团多渠道打包的原理
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里了那么到了这里我们就大功告成了