赞
踩
通过 AndroidStudio 精简多渠道打包方式,我们会发现随着app的体积变大,渠道变多,打包消耗的时间就会越久;所以我也抽了会儿时间,学了下美团的打包方式,差不多十分钟内就可以搞好!
关联篇
因为篇内有些想法来源于here,本应完全转载,但因同时也看了其他多篇文章,遇到了一些不一样的东西,所以有了一些不一样看法,最终为了让自己和他人快速掌握,方便以后使用,所以我写了此篇文章 > < ~
如何声明一个渠道?它的本质实现是什么?
一般都是在 AndroidManifest
通过meta-data
标签声明渠道信息
,很多时候涉及的多渠道信息会放在build.gradle
中(如:通过原始方式进行多渠道分包、打包)
AndroidManifest
示例
<meta-data
android:name="CHANNEL_ID"
android:value="android" />
多渠道是如何实现分渠道的?
一般多渠道有俩种实现方式
meta-data
中的value
值;如将上方中value
中的android
替换为huawei
<meta-data
android:name="CHANNEL_ID"
android:value="huawei" />
AndroidManifest
中通过meta-data
声明任何渠道信息,而是通过三方工具或三方平台打多渠道包时,整体插入meta-data内的渠道信息
(美团多渠道打包就是如此 )美团多渠道打包原理
META-INF
中间中添加以渠道名命名的空文件
。Python
脚本。实现自动化添加(篇内就是如此操作)因为该篇主要使用的是美团多渠道打包的方式,这种方式主要使用了一款py工具,内置了分包、打包的功能
1. Python开发环境的安装,美团打包工具(地址1或地址2任选其一既可)
地址1:借鉴文章内作者的云盘地址
地址2:Github安卓多渠道打包工具
2. 带签名的APK包(也就是我们编写好的代码,需要生成一个正式的签名包)
正式签名包打包方式
Build - Generate Singed APK
填入秘钥信息,进入这里随便打一个包,先行“存放起来”
在以往常规打渠道包的方式图例中有俩个人为问题,需要说明一下
- 该图显示的多渠道是因为使用了Umeng的多渠道声明,所以会显示渠道信息,
美团多渠道打包方式无需声明meta-data渠道标签
- 为了
版本兼容
,在Signature Versions
处选V1+V2
图例:以往常规打渠道包的方式
图例:使用美团打渠道包时,平常打包即可,主要在后续我们会用到它提供的分包工具
事前注意:
系统有32位与64位
的区别,同时我们安装的python
也有这个区别,所以看好系统,当然现在大多数都是64位
操作系统~
这里打出来的签名包,可以是任意平台也可以是自己单独搞一个通用的签名包
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开始,直观影响如下
该篇始写于2018年,后期大多仅做格式整理
因为这里的美团多渠道打包只能适用于V1版本的签名包!!!如果打签名包的时候默认同时勾选V1、V2且没有使用多渠道打包,那么在部分平台会有以下错误提示:
所以,有一种直接解决错误的方式就是只使用V1打包方式
;但治标不治本
,总不能因为一个渠道需求,而导致版本兼容
出现错误。
所以前不久美图技术点评也实现了V2版本
包的功能,想了解的请移步:新一代美团快速打包。集成起来会相对复杂一些,在一些讲究效率和快速开发的团队,正常使用V1签名包和上面的美团打包方式,依旧可以快速高效满足开发需求保证产出以及安全。
三方场景: 同样一个版本号的2个渠道包,比如先装了360,那么
SharedPreferences
存的就是360了。这时再用xiaomi渠道的去覆盖安装
,那么读出来的渠道号还是360的。不知道博主对这个问题怎么看,还是去掉sp缓存,就保留内存和包中读取?
看你个人的使用场景吧 一般来说 渠道使用无非是统计升级渠道、根据相应渠道变化样式、上报崩溃信息携带渠道。
针对1:统计不怎么需要特别情况,你说的这种情况我们也考虑过,但一般用户大多不会这么折腾,也就QA这么玩。
针对2:使用flavor来解决了。而不是判断渠道。早期适配魅族手机也是不管渠道,只看是不是魅族手机
针对3:这个印象也不大,如果是某渠道包崩溃,少这几个也不会影响
综上,我们选择不再读取。 当然,如果你有其他使用场景,考量之后觉得要精确,可以每次读包,缓存到内存中。当时我是感觉没有太大必要,才写成这样
打包时会遇到的最常见的问题(报 MissingTranslation )
string.xml
文件内的 resources
的 attribute
<?xml version="1.0" encoding="utf-8" ?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:ignore="MissingTranslation">
</resources>
string
里加attribute translatable="false"
:<string name="hello_world" translatable="false">你好</string>
File-->Setting-->Editor下的Inspections-->Android Lint
下的 Incomplete translation
勾选去掉优点 :打包方式速度非常快,眨眼之间就可打完所有渠道的包
缺点:需要将apk解压缩,如果APK包较大则解压缩所需时间较长
忧虑点(个人感觉短时间不会发生这些情况)
google
如果哪天更改打包规则,使得在META-INF
中建立空文件还需要重新打包,这种方式将不可用
一些不法的渠道商很容易通过工具修改渠道,如果一个渠道商,通过网络劫持和篡改渠道
的组合方式来获取暴利,对于程序开发者来说可能会存在着巨大的经济损失
apk用的是java那一套签名,放在META-INF
文件夹里的文件原则上是不参与签名的。如果Google修改了apk
的签名规则
, 这一套可能就不适用了
这个方案没法解决不同渠道使用渠道自己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; } }
借鉴文章
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。