您的位置:首页 > 其它

美团多渠道打包方案小记

2017-05-04 21:17 169 查看
我们先说说什么情况会用到多渠道打包,一般是用于友盟不同渠道的统计分析。

要使用友盟渠道统计分析就要先设置,当前的渠道包属于哪个渠道。

设置渠道方式有两种

   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()) {

4000
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里了那么到了这里我们就大功告成了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息