多渠道打包一般应用于向不同应用市场提交app后用来统计不同渠道下载量等一些信息。
在不同的应用市场可能有不同的统计需求,需要为每个应用市场发布一个安装包,这里就引出了Android的多渠道打包。在安装包中添加不同的标识,以此区分各个渠道,方便统计app在市场的各种。
安卓app上线,需要创建各个市场和推广渠道的apk安装包。每个安装包携带对应的渠道信息。
基本所有安卓项目需要创建渠道包,而且上线时间越长,推广渠道会越来越多,时有更新,同时可能还会有创建马甲包的需求。在实际中一次上线可能会创建数十个渠道包。所以使用一套方便、稳定的打包流程,是非常有必要的。尤其体现在打包时间,安装包稳定性方面。
什么是多渠道包?
渠道包就是要在安装包中添加渠道信息,也就是channel,对应不同的渠道,例如:小米市场、360市场、应用宝市场等
为什么要提供多渠道包?
我们要在安装包中添加不同的标识,应用在请求网络的时候携带渠道信息,方便后台做运营统计(这就是添加渠道信息的用处)。
实现多渠道打包的原理
一般来讲,这个渠道的标识会放在AndroidManifest.xml的Application的一个Metadata中。然后就可以在java中通过API获取对应的数据了。
如何实现?
现在android渠道多种多样,其实渠道不仅仅局限于应用市场,一种推广方式也可以看做一个渠道,比如:通过人拉人的方式去推广,官网上推广,百度推广等。所以说渠道成千上万,为了推广,有时候一次也会打成千的安装包,那你半天或者一天啥都别干了,所以介绍几个大公司高效的打包方式,借鉴一下。
第一种:友盟就提供了多渠道打包的方式,可用于渠道统计等。
现在Android的构建工具换成了gradle,通过gradle,简单配置后就可以实现自动打所有渠道包。
友盟多渠道打包
不受加固影响,不会因为加固失渠道信息。
1. 按照umeng的要求,manifest文件中需要有
<!-- name:固定为UMENG_CHANNEL-->
<!--value:填写渠道名称,huawei。这里设置动态渠道名称变量-->
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" />
这段配置,value那里就是wandoujia,360之类的渠道名称,但是我们在这里不会去写渠道名,写的是一个占位符,后面gradle编译的时候会动态的替换掉它。
2. 在module(一般也就是app)的build.gradle的android{}中添加如下内容:
productFlavors {
yingyongbao {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "yingyongbao"]
}
huawei {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "huawei"]
}
xiaomi {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
}
oppo {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "oppo"]
}
vivo {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "vivo"]
}
}
productFlavors是android节点的一个自节点。你需要打什么渠道的包,就在这里按umeng的要求用渠道名给UMENG_CHANNEL_VALUE赋值。
3. 优化1
上面只是几个渠道,如果有几十个渠道,都这样写,重复的东西太多,观察到每个渠道就是flavor的名称,所以修改如下:
productFlavors {
yingyongbao {}
huawei {}
xiaomi {}
oppo {}
vivo {}
}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
4. 优化2:设置打包apk名字
设置打包apk名字,上面经过签名打包后生成的apk的名称是有默认命名规则的,如:xxx-xiaomi-release.apk ,但是我们想包含版本信息如:所以最终打包脚本如下:
productFlavors {
yingyongbao {}
huawei {}
xiaomi {}
oppo {}
vivo {}
}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
//设置打包apk名字
applicationVariants.all { variant ->
variant.outputs.all { output ->
def buildTypeName = variant.buildType.name
def versionName = defaultConfig.versionName
def channelName = variant.productFlavors[0].name
// 多渠道打包的时候,后台不支持中文
outputFileName = "app-v${versionName}-${buildTypeName}-${buildTime()}-${channelName}.apk"
}
}
}
static def buildTime() {
def date = new Date()
def formattedDate = date.format('MMddHHmmss')
return formattedDate
}
5. AS3.0要加上defaultConfig,否则会报错:
defaultConfig {
applicationId "com.xiaoyehai.multichannelpackageing"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// AS3.0之后:原因就是使用了productFlavors分包,解决方法就是在build.gradle中的defaultConfig中
// 添加一个flavorDimensions "1"就可以了,后面的1一般是跟你的versionCode相同
flavorDimensions "1"
}
6. 执行签名打包:
7. 打包成功后如下:
8. 获取渠道信息
在代码中我们可以通过读取mate-data信息来获取渠道,然后添加到请求参数中,获取方法如下:
private String getChannel() {
try {
PackageManager pm = getPackageManager();
ApplicationInfo appInfo = pm.getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
return appInfo.metaData.getString("UMENG_CHANNEL");
} catch (PackageManager.NameNotFoundException ignored) {
}
return "";
}
9. 缺点:
这样的打包方式效率比较低下,如果是几十个包还可以应付,打一个包快的话需要十几秒,慢的话需要几分钟不等,跟机器性能很有关系。
10. 不受加固影响,不会因为加固失渠道信息
在安卓项目studio编译器gradle文件中配置flavors,根据 flavors信息给每个apk包配置对应的渠道信息。生成Apk包再进行加固和签名,这种方法是在编译期将渠道文件写在buildconfig文件中,不受加固影响,不会因为加固失渠道信息。但是它需要studio编译器生成N个渠道包,再将N个渠道包加固。studio和加固工具都不适合一次性处理过多apk文件,所以需要分批处理,这就会导致手动操作频繁和工作时间过长,容易出错也耗时过多。
11. 可以设置每种渠道包app名称不同
productFlavors {
huawei {
manifestPlaceholders = [app_name: "淘金-huawei"]
}
oppo {
manifestPlaceholders = [app_name: "淘金-oppo"]
}
vivo {
manifestPlaceholders = [app_name: "淘金-vivo"]
}
xiaomi {
manifestPlaceholders = [app_name: "淘金-xiaomi"]
}
}
<application
android:name=".AppApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="${app_name}"
12. build.gradle完整代码
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.zly.umeng"
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
// AS3.0之后:原因就是使用了productFlavors分包,解决方法就是在build.gradle中的defaultConfig中
// 添加一个flavorDimensions "1"就可以了,后面的1一般是跟你的versionCode相同
flavorDimensions "1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
//打包签名文件
signingConfigs {
config {
storeFile file('../yipingcankey.jks')
storePassword '88888888'
keyAlias '上海'
keyPassword '88888888'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
}
/* productFlavors {
yingyongbao {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "yingyongbao"]
}
huawei {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "huawei"]
}
xiaomi {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
}
oppo {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "oppo"]
}
vivo {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "vivo"]
}
}*/
//上面只是几个渠道,如果有几十个渠道,都这样写,重复的东西太多,观察到每个渠道就是flavor的名称,所以修改如下:
productFlavors {
yingyongbao {}
huawei {}
xiaomi {}
oppo {}
vivo {}
}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
//设置打包apk名字
applicationVariants.all { variant ->
variant.outputs.all { output ->
def buildTypeName = variant.buildType.name
def versionName = defaultConfig.versionName
def channelName = variant.productFlavors[0].name
// 多渠道打包的时候,后台不支持中文
outputFileName = "app-v${versionName}-${buildTypeName}-${buildTime()}-${channelName}.apk"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
static def buildTime() {
def date = new Date()
def formattedDate = date.format('MMddHHmmss')
return formattedDate
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
Android Studio自带多渠道打包
不受加固影响,不会因为加固失渠道信息。
Android Studio多形态打包与友盟打包方式相同,但是标签中name可自行定义,不限制为"UMENG_CHANNEL"
<meta-data
android:name="UMENG_CHANNEL" //可以随意定义
android:value="${channel}" />
美团打包工具packer-ng-plugin
packer-ng-plugin:下一代Android打包工具,100个渠道包只需要10秒钟。该工具快速生成渠道包。它可以将一个包快速生成多个渠道包。
packer-ng-plugin 是下一代Android渠道打包工具Gradle插件,支持极速打包,100个渠道包只需要10秒钟,速度是 gradle-packer-plugin 的300倍以上,可方便的用于CI系统集成,同时提供命令行打包脚本,渠道读取提供Python和C语言的实现。
V2版只支持APK Signature Scheme v2,要求在 signingConfigs 里 v2SigningEnabled true 启用新版签名模式,如果你需要使用旧版本,看这里 v1.0.9。
使用打包工具(packer-ng-plugin)生成渠道包。先由studio编译器生成release包,再用360加固宝加固(不使用它的自动签名和签名校验),然后进行签名和验证,最后由该工具生成N个渠道包。这种流程单 一 ,始终是操作一个apk文件,相比方案一更好。无论是操作流程还是打包时间都是占有绝对优势的。
具体使用请看github文档。
美团多渠道 walle
Android Signature V2 Scheme签名下的新一代渠道包打包神器。
什么是Walle?
Walle(瓦力):Android Signature V2 Scheme签名下的新一代渠道包打包神器
瓦力通过在Apk中的APK Signature Block区块添加自定义的渠道信息来生成渠道包,从而提高了渠道包生成效率,可以作为单机工具来使用,也可以部署在HTTP服务器上来实时处理渠道包Apk的升级网络请求。
集成
为了方便大家的使用,Walle提供了2种使用方式:(这里主要讲常用的第一种方式)
Gradle插件方式,方便快速集成。
命令行方式,最大化满足各种自定义需求。
Gradle插件使用方式
1. 配置build.gradle:
在位于项目的根目录 build.gradle 文件中添加Walle Gradle插件的依赖, 如下:
buildscript {
dependencies {
classpath 'com.meituan.android.walle:plugin:1.1.7'
}
}
2. 并在当前App的 build.gradle 文件中apply这个插件,并添加上用于读取渠道号的AAR
apply plugin: 'walle'
dependencies {
compile 'com.meituan.android.walle:library:1.1.7'
}
3. 需要在buildTypes中加signingConfig,否则报这个错: Error:Plugin requires ‘APK Signature Scheme v2 Enabled’ for release.
//打包签名文件
signingConfigs {
config {
storeFile file('../yipingcankey.jks')
storePassword '88888888'
keyAlias '上海'
keyPassword '88888888'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
}
4,在app的 build.gradle 文件中配置插件,如下:
walle {
// 指定渠道包的输出路径
apkOutputFolder = new File("${project.buildDir}/outputs/channels");
// 定制渠道包的APK的文件名称
apkFileNameFormat = '${appName}-${packageName}-${channel}-${buildType}-v${versionName}-${versionCode}-${buildTime}.apk';
// 渠道配置文件
channelFile = new File("${project.getProjectDir()}/channel")
}
配置项具体解释:
apkOutputFolder:指定渠道包的输出路径, 默认值为new File("${project.buildDir}/outputs/apk")
apkFileNameFormat:定制渠道包的APK的文件名称, 默认值为’
a
p
p
N
a
m
e
−
{appName}-
appName−{buildType}-${channel}.apk’
可使用以下变量:
projectName - 项目名字
appName - App模块名字
packageName - applicationId (App包名packageName)
buildType - buildType (release/debug等)
channel - channel名称 (对应渠道打包中的渠道名字)
versionName - versionName (显示用的版本号)
versionCode - versionCode (内部版本号)
buildTime - buildTime (编译构建日期时间)
fileSHA1 - fileSHA1 (最终APK文件的SHA1哈希值)
flavorName - 编译构建 productFlavors 名
channelFile:包含渠道配置信息的文件路径:
5. 在app目录下新建名为channel的文件,在该文件里写上需要打包的渠道号,渠道号支持使用#号添加注释。具体内容格式详见下:
yingyongbao # 应用宝
huawei #华为
xiaomi # 小米
oppo #oppo
vivo #vivo
sangxing #三星
meizu #魅族
build.gradle 文件完整配置:
plugins {
id 'com.android.application'
}
apply plugin: 'walle'
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.zly.walle"
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
//打包签名文件
signingConfigs {
config {
storeFile file('../yipingcankey.jks')
storePassword '88888888'
keyAlias '上海'
keyPassword '88888888'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
walle {
// 指定渠道包的输出路径
apkOutputFolder = new File("${project.buildDir}/outputs/channels");
// 定制渠道包的APK的文件名称
apkFileNameFormat = '${appName}-${packageName}-${channel}-${buildType}-v${versionName}-${versionCode}-${buildTime}.apk';
// 渠道配置文件
channelFile = new File("${project.getProjectDir()}/channel")
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.meituan.android.walle:library:1.1.7'
}
如何获取渠道信息
在需要渠道等信息时可以通过下面代码进行获取
String channel = WalleChannelReader.getChannel(this.getApplicationContext());
如何生成渠道包
生成渠道包的方式是和assemble${variantName}Channels指令结合,渠道包的生成目录默认存放在 build/outputs/apk/,也可以通过walle闭包中的apkOutputFolder参数来指定输出目录
用法示例:
生成渠道包 ./gradlew clean assembleReleaseChannels
支持 productFlavors ./gradlew clean assembleMeituanReleaseChannels
使用
在Android Studio的Terminal中输入命令gradlew clean assembleReleaseChannels进行多渠道打包,当运行完成会出现BUILD SUCCESSFUL, 如图
gradlew clean assembleReleaseChannels
简易方法:通常是全部渠道生成使用。 在右侧gradle, app - tasks - package, 选择需要的选项。
打包成功后:
插入额外信息
channelFile只支持渠道写入,如果想插入除渠道以外的其他信息,请在walle配置中使用configFile
walle {
// 渠道&额外信息配置文件,与channelFile互斥
configFile = new File("${project.getProjectDir()}/config.json")
}
configFile是包含渠道信息和额外信息的配置文件路径。
配置文件采用json格式,支持为每个channel单独配置额外的写入信息。具体内容格式详见:渠道&额外信息配置文件示例 。
{
//extraInfo 不要出现以`channel`为key的情况
/*
不声明extraInfo的channel默认使用的extraInfo
如果没有此项则没有默认extraInfo
*/
"defaultExtraInfo": {
"key2": "20161213",
"key": "20161212"
},
/*
strategy:
1. ifNone (默认适用此策略) : 仅当对应channel没有extraInfo时生效
2. always : 所有channel都生效,channel中extraInfo的key与defaultExtraInfo重复时,覆盖defaultExtraInfo中的内容。
*/
//"defaultExtraInfoStrategy": "always",
"channelInfoList": [
{
"channel": "meituan",
// 此channel将使用自己声明的extraInfo
/*
此alias可以做到写入apk的channel是meituan,而打包时输出的文件名是美团
注意:alias不声明时,walle配置apkFileNameFormat中channel就是channel,否则为alias
*/
"alias": "美团",
"extraInfo": {
"buildtime": "20161212",
"hash": "123"
}
},
{
"channel": "360cn",
// 此channel将使用自己声明的extraInfo
"extraInfo": {
"key": "20161213"
}
},
{
"channel": "googleplay"
// 此channel将使用defaultExtraInfo
},
{
"channel": "xiaomi"
// 此channel将使用defaultExtraInfo
},
{
"channel": "meizu"
// 此channel将使用defaultExtraInfo
},
{
"channel": "wandoujia",
"excludeDefaultExtraInfo": true
//强制声明不使用defaultExtraInfo,默认false
},
{
"channel": "myapp",
"excludeDefaultExtraInfo": true,
//强制声明不使用defaultExtraInfo,默认false
"extraInfo": {
// 尽管exclude default,但也可以继续写入自己的。
"key": "20161212"
}
}
]
}
注意:
- 此配置项与channelFile功能互斥,开发者在使用时选择其一即可,两者都存在时configFile优先执行。
- extraInfo 不要出现以channel为key的情况
而对应的渠道信息获取方式如下:
ChannelInfo channelInfo= WalleChannelReader.getChannelInfo(this.getApplicationContext());
if (channelInfo != null) {
String channel = channelInfo.getChannel();
Map<String, String> extraInfo = channelInfo.getExtraInfo();
}
// 或者也可以直接根据key获取
String value = WalleChannelReader.get(context, "buildtime");
临时生成某渠道包
我们推荐使用channelFile/configFile配置来生成渠道包,但有时也可能有临时生成渠道包需求,这时可以使用:
- 生成单个渠道包: ./gradlew clean assembleReleaseChannels -PchannelList=meituan
- 生成多个渠道包: ./gradlew clean assembleReleaseChannels -PchannelList=meituan,dianping
- 生成渠道包&写入额外信息:./gradlew clean assembleReleaseChannels -PchannelList=meituan -PextraInfo=buildtime:20161212,hash:xxxxxxx 注意: 这里的extraInfo以key:value形式提供,多个以,分隔。
- 使用临时channelFile生成渠道包: ./gradlew clean assembleReleaseChannels -PchannelFile=/Users/xx/Documents/channel
- 使用临时configFile生成渠道包: ./gradlew clean assembleReleaseChannels -PconfigFile=/Users/xx/Documents/config.json
使用上述-P参数后,本次打包channelFile/configFile配置将会失效,其他配置仍然有效。 -PchannelList,-PchannelFile, -PconfigFile三者不可同时使用。
Walle 命令行工具使用方式
可以使用命令行工具来支持各类自定义的需求,具体使用方式详见:Walle CLI 使用说明
需要下载walle-cli-all.jar文件。
360加固丢失渠道信息
我们直接通过美团Walle多渠道方案打包生成的apk,在经过360加固之后,是会丢掉渠道信息的,对此问题,美团在GitHub上也提出了解决方案360加固失效?
这里我们就直接采用Jay-Goo大佬提供的Python打包脚本ProtectedApkResignerForWalle,这也是美团官方推荐的方案。接下来我们就讲解如何使用该脚本
ProtectedApkResignerForWalle
一步解决应用加固导致Walle渠道信息失效的自动化脚本,自动生成渠道包。
最近很多朋友问我这个脚本和walle的关系,用了这个脚本还用walle吗?在这里我来解释下:
官方walle分为两部分,第一部分是打包部分,包括 plugin 部分和 build.gradle 中 walle{…} 脚本,另一部分是用于读取渠道号的AAR,如果你使用类似友盟等统计工具,你需要利用walle提供的aar来读取你的渠道信息,然后手动传给友盟渠道信息。在不考虑加固的情况下只需要执行类似./gradlew clean assembleReleaseChannels,AS会自动执行gradle中的脚本和插件进行多渠道打包。
ProtectedApkResignerForWalle是用于解决walle产生的加固问题,用的是walle的打包CLI,替代的是第一部分,所以你无须引用 plugin 部分和 build.gradle 中 walle{…} 脚本部分,第二部分还是要正常引用的。多渠道打包时,先加固,然后把未签名的apk使用此脚本进行多渠道打包即可。
用法:
- 按照config.py文件中的注释改成自己项目配置
- 将已经加固好的包【未签名的包,请不要使用加固客户端签名工具】放到脚本工具根目录下,即app-release.encrypted.apk
- 各种渠道的定义是在channel这个文件中,请根据项目情况修改 运行命令 python
- ApkResigner.py,即可自动生成所有渠道包。
运行注意事项:
需要安装Python
下载安装教程自己网上找。
第一步:在app的build.gradle中添加依赖:
compile 'com.meituan.android.walle:library:1.1.7'
第二步:把https://github.com/Jay-Goo/ProtectedApkResignerForWalle 中的文件克隆下来,放在自己工程自定义的文件夹中,如下:
walle文件夹下除了自定义的,其余文件和文件夹就是从Jay-Goo大佬那里克隆下来的。
第三步:打项目的Release包,然后上传360加固,没有账号的注册一个,进入管理中心:
打一个release包,gradle其实为我们提供了一系列相关的任务,如下图
我们执行加固前是需要拿到一个release包的,所以我们可以利用assembleRelease在加固前先执行assembleRelease这个Task。
注意:一定要用网页版本的,不要用pc工具,因为pc有自动签名的。网页版本不会。
第四步:下载已经加固的包,接着我们把加固后的包放到walle文件夹下,如下图所示:
把config.py文件中的这几个内容改成自己项目中的配置 ,如下图所示:
还有把config.py文件中sdkBuildToolPath改成自己的Android SDK buidtools所在的路径:
然后在ApkResigner.py文件所在路径下运行python脚本:python ApkResigner.py如下图所示:
执行完成后就生成了多渠道包,如下图:
如果你不想每次生成多渠道包时都要修改config.py文件中的protectedSourceApkName的名字,我们复制一个config2.py你可以用如下的方式替换:
同时还有复制一个ApkResigner.py命名为ApkResigner2.py,并把该文件中的所有config替换成config2,如下图:
然后在控制台运行脚本:python AapResigner.py如下图,同样可以生成多渠道包:
自定义360加固+Walle打包脚本
上面这个方案,太蛋疼了。我每次都要手动的打Release包,然后上传360加固,下载加固包。然后本地跑一次Phtyon脚本生成渠道包。请告诉我这个操作蛋疼不?
网上有很多脚本把这些步骤都做了,只要配置一下就可以使用,很方便,可以自己去网上找。
下面是我找的一个博客:
Android Gradle脚本解决美团多渠道打包再加固渠道信息丢失问题
下面是我自己项目中使用的方式
1. 在项目根目录新建_360jiagu文件夹
在需要打包的module的build.gradle中引入:
apply from: '../_360jiagu/jiagu.gradle'
walle依赖:
implementation 'com.meituan.android.walle:library:1.1.7'
2. 配置加固文件夹
把从360加固官网下载的window下载(MAC环境就MAC下载)里的内容有个jiagu文件夹,复制到_360jiagu文件夹中
3. 在_360jiagu文件夹创建walle目录:
channel.txt:渠道标识
dhc_yingyongbao
dhc_huawei
dhc_oppo
dhc_vivo
dhc_xiaomi
dhc_samsung
dhc_meizu
developer-default
jiagu.gradle:加固脚本
/**
*
* 使用360加固版本 1.3.8.4 2018-12-07
*
* 如果出现,签名配置中没有匹配的签名,请使用命令行的方式单独导入签名信息.
*
* java -jar jiagu.jar -importsign<keystore_path><keystore_password><alias><alias_password>
*
* 这有可能是360加固的BUG
*
* 由于需要使用的java环境过大,为了不增加git压力.
* 使用之前:先下载 http://down.360safe.com/360Jiagu/360jiagubao_windows_32.zip,
* 解压之后:将`java`文件夹 放在 `jiagu/` 文件夹下.
*
* 此项目内的 walle-cli-all是从一位网友出自己编译生成,解决在android9.0中 旧版本的walle打包在使用28.0.x的build打包时,抛出签名块长度不对的问题
* */
//360加固文件所在的文件夹名字
def folder_name = "_360jiagu"
//项目根路径的决定路径
def projectPath = getRootDir().getAbsolutePath()
getRootProject().ext.jiaguConfig = [
//360 账号 (必须)
"_360_user_name" : "xxxxxxx@qq.com",
"_360_user_pw" : "xxxxxxx",
//签名文件配置, 使用空字符, 会自动赋值. (可选)
"keystorePath" : '',
"keystorePwd" : '',
"keystoreAlias" : '',
"keystoreAliasPwd": '',
//需要加固的APK路径, 如果不存在任务中断执行, 为空:自动根据Gradle配置获取路径 (可选)
"targetApkPath" : "",
//自动查找`targetApkPath`时, 取名字中包含`likeApkName`字符串的路径, 如果有多个, 取第一个
"likeApkName" : "",
//文件所在的根目录
"root_path" : projectPath + "/" + folder_name,
//360加固文件所在的根目录
"jiagu_root_path" : projectPath + "/" + folder_name + "/jiagu",
"walle_root_path" : projectPath + "/" + folder_name + "/walle",
//渠道文件信息路径
"channelList" : projectPath + "/" + folder_name + "/walle/channel.txt",
//渠道分包后的APK文件名,为空:自动从Gradle脚本中获取 (可选)
"tmpApkName" : "",
//最终生成zip包的文件名 (可选)
"zipFileName" : "",
//360加固jar文件路径
"jiaguJarPath" : projectPath + "/" + folder_name + "/jiagu/jiagu.jar",
//walle分包jar文件路径
"walleJarPath" : projectPath + "/" + folder_name + "/walle/walle-cli-all.jar",
//临时文件输出目录
"tmpOutput" : projectPath + "/" + folder_name + "/output/tmp",
//zip文件输出目录
"zipOutput" : projectPath + "/" + folder_name + "/output/zip",
//加固后文件输出目录
"jiaguOutput" : projectPath + "/" + folder_name + "/output/jiagu",
//walle分包输出目录
"walleOutput" : projectPath + "/" + folder_name + "/output/walle",
]
/**
* 查找所有apk
* @param buildType release 或者 debug
* @return ArrayList <ApkFile>
*/
def findApkFiles(String buildType) {
println "findApkFiles buildType: " + buildType
File apkDir = new File(getRootDir().getAbsolutePath() + "/app/build/outputs/apk/" + buildType)
println "findApkFiles apkDir: " + apkDir.absolutePath
File[] files = apkDir.listFiles()
if (files == null || files.length == 0) {
return null
}
File lastFile = null
for (int i = 0; i < files.length; i++) {
File file = files[i]
//获取最后生成的apk
if (file.name.endsWith(".apk") && (lastFile == null || file.lastModified() > lastFile.lastModified())) {
lastFile = file
}
}
return lastFile
}
//获取apk路径和文件名称
project.afterEvaluate {
project.android.applicationVariants.all { variant ->
/* variant.assemble.doLast{
}*/
println "jeme variant.name====>:" + variant.name.toLowerCase()
if (
//只针对release打包方式
(jiaguConfig.likeApkName == "" && variant.name.toLowerCase().contains("release"))
|| (jiaguConfig.likeApkName != "" && variant.name.toLowerCase().contains(jiaguConfig.likeApkName))
) {
if (jiaguConfig.keystorePath == '') {
//自动配置签名信息
jiaguConfig.keystorePath = variant.signingConfig.storeFile
jiaguConfig.keystorePwd = variant.signingConfig.storePassword
jiaguConfig.keystoreAlias = variant.signingConfig.keyAlias
jiaguConfig.keystoreAliasPwd = variant.signingConfig.keyPassword
List<String> list = new ArrayList<>()
list.add('-importsign')
list.add(jiaguConfig.keystorePath)
list.add(jiaguConfig.keystorePwd)
list.add(jiaguConfig.keystoreAlias)
list.add(jiaguConfig.keystoreAliasPwd)
println "使用签名:" + list
setKeystore.setArgs(list)
//自动配置目标文件路径, 和临时文件名
//File file = findApkFiles("product/release")
File file = findApkFiles("release")
if (file != null) {
jiaguConfig.targetApkPath = file.getAbsolutePath()
println "读取到目标apk路径:" + jiaguConfig.targetApkPath
jiaguConfig.tmpApkName = file.getName()
println "临时文件名:" + jiaguConfig.tmpApkName
copyTmpApk.rename('(.+)', jiaguConfig.tmpApkName)
jiaguConfig.zipFileName = jiaguConfig.tmpApkName
.substring(0, jiaguConfig.tmpApkName.lastIndexOf('.')) + ".zip"
println "zip文件名:" + jiaguConfig.zipFileName
zipChannel.archiveName = jiaguConfig.zipFileName
}
//重置加固task参数
list = new ArrayList<>()
list.add('-jiagu')
list.add(jiaguConfig.targetApkPath)
list.add(jiaguConfig.jiaguOutput)
list.add('-autosign')
jiagu360.setArgs(list)
//重置walle task参数
list = new ArrayList<>()
list.add('batch')
list.add('-f')
list.add(jiaguConfig.channelList)
list.add(jiaguConfig.tmpOutput + File.separator + jiaguConfig.tmpApkName)
list.add(jiaguConfig.walleOutput)
walleApk.setArgs(list)
}
}
}
}
//进行登录
task login(type: JavaExec) {
workingDir(jiaguConfig.jiagu_root_path)
classpath(files(jiaguConfig.jiaguJarPath))
main('com.qihoo.jiagu.CmdMain')
args('-login', jiaguConfig._360_user_name, jiaguConfig._360_user_pw)
}
//配置 ,不要x86
task config(type: JavaExec, dependsOn: login) {
workingDir(jiaguConfig.jiagu_root_path)
classpath(files(jiaguConfig.jiaguJarPath))
main('com.qihoo.jiagu.CmdMain')
args('-config', "-analyse")
}
//设置签名配置
task setKeystore(type: JavaExec, dependsOn: config) {
workingDir(jiaguConfig.jiagu_root_path)
classpath(files(jiaguConfig.jiaguJarPath))
main('com.qihoo.jiagu.CmdMain')
}
//显示签名信息
task showKeystore(type: JavaExec) {
workingDir(jiaguConfig.jiagu_root_path)
classpath(files(jiaguConfig.jiaguJarPath))
main('com.qihoo.jiagu.CmdMain')
args('-showsign')
}
//进行加固
task jiagu360(type: JavaExec, dependsOn: setKeystore) {
workingDir(jiaguConfig.jiagu_root_path)
classpath(files(jiaguConfig.jiaguJarPath))
main('com.qihoo.jiagu.CmdMain')
//动态设置args
/*args('-jiagu',
tmpInputPath + File.separator + tmpInputApkFile,
jiaguOutput,
'-autosign')*/
}
//加固后的包保留,拷贝一份在tmp里面玩
task copyTmpApk(type: Copy, dependsOn: jiagu360) {
from(jiaguConfig.jiaguOutput)
//include('*_jiagu_sign.apk')
include('*.apk')
into(jiaguConfig.tmpOutput)
//名余曰正则兮,字余曰灵均, 动态执行
//rename('(.+)', tmpApk)
}
//瓦力打包
task walleApk(type: JavaExec, dependsOn: copyTmpApk) {
workingDir(jiaguConfig.walle_root_path)
classpath(files(jiaguConfig.walleJarPath))
main('com.meituan.android.walle.Main')
//动态设置args
/*args('batch', '-f',
baseBuildPath + channelList,
tmpPath + File.separator + tmpApk,
wallePath)*/
}
task zipChannel(type: Zip, dependsOn: walleApk) {
//workingDir(baseBuildPath)
from(jiaguConfig.walleOutput)
//动态设置
archiveName(jiaguConfig.zipFileName)
destinationDir file(jiaguConfig.zipOutput)
}
//暂不要求使用walle打包渠道包以及只对单个apk加固,不需要打包为zip
task _360jiagu(dependsOn: zipChannel) {
//task _360jiagu(dependsOn: walleApk) {
doLast {
println "加固结束:"
println("渠道包路径:" + jiaguConfig.walleOutput)
println("zip包路径:" + jiaguConfig.zipOutput)
}
}
login.doFirst {
File targetFile = new File(jiaguConfig.targetApkPath)
if (!targetFile.exists()) {
throw new RuntimeException("加固文件不存在:" + targetFile.getAbsolutePath())
} else {
println("开始加固:" + targetFile.getAbsolutePath())
println("输出zip路径:" + jiaguConfig.zipOutput)
}
//创建基础文件夹
createFolder(jiaguConfig.walleOutput, true)
createFolder(jiaguConfig.zipOutput)
createFolder(jiaguConfig.jiaguOutput, true)
createFolder(jiaguConfig.tmpOutput, true)
}
/**创建路径*/
def createFolder(String path, boolean clear = false) {
File folder = file(path)
if (folder.exists()) {
if (clear) {
clearFolder(path)
}
} else {
folder.mkdirs()
}
}
/**清理文件夹*/
def clearFolder(String path) {
File folder = file(path)
for (File f : folder.listFiles()) {
if (f.isDirectory()) {
clearFolder(f.getAbsolutePath())
} else if (f.isFile()) {
println "删除文件:" + f + " " + f.delete()
}
}
}
/**自动读取签名配置信息*/
def readSigning() {
def application = "com.android.application"
def applicationPlugin = project.plugins.findPlugin(application)
//applicationPlugin.extension
//println "插件:" + applicationPlugin
//println applicationPlugin.extension.signingConfigs[0]
def signingConfigs = applicationPlugin.extension.signingConfigs
def signingMap = new HashMap<String, String>()
if (signingConfigs.size() > 0) {
def builder = new StringBuilder()
builder.append("找到签名配置" + signingConfigs.size() + "个\n")
signingConfigs.each {
builder.append("name:")
builder.append(it.name)
builder.append("\n")
builder.append("mStoreFile:")
builder.append(it.storeFile)
builder.append("\n")
builder.append("mStorePassword:")
builder.append(it.storePassword)
builder.append("\n")
builder.append("mKeyAlias:")
builder.append(it.keyAlias)
builder.append("\n")
builder.append("mKeyPassword:")
builder.append(it.keyPassword)
builder.append("\n")
builder.append("\n")
signingMap.put("sf", it.storeFile)
signingMap.put("sp", it.storePassword)
signingMap.put("kp", it.keyPassword)
signingMap.put("ka", it.keyAlias)
}
println builder
} else {
println "未找到签名配置信息"
}
return signingMap
}
4. 生成release包
5. 打渠道包
在终端中使用命令:
gradlew _360jiagu //windows
./gradlew _360jiagu //mac 如果权限不足,在前面加上sudo
直接双击_360jiagu也可以:
打包成功后: