【Gradle】自定义插件之自动化打包加固并发布

2,609 阅读14分钟

前言

前面学习了很多Groovy的基础,还有Gradle的Task跟Project,现在我们可以根据这些所学的基础来进行自定义插件的实战了。

相信很多人公司的项目都有这么一个插件,可以实现自动打包后加固并上传到某个平台(一般公司都会搭建自己的FTP服务器)并把相关链接通过钉钉机器人发送到钉钉群消息。

这里笔者使用的加固是360加固,加固后的apk会上传到蒲公英,然后把上传到蒲公英后返回的相关信息发送到钉钉,所以这里一共有三个步骤,也就是会定义3个Task,分别是:

创建插件Module

  • AS版本:北极狐
  • AGP版本:4.2.0
  • 开发语言:Groovy
  • Groovy Version: 3.0.9

笔者这里使用的开发插件的方式是通过独立 Module 来实现的,如果习惯用 buildSrc 的方式来开发的话也可以,具体的创建步骤在这里就不说了,大家可以根据下面的项目结构来自行创建:

image.png

如果需要上传到远程仓库的话就保留红框内的 .gitignore 文件,否则的话在创建一个 Module 的时候除了 src 目录还有 build.gradle 之外,其余的文件都要先删除,然后按照 src -> main -> groovy -> 包名 来创建目录并编写代码,最后再把 build.gradle 下的所有代码都删掉。

这里我直接把build.gradle的所有代码都添加进来,注释会写得很详细:

plugins {
    id 'groovy'
    id 'maven-publish'
    id 'java-gradle-plugin'
}

dependencies {
    // 引入AGP,后面需要用到AppExtension
    implementation 'com.android.tools.build:gradle:4.2.0'
    // 由于需要用到网络请求,所以这里使用Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
}

gradlePlugin {
    plugins {
        standaloneGradlePlugins {
            // 配置插件的id,这里配置的id会在编译的时候生成一个xxx.properties,也就是你在使用这个插件的时候需要引入的插件id
            id = "com.imiyar.upload"
            // 配置插件的实现类
            implementationClass = "com.imiyar.upload.UploadApkPlugin"
        }
    }

}

publishing {
    publications {
        // 配置插件的id跟版本号:com.imiyar.upload:uploadApk:1.0.0
        // 具体配置可看下面的官方文档链接
        maven(MavenPublication) {
            groupId = 'com.imiyar.upload'
            artifactId = 'uploadApk'
            version = '1.0.0'

            from components.java // 要把源码jar包上传
        }
    }
}

这里主要讲一下 plugins 闭包中使用到的三个 plugin 的作用,其他的都是相关的配置代码而已

  • groovy: 这个问题不大,是用于支持groovy语言的。

  • maven-publish: 用于把插件代码发布到maven。官方文档

  • java-gradle-plugin: 用于协助 Gradle 插件的开发。它会自动应用 Java 库插件,将gradleApi()依赖项添加到配置中,并在任务执行api期间执行插件元数据的验证。

加固

创建完插件 Module 之后,我们可以开始来写代码了,首先创建一个 Plugin 类实现 Plugin<Project> 接口,并实现该接口的 apply 方法:

UploadApkPlugin

class UploadApkPlugin implements Plugin<Project> {
    // AGP中 AppExtension 的扩展名字
    static final String ANDROID_EXTENSION = "android"
    // 自定义一个扩展用于配置自定义插件所需要的信息
    static final String UPLOAD_APK_EXTENSION = "uploadApk"

    @Override
    void apply(Project project) {
        // 当在build.gradle中配置好 UploadApkExtension 的属性后,如果直接通过uploadApkExtension.getXxx()是无法获取得到值的
        // 所以需要调用project.afterEvaluate,该闭包会在gradle配置完成后回调,即解析完build.gradle文件后回调
        project.afterEvaluate {

        }
    }
}

apply 方法的调用时机是在 build.gradle 中定义了插件 id 后就会调用,所以我们如果需要拿到我们需要的扩展属性的话,就需要在 project.afterEvaluate 闭包中拿,否则拿到的会为空。

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.imiyar.upload'
}

uploadApk {
    
}

举个例子,当我们在调用了 id 'com.imiyar.upload' 这一句代码的时候,我们就会执行 apply 方法,而这时我们我们需要等 gradle 配置完成之后才可以拿到我们在 uploadApk 闭包中定义的属性(现在是空的,因为还没开始定义我们自己的扩展)。

接下来我们来自定义一个扩展类:

UploadApkExtension

class UploadApkExtension {

    // -------------- 加固相关的扩展 --------------
    String reinforceUserName // 360加固的用户名
    String reinforcePassword // 360加固的密码
    String reinforceFilePath // 360加固jar包的路径
    String outputDirectory    // 加固后输出的apk目录
    boolean isOpenReinforce = true // 是否需要加固,默认为true

}

定义完我们的扩展类以及所需要的属性后,我们需要在 Plugin 中创建我们的扩展并开始写我们的加固 Task 相关的逻辑了,继续编写上面的插件类

UploadApkPlugin

class UploadApkPlugin implements Plugin<Project> {
    ...

    @Override
    void apply(Project project) {
        // 创建 uploadApk 扩展
        project.extensions.create(UPLOAD_APK_EXTENSION, UploadApkExtension.class)
        
        project.afterEvaluate {
            // AppExtension是Android插件创建的扩展,对应着 app module 下的 android{} 闭包
            AppExtension androidExtension = project.extensions.findByName(ANDROID_EXTENSION)
            
            // 获取apk包的变体,applicationVariants默认有debug跟release两种变体
            androidExtension.applicationVariants.all { ApplicationVariant variant ->
                if (variant.name.equalsIgnoreCase("release")) {
                    // 自定义一个用于加固的Task,代码后面贴出
                    ReinforceTask reinforceTask = project.tasks.create("reinforceRelease", ReinforceTask)
                    reinforceTask.init(variant)
                }
            }
        }
    }
}

这里要注意的是apk的变体默认有 debugrelease 两种,当然你也可以自己创建多个变体,这里只针对 release 包进行加固操作,所以在创建 Task 的时候只判断了变体名称是否为“release”。接下来我们要来写加固Task的逻辑了。

在写加固相关逻辑前,大家可以先去 360加固 下载一下对应平台的压缩包,因为我们加固需要用到里面的 jiagu.jar 包,下载完之后我们来看一下解压后的文件目录:

image.png

这里我们主要用到 jiagu.jar 还有 help.txt ,大家可以打开 help.txt 来对应着稍后的代码进行阅读。不想打开的话笔者这里也可以截图放上来:

image.png

注:如果后面发现在使用该 jar 包的时候报权限不足等错误信息的话,可以下载一下旧版本的360加固来进行测试。

ReinforceTask

class ReinforceTask extends DefaultTask {

    @Internal
    ApplicationVariant variant // 拿到apk的变体,后面需要该变体的相关信息

    void init(ApplicationVariant variant) {
        this.variant = variant
        description = "Reinforce Release Apk"
        // 定义该task所属的group,如果不定义group的话task会在other分组里
        group = "uploadApk"
    }

    @TaskAction
    void action() {
        UploadApkExtension uploadApkExtension = project.extensions.findByName(UploadApkPlugin.UPLOAD_APK_EXTENSION)
        // 获取变体的签名信息,以便后面进行重签名
        SigningConfig signingConfig = variant.signingConfig
        String apkFilePath
        variant.outputs.all { BaseVariantOutput output ->
            // 拿到apk打包后的路径,一般是在 app -> build -> outputs -> apk -> release 目录下
            apkFilePath = output.outputFile.absolutePath
        }

        // 调用命令行工具执行360加固的登录操作
        project.exec { ExecSpec spec ->
            spec.commandLine(
                    "java", "-jar", uploadApkExtension.reinforceFilePath,
                    "-login", uploadApkExtension.reinforceUserName, uploadApkExtension.reinforcePassword)
        }

        // 调用命令行工具执行360加固的获取签名信息操作
        if (signingConfig) {
            project.exec { ExecSpec spec ->
                spec.commandLine("java", "-jar", uploadApkExtension.reinforceFilePath,
                        "-importsign", signingConfig.storeFile.absolutePath, signingConfig.storePassword,
                        signingConfig.keyAlias, signingConfig.keyPassword)
            }
        }

        // 调用命令行工具执行360加固的加固操作
        project.exec { ExecSpec spec ->
            spec.commandLine("java", "-jar", uploadApkExtension.reinforceFilePath,
                    "-jiagu", apkFilePath, uploadApkExtension.outputDirectory, "-autosign")
        }
    }

}

首先创建一个Task需要继承自DefaultTask,并且这里使用到的 @TaskAction 注解表示该方法是task执行的入口,当双击该task的时候就会执行里面的逻辑。

后面调用命令行的的相关代码都是根据刚刚所说的 help.txt 中的帮助文档来进行编写的,这里唯一要注意的是,因为360加固需要先拿到你的签名信息,然后加固后需要进行重新签名,所以一定要在 app的build.gradle 下填写相关的signingConfigs信息,否则加固后的会是一个未签名的apk文件。

ReinforceTask写完了,接下来我们要来完善UploadApkPlugin的代码了:

class UploadApkPlugin implements Plugin<Project> {
    ...

    @Override
    void apply(Project project) {
        ...
        project.afterEvaluate {
            ...
            androidExtension.applicationVariants.all { ApplicationVariant variant ->
                if (variant.name.equalsIgnoreCase("release")) {
                    // 自定义一个用于加固的Task
                    ReinforceTask reinforceTask = project.tasks.create("reinforceRelease", ReinforceTask)
                    reinforceTask.init(variant)
                    
                    // 修改task的依赖关系
                    variant.getAssembleProvider().get().dependsOn(project.getTasks().findByName("clean"))
                    reinforceTask.dependsOn(variant.getAssembleProvider().get())
                }
            }
        }
    }
}

dependsOn 表示依赖于某个task,这里我们首先定义 assembleRelease 依赖于clean task,然后使reinforceTask依赖于assembleRelease,所以最后我们只需要点击reinforceTask就可以执行打包并加固了。

接下来我们可以打开AS右上角的Gradle,然后会看到以下的目录:

image.png

这里笔者使用的是发布到本地仓库,所以选择的是publishToMavenLocal,双击该task,就可以把插件发布到本地Maven仓库了,大家打开本地Maven仓库就可以看到刚刚发布的插件了。

image.png

  • 对于Mac: 本地Maven仓库默认的路径是 .m2/repository/包名
  • 对于Windows: 本地Maven仓库默认的路径是 C://UsersAdministrator/.m2/epository/包名

接下来我们就可以配置我们的插件了,打开app module下的build.gradle,添加脚本代码如下:

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    // 刚刚定义的插件id
    id 'com.imiyar.upload'
}

uploadApk {
    // 360加固相关
    reinforceUserName "360加固的账号"
    reinforcePassword "360加固的密码"
    reinforceFilePath "刚刚下载的360加固的jar包绝对路径"
    // 加固后输出的目录
    outputDirectory "/Users/sherry/ASProjects/UploadApkPlugin/app/build/outputs/apk/release/"
    // 是否开启加固
    isOpenReinforce true
}

// 然后在android闭包中添加签名相关的信息
android {
    signingConfigs {
        release {
            storeFile file('../upload.keystore')
            storePassword '123456'
            keyAlias 'upload'
            keyPassword '123456'
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

最后我们还要在项目的根目录下配置插件,以及添加 mavenLocal() 用于加载本地maven仓库的插件:

buildscript {
    repositories {
        ...
        mavenLocal()
    }
    dependencies {
        ...
        classpath "com.imiyar.upload:uploadApk:1.0.0"
    }
}

allprojects {
    repositories {
        ...
        mavenLocal()
    }
}

添加完这些信息之后就可以点击Sync Now了,然后我们就会看到AS右侧的Gradle中有我们刚刚定义的Task了

image.png

双击reinforceRelease,就可以执行打包并加固了,下面来看一下加固时打印的部分信息,如果加固失败的时候也是会打印出报错信息的:

image.png

接下来我们来看一下加固后的apk:

image.png

大家也可以试一下是否能安装成功或者反编译看看是否是真正加固的了,这些笔者已经测试过啦~ 现在我们的加固就已经大功告成啦~

上传蒲公英

加固完之后我们需要上传到分发平台,不管这个平台是你个人的还是第三方的都可以,只有上传到分发平台我们才可以拿到这个apk的链接供他人下载安装,这里我们使用的是蒲公英。

那么如何把打包好的apk上传到蒲公英呢?蒲公英文档传送门

开始编写上传蒲公英的相关代码啦,这里我们先在我们自己创建的 UploadApkExtension 添加一些上传时所需要配置的信息:

UploadApkExtension

class UploadApkExtension {
    ...
    
    // ------------ 上传蒲公英相关的扩展 ------------
    String apiKey  // API Key
    String appName // 应用名称
}

然后我们再创建一个用于实现上传蒲公英逻辑的Task,这里命名为 PgyUploadTask ,下面来看一下相关代码:

class PgyUploadTask extends DefaultTask {

    @Internal
    ApplicationVariant variant

    void init(ApplicationVariant variant) {
        this.variant = variant
        description = "Upload apk to Pgyer"
        group = "uploadApk"
    }

    @TaskAction
    void action() {
        // 拿到我们自定义的扩展
        UploadApkExtension uploadApkExtension = project.extensions.findByName(UploadApkPlugin.UPLOAD_APK_EXTENSION)
        // 拿到android扩展AppExtension
        AppExtension appExtension = project.extensions.findByName(UploadApkPlugin.ANDROID_EXTENSION)

        // 这里只是普通的打印,没什么特殊的,只是方便在执行task的时候可以看到相关的信息而已
        println("############################上传蒲公英#############################")
        println("# applicationId : " + variant.getApplicationId())
        println("# versionName   : " + appExtension.defaultConfig.versionName)
        println("# versionCode   : " + appExtension.defaultConfig.versionCode)
        println("# appName       : " + uploadApkExtension.appName)
        println("##################################################################")

        File outputFile
        // 先拿到刚刚加固时定义的输出目录
        File directory = new File(uploadApkExtension.outputDirectory)
        // 判断是否是目录,如果是的话则遍历该目录下的文件,并拿到加固后的apk
        if (directory.isDirectory()) {
            File[] files = directory.listFiles()
            for (File file : files) {
                // 如果开启了加固的话,则匹配输出路径下的apk文件是否是加固后的apk文件
                // 由于我们并不清楚360在加固后生成的名字规则,所以笔者这里是通过匹配 "jiagu" 与 ".apk" 来分辨的
                if (uploadApkExtension.isOpenReinforce) {
                    if (file.getName().contains("jiagu") && file.getName().endsWith(".apk")) {
                        outputFile = file
                        break
                    }
                } else {
                    // 如果没有开启加固的话,则该目录下只会有一个.apk文件,直接拿即可
                    if (file.getName().endsWith(".apk")) {
                        outputFile = file
                        break
                    }
                }
            }
        }
    }
}

这里我们同样是创建一个Task并继承自DefaultTask,然后开始定义我们的上传逻辑。注释写得很清楚,这里的逻辑相对比较简单,由于我们在执行 assembleRelease 的时候已经执行了一遍 clean,所以可以保证该输出目录下只会有当次打包生成的 apk 文件,所以不必担心通过 contains 匹配的文件会不正确。

接下来我们需要用到网络请求了,这里笔者为了方便,只是简单的Java语言封装了一下,不考虑任何性能问题,直接把相关代码贴上来并且不做解释了,相信对于网络请求Retrofit这一块大家都已经很熟悉了。

ApiFactory

public class ApiFactory {

    private static volatile ApiFactory mInstance;

    private ApiFactory() {

    }

    public static ApiFactory getInstance() {
        if (mInstance == null) {
            synchronized (ApiFactory.class) {
                if (mInstance == null) {
                    mInstance = new ApiFactory();
                }
            }
        }
        return mInstance;
    }

    public <T> T create(String baseUrl, Class<T> clazz) {
        return new Retrofit.Builder()
                .baseUrl(baseUrl)
                .client(newClient())
                .addConverterFactory(GsonConverterFactory.create())
                .build()
                .create(clazz);
    }

    private OkHttpClient newClient() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS);
        return builder.build();
    }

    public MultipartBody.Part getFilePart(String mediaType, File file) {
        return MultipartBody.Part.createFormData("file", file.getAbsoluteFile().getName(),
                RequestBody.create(MediaType.parse(mediaType), file));
    }

    public RequestBody getTextBody(String text) {
        return RequestBody.create(MediaType.parse("text/plain"), text);
    }

}

ApiConstants

public interface ApiConstants {
    String PGY_BASE_URL = "https://www.pgyer.com/apiv2/";
}

PgyUploadService

public interface PgyUploadService {

    @Multipart
    @POST("app/upload")
    Call<ResponseBody> uploadFile(@Part("_api_key") RequestBody key,
                                  @Part("buildName") RequestBody buildName,
                                  @Part MultipartBody.Part file);
}

在简单封装好Retrofit相关的代码以及所需要调用的接口之后,我们可以执行真正的上传操作了,现在我们要在上面的 PgyUploadTask 中增加上传的代码:

PgyUploadTask

class PgyUploadTask extends DefaultTask {
    @TaskAction
    void action() {
        ...
        // 在末尾处添加如下代码
        if (outputFile != null) {
            // 这部分不用多说
            PgyUploadService pgyService = ApiFactory.getInstance().create(ApiConstants.PGY_BASE_URL, PgyUploadService.class)
            // 执行上传文件操作
            Response<ResponseBody> appResponse = pgyService.uploadFile(
                ApiFactory.getInstance().getTextBody(uploadApkExtension.apiKey), 
                ApiFactory.getInstance().getTextBody(uploadApkExtension.appName),
                ApiFactory.getInstance().getFilePart("application/vnd.android.package-archive", outputFile)
            ).execute()

            // 打印上传后返回的结果
            println("\nappResponse: ${appResponse.body().string()}")
        } else {
            println("Could not found the apk file")
        }
    }
}

这里需要传入的参数在上面的文档里面都有说明,如果不理解参数的作用的话可以翻上去看一下蒲公英的文档~

定义完Task之后我们需要回到 UploadApkPlugin 中创建我们的task:

class UploadApkPlugin implements Plugin<Project> {
    ...

    @Override
    void apply(Project project) {
        ...
        project.afterEvaluate {
            ...
            androidExtension.applicationVariants.all { ApplicationVariant variant ->
                if (variant.name.equalsIgnoreCase("release")) {
                    // 定义一个用于加固的 Task
                    ReinforceTask reinforceTask = project.tasks.create("reinforceRelease", ReinforceTask)
                    reinforceTask.init(variant)
                    
                    // 定义一个用于把apk上传到蒲公英的 Task
                    PgyUploadTask pgyUploadTask = project.tasks.create("pgyUploadRelease", PgyUploadTask)
                    pgyUploadTask.init(variant)
                    
                    // 修改task的依赖关系
                    variant.getAssembleProvider().get().dependsOn(project.getTasks().findByName("clean"))
                    reinforceTask.dependsOn(variant.getAssembleProvider().get())
                    // 使该task依赖于reinforceTask
                    pgyUploadTask.dependsOn(reinforceTask)
                }
            }
        }
    }
}

此处需要说一下,因为我们并不是每次打包都希望加固的,所以如果我们不需要加固的话,我们可以把UploadApkExtension中 的 isOpenReinforce设为 false ,并在上面的依赖关系中进行判断,由大家自行添加。

接下来我们只需要再修改一下app build.gradle添加我们刚刚设置好的扩展就可以啦~

build.gradle

uploadApk {
    // 360加固相关
    reinforceUserName "360加固的账号"
    reinforcePassword "360加固的密码"
    reinforceFilePath "刚刚下载的360加固的jar包绝对路径"
    // 加固后输出的目录
    outputDirectory "/Users/sherry/ASProjects/UploadApkPlugin/app/build/outputs/apk/release/"
    // 是否开启加固
    isOpenReinforce true
    
    // 上传蒲公英相关
    apiKey "没有apiKey的话需要在上面给出的官方文档进行申请"
    appName "UploadApkPlugin"
}

修改完之后记得先把刚刚写好的引入插件id相关的代码都先注释掉,然后再重新双击publishToMavenLocal,等发布到本地maven仓库后再把这些相关的代码放出来,重新Sync Now,只有这样才能保证maven仓库中的插件代码是最新的。

下面我们来执行一下 pgyUploadRelease ,由于我们在定义了 pgyUploadRelease dependsOn reinforceRelease ,所以我们在执行 pgyUploadRelease 的时候会先执行 reinforceRelease ,所以不用担心不会进行加固。

image.png

来看一下上传到蒲公英后打印出来的相关日志,我们在上传成功后会返回apk的文件链接,这里由于打印的日志太长了,后面就无法截图了,大家可以看文档,这里再放一下文档的传送门

image.png

发送到钉钉消息

当上传到蒲公英后我们肯定是希望分享出去让大家下载的,所以在这里我们使用钉钉来进行分享,这样的好处在于当我们在执行打包的时候我们也就不需要时时刻刻盯着打包进度了,因为当打完包上传完之后就会自动发送到钉钉群消息,所以我们就能知道此时已经打包完成了。

下面我们来了解一下如何把消息发送到钉钉吧,我们先来做一下前置工作,首先我们需要看一下接入钉钉的文档 传送门 ,根据该文档,我们需要先在钉钉建一个群,然后自定义一个机器人,再根据文档去调用接口并传参就好了。

image.png

这里要注意的是,在安全设置部分,如果你选择了“加签”的话,你在接入接口的时候要额外看另一篇文档,安全设置传送门,这里笔者使用的安全设置是自定义关键词,表示只有你传的参数中包含这个关键词才可以发送成功。

添加好钉钉机器人后,我们就可以开始写发送钉钉消息的Task啦,首先我们还是需要先在UploadApkExtension 添加相关的扩展属性,如下所示:

class UploadApkExtension {
    // ----------- 发送钉钉消息相关的扩展 -----------
    String webHook // 钉钉机器人WebHook地址
}

这里比较简单,只添加了webHook,如果在安全设置那里选择了加签的话,则还需要把secret添加进来。定义完扩展之后我们就可以开始定义我们的发送到钉钉消息的Task了,这里命名为 SendMsgToDingTalkTask :

class SendMsgToDingTalkTask extends DefaultTask {

    @Internal
    ApplicationVariant variant
    
    // 需要发送到钉钉供大家打开的短链接,需要跟 "http://www.pgyer.com/" 进行拼接
    static String mShortCutUrl
    // 需要发送到钉钉让大家进行扫描下载的二维码图片链接
    static String mQRCodeURL

    void init(ApplicationVariant variant) {
        this.variant = variant
        description = "Send message to DingTalk"
        group = "uploadApk"
    }
    
    // 把上传到蒲公英成功后返回的 短链接 还有下载的 二维码链接 传过来
    static void setUrl(String shortCutUrl, String qrUrl) {
        mShortCutUrl = shortCutUrl
        mQRCodeURL = qrUrl
    }
    
    @TaskAction
    void action() {
        UploadApkExtension uploadApkExtension = project.extensions.findByName(UploadApkPlugin.UPLOAD_APK_EXTENSION)
        AppExtension appExtension = project.extensions.findByName(UploadApkPlugin.ANDROID_EXTENSION)
        
        // 不明白这些参数的意义的话可以移步先看下面的参数说明
        Link link = new Link()
        link.picUrl = mQRCodeURL
        link.messageUrl = "http://www.pgyer.com/" + mShortCutUrl
        link.title = uploadApkExtension.appName + "正式版"
        link.text = "版本${appExtension.defaultConfig.versionName}"

        DingTalkRequest request = new DingTalkRequest(link, "link")

        // 跟apk上传到蒲公英一样,我们都需要先定义一个接口,然后再执行网络操作
        // SendDingTalkService 代码下面会给出
        SendDingTalkService dingTalkService = ApiFactory.getInstance().create(ApiConstants.DING_TALK_BASE_URL, SendDingTalkService.class)
        Response<ResponseBody> appResponse = dingTalkService.sendMsgToDingTalk(uploadApkExtension.webHook, request)
                .execute()

        // 这里打印看结果
        println("\nDingTalkMsgResponse:" + new Gson().toJson(appResponse.body().string()))
    }
    
    // 定义发送钉钉消息需要传的相关参数
    static class DingTalkRequest {
        String msgtype
        Link link

        DingTalkRequest(Link link, String msgtype) {
            this.link = link
            this.msgtype = msgtype
        }
    }

    static class Link {
        String picUrl
        String messageUrl
        String title
        String text
    }
    
}

跟之前创建的task一样,我们都需要先拿到我们定义的扩展以及 AGP 中的 android 扩展,这里我们传的是 Link 类型,具体可以看上面给出的文档中的 link类型 ,里面有对应的参数说明,这里为了方便大家查看,直接把参数说明截图过来了:

image.png

接下来来定义一下我们用到的接口,如下所示:

ApiConstants

public interface ApiConstants {
    String DING_TALK_BASE_URL = "https://oapi.dingtalk.com/";
}

SendDingTalkService

public interface SendDingTalkService {

    @POST("robot/send")
    Call<ResponseBody> sendMsgToDingTalk(
            @Query("access_token") String accessToken,
            @Body SendMsgToDingTalkTask.DingTalkRequest request
    );

}

有关发送钉钉的代码我们写完了,接下来需要修改一下PgyUploadTask的代码了,毕竟我们需要把短链接还有二维码链接传过来呢。

PgyUploadTask

class PgyUploadTask extends DefaultTask {

    @TaskAction
    void action() {
        // 接下来我们来修改一下这部分代码,大家可以对照着修改
        if (outputFile != null) {
            PgyUploadService pgyService = ApiFactory.getInstance().create(ApiConstants.PGY_BASE_URL, PgyUploadService.class)
            Response<ResponseBody> appResponse = pgyService.uploadFile(ApiFactory.getInstance().getTextBody(uploadApkExtension.apiKey), ApiFactory.getInstance().getTextBody(uploadApkExtension.appName),
                    ApiFactory.getInstance().getFilePart("application/vnd.android.package-archive", outputFile))
                    .execute()
            
            // 以下为需要修改的代码
            String result = appResponse.body().string()
            // 使用Gson把返回的数据转成我们需要的object对象
            PgyResponse response = new Gson().fromJson(result, PgyResponse.class)
            if (response != null) {
                // 把相关信息传到SendMsgToDingTalkTask
                SendMsgToDingTalkTask.setUrl(response.data.buildShortcutUrl, response.data.buildQRCodeURL)
            }
        } else {
            println("Could not found the apk file")
        }
    }

    // 在PgyUploadTask的最后加上这个object的定义
    // 这个object对应的是上传蒲公英成功后返回的数据
    static class PgyResponse {
        public int code
        public String message
        public PgyDetail data

        // 这里拿的只是我们需要的,实际上还有返回很多数据,本文暂且不需要用到
        static class PgyDetail {
            public String buildShortcutUrl
            public String buildQRCodeURL
            public String buildIcon
        }
    }
}

修改完了PgyUploadTask,我们还需要修改我们的UploadApkPlugin,毕竟我们还没创建这个task呢,也就增加几行代码而已~

class UploadApkPlugin implements Plugin<Project> {
    ...

    @Override
    void apply(Project project) {
        ...
        project.afterEvaluate {
            ...
            androidExtension.applicationVariants.all { ApplicationVariant variant ->
                if (variant.name.equalsIgnoreCase("release")) {
                    // 定义一个用于加固的 Task
                    ReinforceTask reinforceTask = project.tasks.create("reinforceRelease", ReinforceTask)
                    reinforceTask.init(variant)
                    
                    // 定义一个用于把apk上传到蒲公英的 Task
                    PgyUploadTask pgyUploadTask = project.tasks.create("pgyUploadRelease", PgyUploadTask)
                    pgyUploadTask.init(variant)
                    
                    // 定义发送钉钉消息的task
                    SendMsgToDingTalkTask dingTalkTask = project.tasks.create("sendMsgRelease", SendMsgToDingTalkTask)
                    dingTalkTask.init()
                    
                    // 修改task的依赖关系
                    variant.getAssembleProvider().get().dependsOn(project.getTasks().findByName("clean"))
                    reinforceTask.dependsOn(variant.getAssembleProvider().get())
                    pgyUploadTask.dependsOn(reinforceTask)
                    // 依赖于pgyUploadTask
                    dingTalkTask.dependsOn(pgyUploadTask)
                }
            }
        }
    }
}

我们需要再添加一下我们刚刚定义的扩展webHook~

build.gradle

uploadApk {
    ...
    
    // 钉钉机器人
    webHook "进入刚刚创建的机器人管理页面可以看得到这个webHook"
}

好啦,还记得我们刚刚说的吗?在发布插件前需要先注释掉这些相关的插件id还有扩展的代码,这里不重复刚刚的步骤了,直接看执行该task后的结果了:

点击sengMsgRelease:

image.png

看一下打印的结果:

image.png

收到的钉钉消息:

image.png

从这条消息点进去就可以进到蒲公英的下载链接了~

好啦,完结 撒花✿✿

最后贴一下本文所有代码的github地址