当前位置:   article > 正文

Android进阶之路 - 美团多渠道分包、打包_美团多渠道打包

美团多渠道打包

通过 AndroidStudio 精简多渠道打包方式,我们会发现随着app的体积变大,渠道变多,打包消耗的时间就会越久;所以我也抽了会儿时间,学了下美团的打包方式,差不多十分钟内就可以搞好!

关联篇

因为篇内有些想法来源于here,本应完全转载,但因同时也看了其他多篇文章,遇到了一些不一样的东西,所以有了一些不一样看法,最终为了让自己和他人快速掌握,方便以后使用,所以我写了此篇文章 > < ~

基础认知

如何声明一个渠道?它的本质实现是什么?

一般都是在 AndroidManifest通过meta-data标签声明渠道信息,很多时候涉及的多渠道信息会放在build.gradle中(如:通过原始方式进行多渠道分包、打包)

AndroidManifest 示例

		<meta-data
            android:name="CHANNEL_ID"
            android:value="android" /> 
  • 1
  • 2
  • 3

多渠道是如何实现分渠道的?

一般多渠道有俩种实现方式

  • 动态替换meta-data中的value值;如将上方中value中的android替换为huawei
		<meta-data
            android:name="CHANNEL_ID"
            android:value="huawei" /> 
  • 1
  • 2
  • 3
  • 没有在AndroidManifest中通过meta-data声明任何渠道信息,而是通过三方工具或三方平台打多渠道包时,整体插入meta-data内的渠道信息美团多渠道打包就是如此

美团多渠道打包原理

  1. 直接将apk文件解压缩,然后在META-INF中间中添加以渠道名命名的空文件
  2. 代码中读取该文件名作为渠道名。
    该种方式不需要重新对apk进行签名,操作简单,也可将添加渠道文件的步骤做成Python脚本。实现自动化添加(篇内就是如此操作)

前期准备

因为该篇主要使用的是美团多渠道打包的方式,这种方式主要使用了一款py工具,内置了分包、打包的功能

1. Python开发环境的安装,美团打包工具(地址1或地址2任选其一既可)

2. 带签名的APK包(也就是我们编写好的代码,需要生成一个正式的签名包)

正式签名包打包方式

  • Build - Generate Singed APK
    这里写图片描述

  • 填入秘钥信息,进入这里随便打一个包,先行“存放起来

在以往常规打渠道包的方式图例中有俩个人为问题,需要说明一下

  1. 该图显示的多渠道是因为使用了Umeng的多渠道声明,所以会显示渠道信息,美团多渠道打包方式无需声明meta-data渠道标签
  2. 为了版本兼容,在Signature Versions选V1+V2

图例:以往常规打渠道包的方式

这里写图片描述

图例:使用美团打渠道包时,平常打包即可,主要在后续我们会用到它提供的分包工具

在这里插入图片描述


事前注意:

  1. 系统有32位与64位的区别,同时我们安装的python也有这个区别,所以看好系统,当然现在大多数都是64位操作系统~

  2. 这里打出来的签名包,可以是任意平台也可以是自己单独搞一个通用的签名包

安装流程

  • 勾选 Add Pythpn 3.5 to PATH
    这里写图片描述
  • 打开 Windows 系统自带的cmd,输入 python

成功图

这里写图片描述
失败图

这里写图片描述

如安装失败,请尝试以下解决办法 :

1.重新安装Python环境,记得勾选Add Python 3.5 to PATH

2.如果方法1还是不行,那我们需要去环境变量里面,配置python的环境变量即可,想快速解决环境变量百度即可。


使用流程

1.按照地址1下载好之后,如下图所示

这里写图片描述
2.进行解压,描述如下

这里写图片描述
3.进入meituan\AndroidMultiChannelBuildTool-master\PythonTool

这里写图片描述
4. 渠道名配置 进入PythonTool-info目录下找到channel.txt文件

这里写图片描述
5.根据自己需求配置所需渠道,建议使用直观点的代码编辑工具,当然用txt要记得换行,一个渠道占一行

这里写图片描述

6.导入之前生成的签名包,执行python程序,生成output开头的文件夹

这里写图片描述
7.进入output文件夹,查看我们已生成的所有渠道签名包

这里写图片描述
注: 解压之后在执行python程序!不要在压缩包内一直操作!!!


常见问题&注意事项

先打渠道包还是先加固&加密?

有一天和一位同事讨论apk加固和分渠道包的顺序,为何有的时候能先加固,然后再通过工具分渠道包;而有的时候必须先打出渠道包再进行加固?

正确结果:本质肯定是先打渠道包,然后再进行三方加固;

解析:之所以之前加固后可以改渠道,主要是因为之前方式改META-INF中的文件,在android高版本好像就不行了!(我在这里就看到作者提到了使用爱加密加密过后,再按此种方式进行渠道包的添加,会导致在7.0及以上手机上无法安装)

而加固软件(类似邦邦、乐固)改的是manifest ,这个属于加固加密的文件之一文件加密了就不能改了!

这个和android签名V1、V2有关V1只签名java代码的,V2就包含了代码和资源

签名文件丢失所承担不起的后果

如果签名文件丢失,公司发布应用时只能重新使用新签名文件进行签名,这样意味着我们的app是一款新的app,很多方面的积累又要从0开始,直观影响如下

  1. 用户安装时必须先卸载才能成功安装
  2. 应用市场上的排行就会从0开始了
V1、V2签名渠道包的注意之处

该篇始写于2018年,后期大多仅做格式整理

因为这里的美团多渠道打包只能适用于V1版本的签名包!!!如果打签名包的时候默认同时勾选V1、V2且没有使用多渠道打包,那么在部分平台会有以下错误提示:

这里写图片描述

所以,有一种直接解决错误的方式就是只使用V1打包方式;但治标不治本,总不能因为一个渠道需求,而导致版本兼容出现错误。

所以前不久美图技术点评也实现了V2版本包的功能,想了解的请移步:新一代美团快速打包。集成起来会相对复杂一些,在一些讲究效率和快速开发的团队,正常使用V1签名包和上面的美团打包方式,依旧可以快速高效满足开发需求保证产出以及安全。

三方场景: 同样一个版本号的2个渠道包,比如先装了360,那么SharedPreferences存的就是360了。这时再用xiaomi渠道的去覆盖安装,那么读出来的渠道号还是360的。不知道博主对这个问题怎么看,还是去掉sp缓存,就保留内存和包中读取?

看你个人的使用场景吧 一般来说 渠道使用无非是统计升级渠道、根据相应渠道变化样式、上报崩溃信息携带渠道。

  • 针对1:统计不怎么需要特别情况,你说的这种情况我们也考虑过,但一般用户大多不会这么折腾,也就QA这么玩。

  • 针对2:使用flavor来解决了。而不是判断渠道。早期适配魅族手机也是不管渠道,只看是不是魅族手机

  • 针对3:这个印象也不大,如果是某渠道包崩溃,少这几个也不会影响

    综上,我们选择不再读取。 当然,如果你有其他使用场景,考量之后觉得要精确,可以每次读包,缓存到内存中。当时我是感觉没有太大必要,才写成这样

打包时会遇到的最常见的问题

打包时会遇到的最常见的问题(报 MissingTranslation )

  • 方式一:直接设定 string.xml 文件内的 resourcesattribute
    <?xml version="1.0" encoding="utf-8" ?>  
    <resources xmlns:tools="http://schemas.android.com/tools"  
      tools:ignore="MissingTranslation">  
    </resources> 
    
    • 1
    • 2
    • 3
    • 4
  • 方式二:在string 里加attribute translatable="false":
    <string name="hello_world" translatable="false">你好</string>
    
    • 1
  • 方式三:File-->Setting-->Editor下的Inspections-->Android Lint 下的 Incomplete translation勾选去掉
    (不是很推荐,这是在google上找到的一种方法,试了一下不可以)

该方式优缺点

优点 :打包方式速度非常快,眨眼之间就可打完所有渠道的包

缺点:需要将apk解压缩,如果APK包较大则解压缩所需时间较长

忧虑点(个人感觉短时间不会发生这些情况)

  1. google如果哪天更改打包规则,使得在META-INF中建立空文件还需要重新打包,这种方式将不可用

  2. 一些不法的渠道商很容易通过工具修改渠道,如果一个渠道商,通过网络劫持和篡改渠道的组合方式来获取暴利,对于程序开发者来说可能会存在着巨大的经济损失

  3. apk用的是java那一套签名,放在META-INF文件夹里的文件原则上是不参与签名的。如果Google修改了apk签名规则, 这一套可能就不适用了

  4. 这个方案没法解决不同渠道使用渠道自己SDK的问题,友盟的SDK提供了在代码中设置渠道的方式,所以再获取到渠道号后再调用SDK相关设置渠道的方法就可以了

渠道获取工具

其实本身根据地址1下载好之后,内部有一个javaUtil里面的ChannelUtil就是渠道获取工具 (直接Copy使用即可)

package com.nankang.surveyapp.utils;

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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146

借鉴文章

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/253098
推荐阅读
相关标签
  

闽ICP备14008679号