美团多渠道打包方案小记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yrmao9893/article/details/71188968

我们先说说什么情况会用到多渠道打包,一般是用于友盟不同渠道的统计分析。

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

设置渠道方式有两种

   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外出办事了,不然要崩溃了)这越发使得我,想要加快的想要学会快速多渠道打包,今天终于把美团的打包方案玩明白了。

扫描二维码关注公众号,回复: 3591830 查看本文章


第二种:

在代码里配置

 下面先说说美团多渠道打包的原理

  • 由于传统的打包方式每次修改渠道都需要重新的构建项目,时间都浪费构建上面了,美团提供了一种新的打包方案,将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里了那么到了这里我们就大功告成了

猜你喜欢

转载自blog.csdn.net/yrmao9893/article/details/71188968