Android多渠道打包

多渠道打包一般应用于向不同应用市场提交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

github地址

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签名下的新一代渠道包打包神器。

github地址

什么是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多渠道打包以及解决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也可以:

在这里插入图片描述

打包成功后:

在这里插入图片描述

全部代码

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值