关于多渠道打包的最强攻略--总结版

2024-06-01 07:58

本文主要是介绍关于多渠道打包的最强攻略--总结版,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

作开发工程师发布产品时多渠道打包是个必要的过程,此文可以对产品打包及上线不太熟悉的人提供了解及建议:

原始多渠道打包

原始多渠道打包的方式,指的是每次打包的时候在代码中设置channelId,打包完这个渠道的apk包后,需要重新设置channelId再进行打包,如此反复。该方式多出现在android早期的时候,多被一些刚入行的android工程师使用,或者是一些公司面对较少渠道的时候使用。

原理

原始多渠道打包就是个体力活,在较少渠道的时候可以使用,但是面对上千的渠道的时候,使用这种方式你会后悔当一名android开发工程师。它的原理是在应用代码中设置渠道ID,使用的时候将渠道ID设置给数据分析接口,数据分析平台通过该渠道ID分析之。其实后面多渠道方式的本质原理都是这样的,但是具体扩展方式不同而已,将在后面的分析的时候介绍。

实现

  • 第一步:设置渠道id

方式一 在代码中直接设置channelId

String channelId="channel1";
  • 1
  • 1

方式二 在AndroidMainfest.xml中application中设置meta-data

<manifest ...><application ...><meta-data
            android:name="CHANNEL_NAME"android:value="channel1" />...</application></manifest>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在代码中获取channelId

ApplicationInfo appInfo = this.getPackageManager().getApplicationInfo(getPackageName(),PackageManager.GET_META_DATA);
String channelId = appInfo.metaData.getString("CHANNEL_NAME");
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
  • 第二步:集成到sdk中,比如友盟sdk
MobclickAgent. startWithConfigure(UMAnalyticsConfig config)
UMAnalyticsConfig(Context context, String appkey, String channelId)
UMAnalyticsConfig(Context context, String appkey, String channelId, EScenarioType eType)
UMAnalyticsConfig(Context context, String appkey, String channelId, EScenarioType eType,Boolean isCrashEnable)
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

优缺点

在渠道较少(个位数)的时候可以使用,但对于多渠道的时候太耗时耗力了。

友盟多渠道打包

该方法是友盟几年前公布的多渠道打包方式,并且在github开源了打包工具,友盟多渠道打包方式经历了多次迭代,主要有两种方式,一种是通过反编译apk修改渠道信息,另一种是通过AXML解析器编辑修改渠道信息。

原理

  • 第一种方法: 
    通过ApkTool进行解包,然后修改AndroidManifest中修改渠道标示,最后再通过ApkTool进行打包、签名。

  • 第二种方法: 
    使用AXML解析器axmleditor.jar,拥有很弱的编辑功能,工程中用来编辑二进制格式的 AndroidManifest.xml 文件.

实现

  • 第一步 apktool解包apk

apktool是一个逆向工程工具,可以用它解码(decode)并修改apk中的资源。接下来详细介绍如何使用apktool生成渠道包。

在Android多渠道打包(一)介绍过,同样需要在AndroidManifest.xml文件中定义元素,并在应用启动的时候读取清单文件中的渠道号。打包时,只需构建一次生成一个apk,然后在该apk的基础上生成其他渠道包即可。

首先,使用apktool decode应用程序,在终端中输入如下命令:

apktool d your_unsigned.apk build

解包后生成如下图片的文件 

  • 第二步 使用python脚本修改AndroidManifest.xml中的渠道号

AndroidManifest.xml文件内容

<manifest ...><application ...><meta-data
            android:name="CHANNEL_NAME"android:value="channel" />...</application></manifest>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Python脚本

import redef replace_channel(channel, manifest):pattern = r'(<meta-data\s+android:name="CHANNEL_NAME"\s+android:value=")(\S+)("\s+/>)'replacement = r"\g<1>channel\g<3>".format(channel=channel1)return re.sub(pattern, replacement, manifest)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

或者使用AXML解析器直接编辑修改AndroidManifest.xml中的渠道号

  • 第三步 使用apktool重新构建未签名的apk

apktool b build your_unsigned_apk

  • 第四步 使用jarsigner重新签名apk

jarsigner -sigalg MD5withRSA -digestalg SHA1 -keystore your_keystore_path -storepass your_storepass -signedjar your_signed_apk, your_unsigned_apk, your_alias

  • 另在代码中集成,比如友盟sdk
MobclickAgent. startWithConfigure(UMAnalyticsConfig config)
UMAnalyticsConfig(Context context, String appkey, String channelId)
UMAnalyticsConfig(Context context, String appkey, String channelId, EScenarioType eType)
UMAnalyticsConfig(Context context, String appkey, String channelId, EScenarioType eType,Boolean isCrashEnable)
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

官方说明

  • 最近更新

友盟本次更新最大的改变是放弃了 V2.x 版本中通过 Apktool 反编译apk文件打包的方式,这种打包方式会对开发的apk文件做出大幅度的修改,可能会产生许多不兼容的问题,比如对jar包中包含资源的情况无法支持,对包含 .so 文件的apk兼容性也不好,而且在打包时 AndroidManifest.xml 文件中的特殊标签会丢失。为了解决这些问题减少对开发者apk文件的修改, 我们决定放弃这种方式,而采用直接编辑二进制的AndroidManifest.xml 文件的方式。这种方式只会修改 AndroidManifest.xml 文件,对于apk包中的资源文件和代码文件都不会做任何改变。如果打包不成功,生成的apk文件有问题,在测试阶段也可以快速发现,因为修改只会影响AndroidManifest.xml 相关的少量的设置。

  • 工具使用

axmleditor.jar 一个AXML解析器,拥有很弱的编辑功能,工程中用来编辑二进制格式的 AndroidManifest.xml 文件. 
JarSigner.jar 给 Apk 签名, SignApk.jar 文件是我们修改过的 apk 签名工具,实现了和 ADT 中一样的签名方式. 
这些Java工具都是使用java7编译的,如果您还在使用Java 1.6 请留下issue。 
DotNetZip 解压缩和压缩文件使用的是DotNetZip(Ionic.Zip.dll), 运行源码需要加入这个库.

优缺点

对比之前的老方法大大节省了构建时间,因为该方法只需构建一次,然后通过脚本修改渠道并签名就可。 
但是对于三位数以上的渠道还是有点力不从心,另外该方法需要解压缩、压缩、重签名耗费时间较多,重签名可能会导致apk包在运行时有兼容性问题。

引用

友盟github

maven&gradle打包

原理

都是采用在AndroidManifest.xml的节点中添加如下元素,构建时替换value值得方式。

实现

  • Maven

Maven是一个软件项目管理和自动构建工具,配合使用Android-maven-plugin插件,以及maven-resources-plugin插件可以很方便的生成渠道包,下面简要介绍下打包过程,更多Maven以及插件的使用方法请参考相关文档。

首先,在AndroidManifest.xml的节点中添加如下元素,用来定义渠道的来源:

<!-- 使用Maven打包时会用具体的渠道号替换掉${channel} --><meta-data
        android:name="channel"android:value="${channel}" />
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

定义好渠道来源后,接下来就可以在程序启动时读取渠道号了:

private String getChannel(Context context) {try {PackageManager pm = context.getPackageManager();ApplicationInfo appInfo = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);return appInfo.metaData.getString("channel");} catch (PackageManager.NameNotFoundException ignored) {}return "";}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

要替换AndroidManifest.xml文件定义的渠道号,还需要在pom.xml文件中配置Resources插件:

<resources><resource><directory>${project.basedir}</directory><filtering>true</filtering><targetPath>${project.build.directory}/filtered-manifest</targetPath><includes><include>AndroidManifest.xml</include></includes></resource></resources>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

准备工作已经完成,现在需要的就是实际的渠道号了。下面的脚本会遍历渠道列表,逐个替换并打包:

#!/bin/bash
package(){while read linedomvn cleanmvn  -Dchannel=$line packagedone < $1
}package $1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在前期渠道很少时这种方法还可以接受,但只要渠道稍微增多该方法就不再适用了,原因是每打一个包都要执行一遍构建过程,效率太低。

  • gradle

以友盟的渠道统计为例,渠道信息一般在 AndroidManifest.xml中修改以下值:

<meta-dataandroid:name="UMENG_CHANNEL"android:value="wandoujia" />
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

首先你必须在AndroidManifest.xml中的meta-data修改以下的样子:

<meta-dataandroid:name="UMENG_CHANNEL"android:value="${UMENG_CHANNEL_VALUE}" />
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

其中${UMENG_CHANNEL_VALUE}中的值就是你在gradle中自定义配置的值。 
build.gradle文件就利用productFlavors这样写

productFlavors { wandoujia {manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]}baidu {manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]} c360 {manifestPlaceholders = [UMENG_CHANNEL_VALUE: "c360"]} uc {manifestPlaceholders = [UMENG_CHANNEL_VALUE: "uc"]} 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

其中[UMENG_CHANNEL_VALUE: "wandoujia"]就是对应${UMENG_CHANNEL_VALUE}的值。

不过现在有个更加简洁的写法

productFlavors {wandoujia {...}//支持在{}定义属性baidu {...}c360 {...}uc {...}productFlavors.all { flavor ->flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在android studio中sync gradle在build下可以看到

 
直接在gradle中点击assemble可构建所有渠道的包 
单独点击对应渠道的assemble 比如assembleC360可以单独构建出C360渠道的包 
代码中获取渠道值如下代码

private String getChannel(Context context) {try {PackageManager pm = context.getPackageManager();ApplicationInfo appInfo = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);return appInfo.metaData.getString("channel");} catch (PackageManager.NameNotFoundException ignored) {}return "";}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

优缺点

maven&gradle对于每个渠道都会单独构建一次,比较耗时,但是可以对各个渠道更加细化的定制


样例参考:

    
Gradle多渠道打包
由于国内Android市场众多渠道,为了统计每个渠道的下载及其它数据统计,就需要我们针对每个渠道单独打包,如果让你打几十个市场的包岂不烦死了,不过有了Gradle,这再也不是事了。 以友盟统计为例,在AndroidManifest.xml里面会有这么一段:
<meta-data
android:name="UMENG_CHANNEL"
android:value="Channel_ID" />
里面的Channel_ID就是渠道标示。我们的目标就是在编译的时候这个值能够自动变化。 * 第一步 AndroidManifest.xml里配置PlaceHolder
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" />
第二步 build.gradle 设置productFlavors
android {
productFlavors {
xiaomi {}
_360 {}
baidu {}
wandoujia {}
}
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
}
然后直接执行 ./gradlew assembleRelease
然后就等待打包完成吧。
assemble 这个命令,会结合 Build Type 创建自己的task,如:
./gradlew assembleDebug
./gradlew assembleRelease
    
常用命令如下:(linux下是./gradlew,该脚本在项目下,windows直接gradlew即可)
 
./gradlew -v 版本号,首次运行,没有gradle的要下载的哦。
 
./gradlew clean 删除HelloWord/app目录下的build文件夹
 
./gradlew build 检查依赖并编译打包
这里注意的是 ./gradlew build 命令把debugrelease环境的包都打出来,生成的包在目录HelloWord/app/build/outputs/apk/下。如果正式发布只需要打release的包,该怎么办呢,下面介绍一个很有用的命令 assemble,
 
./gradlew assembleDebug 编译并打Debug
 
./gradlew assemblexiaomiDebug 编译并打xiaomidebug包,其他类似
 
./gradlew assembleRelease 编译并打Release的包
 
./gradlew assemblexiaomiRelease 编译并打xiaomiRelease包,其他类似
 
./gradlew installRelease Release模式打包并安装
 
./gradlew uninstallRelease 卸载Release模式包
http://www.jianshu.com/p/44d40f8e67c9 git自动获取包名打包

360多渠道打包

来源

这个打包方法是由奇虎360的工程师开源出来的,这位大神在github的id是seven456

原理

利用的是Zip文件“可以添加comment(摘要)”的数据结构特点,在文件的末尾写入任意数据,而不用重新解压zip文件(apk文件就是zip文件格式);所以该工具不需要对apk文件解压缩和重新签名即可完成多渠道自动打包,高效速度快,无兼容性问题;

实现方式

  • java源码
/*关键代码*/public static void main(String[] args) throws Exception {
//      写入渠道号//      args = "-path D:/111.apk -outdir D:/111/ -contents googleplay;m360; -password 12345678".split(" ");//      查看工具程序版本号//      args = "-version".split(" ");//      读取渠道号//      args = "-path D:/111_m360.apk -password 12345678".split(" ");long time = System.currentTimeMillis();String cmdPath  = "-path";String cmdOutdir  = "-outdir";String cmdContents  = "-contents";String cmdPassword  = "-password";String cmdVersion  = "-version";String help = "用法:java -jar MCPTool.jar [" + cmdPath + "] [arg0] [" + cmdOutdir + "] [arg1] [" + cmdContents + "] [arg2] [" + cmdPassword + "] [arg3]"+ "\n" + cmdPath + "        APK文件路径"+ "\n" + cmdOutdir + "      输出路径(可选),默认输出到APK文件同一级目录"+ "\n" + cmdContents + "    写入内容集合,多个内容之间用“;”分割(linux平台请在“;”前加“\\”转义符),如:googleplay;m360; 当没有" + cmdContents + "”参数时输出已有文件中的contents"+ "\n" + cmdPassword + "    加密密钥(可选),长度8位以上,如果没有该参数,不加密"+ "\n" + cmdVersion + " 显示MCPTool版本号"+ "\n例如:"+ "\n写入:java -jar MCPTool.jar -path D:/test.apk -outdir ./ -contents googleplay;m360; -password 12345678"+ "\n读取:java -jar MCPTool.jar -path D:/test.apk -password 12345678";if (args.length == 0 || args[0] == null || args[0].trim().length() == 0) {System.out.println(help);} else {if (args.length > 0) {if (args.length == 1 && cmdVersion.equals(args[0])) {System.out.println("version: " + VERSION_1_1);} else {Map<String, String> argsMap = new LinkedHashMap<String, String>();for (int i = 0; i < args.length; i += 2) {if (i + 1 < args.length) {if (args[i + 1].startsWith("-")) {throw new IllegalStateException("args is error, help: \n" + help);} else {argsMap.put(args[i], args[i + 1]);}}}System.out.println("argsMap = " + argsMap);File path = argsMap.containsKey(cmdPath) ? new File(argsMap.get(cmdPath)) : null;String parent = path == null? null : (path.getParent() == null ? "./" : path.getParent());File outdir = parent == null ? null : new File(argsMap.containsKey(cmdOutdir) ? argsMap.get(cmdOutdir) : parent);String[] contents = argsMap.containsKey(cmdContents) ? argsMap.get(cmdContents).split(";") : null;String password = argsMap.get(cmdPassword);if (path != null) {System.out.println("path: " + path);System.out.println("outdir: " + outdir);if (contents != null && contents.length > 0) {System.out.println("contents: " + Arrays.toString(contents));}System.out.println("password: " + password);if (contents == null || contents.length == 0) { // 读取数据;System.out.println("content: " + readContent(path, password));} else { // 写入数据;String fileName = path.getName();int dot = fileName.lastIndexOf(".");String prefix = fileName.substring(0, dot);String suffix = fileName.substring(dot);for (String content : contents) {File target = new File(outdir, prefix + "_" + content + suffix);if (nioTransferCopy(path, target)) {write(target, content, password);}}}}}}}System.out.println("time:" + (System.currentTimeMillis() - time));}
}
  • 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
  • 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
  • 使用方法

1、命令行使用说明: 
用法:Java -jar MCPTool.jar [-path] [arg] [-contents] [arg] [-password] [arg] 
-path APK文件路径 
-outdir 输出路径(可选),默认输出到APK文件同一目录 
-contents 写入内容集合,多个内容之间用“;”分割,如:googleplay;m360; 当没有“-contents”参数时输出已有文件中的content 
-password 加密密钥(可选),长度8位以上,如果没有该参数,不加密 
-version 显示版本号 
例如:

写入: 
java -jar MCPTool.jar -path D:/test.apk -outdir ./ -contents googleplay;m360; -password 12345678 
读取: 
java -jar MCPTool.jar -path D:/test.apk -password 12345678

2、Android代码中读取写入的渠道号: 
导入MCPTool.jar中的MCPTool类,MCPTool.getChannelId(context, mcptoolPassword, defValue)读出写入的渠道号;

3、jenkins、hudson、ant使用说明: 
请看MultiChannelPackageTool\build-ant\MCPTool\build.xml文件;

4、Windows下bat脚本运行说明: 
拖拽文件即可完成多渠道打包:MultiChannelPackageTool\build-ant\MCPTool\MCPTool.bat; 
拖拽文件检查渠道号是否写入成功:MultiChannelPackageTool\build-ant\MCPTool\MCPTool-check.bat;

  • 获取渠道号
/*** Android平台读取渠道号* @param context Android中的android.content.Context对象* @param mcptoolPassword mcptool解密密钥* @param defValue 读取不到时用该值作为默认值* @return*/public static String getChannelId(Object context, String mcptoolPassword, String defValue) {String content = MCPTool.readContent(new File(getPackageCodePath(context)), mcptoolPassword);return content == null || content.length() == 0 ? defValue : content;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
String channelId = MCPTool.getChannelId(context,password,default);
  • 1
  • 1

优缺点

没有解压缩、压缩、重签名,没有兼容性问题,速度最快;写入的渠道号数据支持加密,安全可靠;

由于速度极快,我还可以作为服务器端下载apk时动态写入“特定数据”,用户下载到apk后安装启动,读取“特定数据”完成特定的操作; 
如:加好友功能,下载前写入用户ID,用户下载后启动apk,读取写入的用户ID,完成加好友操作,用户体验大大提升,没有断裂感; 
当然,也可以写入JSON数据,想做什么就做什么;

引用

seven456:MultiChannelPackageTool

360多渠道打包升级版:

原理

利用的是Zip文件“可以添加comment(摘要)”的数据结构特点,在文件的末尾写入任意数据,而不用重新解压zip文件(apk文件就是zip文件格式)。

实现

实现方式有三种:Python脚本、Java脚本、gradle构建

  • 方法一:python脚本的方式

python源码

'''关键代码'''def _check(apkfile, marketfile=MARKET_PATH, output=OUTPUT_PATH, format=ARCHIVE_FORMAT, show=False, test=0):'''check apk file exists, check apk valid, check arguments, check market file exists'''if not os.path.exists(apkfile):print('apk file', apkfile, 'not exists or not readable')returnif not parse_apk(apkfile):print('apk file', apkfile, 'is not valid apk')returnif show:show_market(apkfile)returnif test > 0:run_test(apkfile, test)returnif not os.path.exists(marketfile):print('marketfile file', marketfile, 'not exists or not readable.')returnold_market = read_market(apkfile)if old_market:print('apk file', apkfile, 'already had market:', old_market,'please using original release apk file')returnprocess(apkfile, marketfile, output, format)def _parse_args():'''parse command line arguments'''parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,description='PackerNg v{0} created by mcxiaoke.\n {1}'.format(__version__, INTRO_TEXT),epilog='')parser.add_argument('apkfile', nargs='?',help='original release apk file path (required)')parser.add_argument('marketfile', nargs='?', default=MARKET_PATH,help='markets file path [default: ./markets.txt]')parser.add_argument('output', nargs='?', default=OUTPUT_PATH,help='archives output path [default: ./archives]')parser.add_argument('-f', '--format', nargs='?', default=ARCHIVE_FORMAT, const=True,help="archive format [default:'${name}-${package}-v${vname}-${vcode}-${market}${ext}']")parser.add_argument('-s', '--show', action='store_const', const=True,help='show apk file info (pkg/market/version)')parser.add_argument('-t', '--test', default=0, type=int,help='perform serval times packer-ng test')args = parser.parse_args()if len(sys.argv) == 1:parser.print_help()return Nonereturn argsif __name__ == '__main__':args = _parse_args()if args:_check(**vars(args))
  • 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
  • 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

python脚本

python PackerNg.py [file] [market] [output] [-h] [-s] [-t TEST]

方法二:java脚本的方式

/*关键代码*//*java 脚本程序入口*/public static void main(String[] args) {if (args.length < 2) {Helper.println(USAGE_TEXT);Helper.println(INTRO_TEXT);System.exit(1);}File apkFile = new File(args[0]);File marketFile = new File(args[1]);File outputDir = new File(args.length >= 3 ? args[2] : "apks");if (!apkFile.exists()) {Helper.printErr("Apk file '" + apkFile.getAbsolutePath() +"' is not exists or not readable.");Helper.println(USAGE_TEXT);System.exit(1);return;}if (!marketFile.exists()) {Helper.printErr("Market file '" + marketFile.getAbsolutePath() +"' is not exists or not readable.");Helper.println(USAGE_TEXT);System.exit(1);return;}if (!outputDir.exists()) {outputDir.mkdirs();}Helper.println("Apk File: " + apkFile.getAbsolutePath());Helper.println("Market File: " + marketFile.getAbsolutePath());Helper.println("Output Dir: " + outputDir.getAbsolutePath());List<String> markets = null;try {markets = Helper.parseMarkets(marketFile);} catch (IOException e) {Helper.printErr("Market file parse failed.");System.exit(1);}if (markets == null || markets.isEmpty()) {Helper.printErr("No markets found.");System.exit(1);return;}final String baseName = Helper.getBaseName(apkFile.getName());final String extName = Helper.getExtension(apkFile.getName());int processed = 0;try {for (final String market : markets) {final String apkName = baseName + "-" + market + "." + extName;File destFile = new File(outputDir, apkName);Helper.copyFile(apkFile, destFile);Helper.writeMarket(destFile, market);if (Helper.verifyMarket(destFile, market)) {++processed;Helper.println("Generating apk " + apkName);} else {destFile.delete();Helper.printErr("Failed to generate " + apkName);}}Helper.println("[Success] All " + processed+ " apks saved to " + outputDir.getAbsolutePath());Helper.println(INTRO_TEXT);} catch (MarketExistsException ex) {Helper.printErr("Market info exists in '" + apkFile+ "', please using a clean apk.");System.exit(1);} catch (IOException ex) {Helper.printErr("" + ex);System.exit(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
  • 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

java脚本

java -jar PackerNg.jar apkFile marketFile outputDir

方法三:gradle构建

在项目top level build.gradle中添加

buildscript {......dependencies{// add packer-ngclasspath 'com.mcxiaoke.gradle:packer-ng:1.0.7'}
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在 app level build.gradle中添加

apply plugin: 'packer'
packer {checkSigningConfig = truecheckZipAlign = truearchiveNameFormat = '${appPkg}-${flavorName}-${buildType}-v${versionName}-${versionCode}-${fileMD5}'archiveOutput = file(new File(project.rootProject.buildDir.path, "apks"))
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

着重几点

  • 改善了360多渠道打包方式中api兼容性的问题

ZipFile.getComment是ZIP文件注释写入,使用Java会导致APK文件被破坏,无法安装。这里是读取ZIP文件注释的问题,Java 7里可以使用zipFile.getComment()方法直接读取注释,非常方便。但是Android系统直到API 19,也就是4.4以上的版本才支持 ZipFile.getComment() 方法。由于要兼容之前的版本,所以这个方法也不能使用。改为:

public static boolean hasZipCommentMagic(File file) throws IOException {RandomAccessFile raf = null;try {raf = new RandomAccessFile(file, "r");long index = raf.length();byte[] buffer = new byte[MAGIC.length];index -= MAGIC.length;// read magic bytesraf.seek(index);raf.readFully(buffer);// check magic bytes matchedreturn isMagicMatched(buffer);} finally {if (raf != null) {raf.close();}}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • Android 7.0签名校验引起的安装失败

为了提高Android系统的安全性,Google从Android 7.0开始增加一种新的增强签名模式,从Android Gradle Plugin 2.2.0开始,构建系统在打包应用后签名时默认使用APK signature scheme v2,该模式在原有的签名模式上,增加校验APK的SHA256哈希值,如果签名后对APK作了任何修改,安装时会校验失败,提示没有签名无法安装,使用本工具修改的APK会无法安装,解决办法是在 signingConfigs 里增加 v2SigningEnabled false ,禁用新版签名模式,技术细节请看官方文档:APK signature scheme v2

android {...defaultConfig { ... }signingConfigs {release {storeFile file("myreleasekey.keystore")storePassword "password"keyAlias "MyReleaseKey"keyPassword "password"v2SigningEnabled false //禁用v2签名增强模式}}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

优缺点

使用APK注释保存渠道信息和MAGIC字节,从文件末尾读取渠道信息,速度飞快

实现为一个Gradle Plugin,支持定制输出APK的文件名等信息,方便CI集成

提供Java版和Python的独立命令行脚本,不依赖Gradle插件,支持独立使用 
缺点

没有使用Android的productFlavors实现,无法利用flavors条件编译的功能


现360推出加固宝,方便进行签名与打包以及软件加固防解密,操作比较简便易上手,有兴趣可以用一下

总结


原始多渠道打包:

渠道较少的情况下使用,每设置一次渠道id需要构建一次,完全是个体力活。
  • 1
  • 1

友盟多渠道打包:

打包:解压apk文件 -> 替换AndroidManifest.xml中的meta-data -> 压缩apk文件 -> 签名
读取渠道号:直接通过Android的API读取meta-data
特点:需要解压缩、压缩、重签名耗费时间较多,重签名会导致apk包在运行时有兼容性问题。
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

360多渠道打包

打包:直接写入渠道号到apk文件的末尾
读取渠道号:直接读取data/app/<package>.apk文件末尾的渠道号
特点:没有解压缩、压缩、重签名,没有兼容性问题,速度最快;写入的渠道号数据支持加密,安全可靠。
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

360多渠道打包plus(进阶版)

改善了360多渠道打包受android api19的影响,并扩展了java、python、gradle插件版,是目前第三方多渠道打包中速度最快最灵活的。
  • 1
  • 1

maven、gradle版

对于android studio而言基本上抛弃了maven的方式,那么对于gradle版我们可以通过productFlavors通过更加细腻的定制,不过打包构建过程还是比较耗时。
  • 1
  • 1

那么我们选择时可以按实际情况使用360多渠道打包plus,或者android studio gradle多渠道打包。需注意的是360多渠道打包plus无法通过android7.0签名校验,当然只要是通过后期修改apk文件的方式都不能通过android7.0的签名校验。


这篇关于关于多渠道打包的最强攻略--总结版的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1020424

相关文章

Python pyinstaller实现图形化打包工具

《Pythonpyinstaller实现图形化打包工具》:本文主要介绍一个使用PythonPYQT5制作的关于pyinstaller打包工具,代替传统的cmd黑窗口模式打包页面,实现更快捷方便的... 目录1.简介2.运行效果3.相关源码1.简介一个使用python PYQT5制作的关于pyinstall

javafx 如何将项目打包为 Windows 的可执行文件exe

《javafx如何将项目打包为Windows的可执行文件exe》文章介绍了三种将JavaFX项目打包为.exe文件的方法:方法1使用jpackage(适用于JDK14及以上版本),方法2使用La... 目录方法 1:使用 jpackage(适用于 JDK 14 及更高版本)方法 2:使用 Launch4j(

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

Java向kettle8.0传递参数的方式总结

《Java向kettle8.0传递参数的方式总结》介绍了如何在Kettle中传递参数到转换和作业中,包括设置全局properties、使用TransMeta和JobMeta的parameterValu... 目录1.传递参数到转换中2.传递参数到作业中总结1.传递参数到转换中1.1. 通过设置Trans的

C# Task Cancellation使用总结

《C#TaskCancellation使用总结》本文主要介绍了在使用CancellationTokenSource取消任务时的行为,以及如何使用Task的ContinueWith方法来处理任务的延... 目录C# Task Cancellation总结1、调用cancellationTokenSource.

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

电脑桌面文件删除了怎么找回来?别急,快速恢复攻略在此

在日常使用电脑的过程中,我们经常会遇到这样的情况:一不小心,桌面上的某个重要文件被删除了。这时,大多数人可能会感到惊慌失措,不知所措。 其实,不必过于担心,因为有很多方法可以帮助我们找回被删除的桌面文件。下面,就让我们一起来了解一下这些恢复桌面文件的方法吧。 一、使用撤销操作 如果我们刚刚删除了桌面上的文件,并且还没有进行其他操作,那么可以尝试使用撤销操作来恢复文件。在键盘上同时按下“C

springboot3打包成war包,用tomcat8启动

1、在pom中,将打包类型改为war <packaging>war</packaging> 2、pom中排除SpringBoot内置的Tomcat容器并添加Tomcat依赖,用于编译和测试,         *依赖时一定设置 scope 为 provided (相当于 tomcat 依赖只在本地运行和测试的时候有效,         打包的时候会排除这个依赖)<scope>provided