欢迎经过我的个人博客来拜访此网站(clwater.top)

一切的一切到要从阿里云emas移动推送说起, 这玩意的接入进程暂且不表, 有爱好的能够看一看, 尤其是其控制台. 接入后首要需求在build.gradle中进行装备, 一起还有一份装备文件在app目录下. 可是emas的装备文件只能装备一份, (甚至不能实时更新, 需求gradle clean), 而实践的开发运用中或许包含开发,测验和上线环境的装备. 这些装备就需求咱们在构架的时分主动匹配为最近的装备文件.

0.在开端之前

其实最开端的完成方法并不是这篇文章运用的Android Gradle Plugin的办法来处理, 遇到这个状况的时分, 第一个想到的办法是在build.gradle中修正task来完成, 可是阿里云emas移动推送是经过Gradle Plugin的形式引进的, 假如task来完成的话会在emas plugin履行后再履行咱们的task, 这样就导致了对应的装备文件或许没有更新成功问题的产生.

第二个的想到的办法是经过shell在gradle build履行前处理对应的文件. 不过由所以团队协作的项目, 更改项意图构架办法或许会影响到其他人, 秉承着对他人影响最小的原则, 这个办法就不适宜了.

终究便是现在的完成办法, 依据Gradle Plugin的相关特性, 咱们经过自定义咱们自己的Android Gradle Plugin来帮咱们构架咱们的项目.

1.开端运用Android Gradle Plugin来协助你构建的项目

什么是Android Gradle Plugin插件?

开始使用Android Gradle Plugin来帮助你进行项目的构建

当咱们创立一个新的项意图时分, 在默许的装备文件中咱们就能看到以下运用的插件信息. 当然不仅仅官方能够发布插件, 咱们也能够创立和发布咱们自己的插件. 用官方的解释来说便是1:”Android Gradle 插件 (AGP) 是官方的 Android 运用构建体系。该体系支撑编译多种不同类型的源代码,以及将其链接到可在实体 Android 设备或模拟器上运转的运用中。” 简略归纳来说, Android Gradle Plugin便是能够在Android构架的时分将那些可重复运用的构建逻辑抽出来, 作为一个独立的项目/插件, 运用在不同的项目构建中. 例如多途径打包, 修正图片, 紧缩图片等相关的操作都能够作为一个插件来运用到不同的项目中.

Gradle和Android Gradle Plugin的差异?

Gradle和Android Gradle Plugin是两个不同的方向和功用, Gradle是用来进行项意图构建, 而Android Gradle Plugin是一个构建的动作, 和咱们项目中的.gradle文件功用相同.

2.为什么要运用Android Gradle Plugin?

或许在初度触摸到相似问题的时分都会有这样的问题, 这玩应到底有啥用?

假如你打包过.apk, 那么你便是Android Gradle Plugin的头号潜在用户.

咱们能够构建咱们自己的Android Gradle Plugin来协助咱们完成以下的功用

  • 资源的预处理: 紧缩图片, 修正资源内容
  • 装备文件的处理: 依据不同flavor更新不同的装备文件内容
  • 自定义规矩增加: 针对项目中自定的规矩进行查看
  • AOP: 切面增加相关代码

当然, Android Gradle Plugin能做的不仅仅是这些, 咱们还能够依据咱们自己的需求来修正创立满意自己的特别功用的Android Gradle Plugin

3. 创立咱们自己的Android Gradle Plugin

Android Gradle Plugin有三种创立的办法,

3.1 build.gradle

第一种便是直接在app module的build.gradle文件中增加以下代码

// 编写plugin类
class DemoPlugin implements Plugin<Project>{
    @Override
    void apply(Project project) {
        println "==============="
        println "DemoPlugin"
        println "==============="
    }
}
// 运用自定义Plugin
apply plugin: DemoPlugin

咱们Sync后就能够在Build窗口中看到以下的数据了.

开始使用Android Gradle Plugin来帮助你进行项目的构建

这种运用的办法最大的有点便是简略简洁, 最大的缺点是过分简略简洁, 最适合的是小功用, 而且没有可移植性, 只能跟着build.gradle文件走.

GitHub文件地址:https://github.com/clwater/First_Gradle_Plugin_of_Android/blob/master/First_Gradle_Plugin_of_Android_Test_1/app/build.gradle

3.2 buildSrc

第二章办法是在项目中创立新的module, 而且命名为buildSrc.

构建后的文件目录结构如下, 具体的概况说明会在后面进行具体的说明.

开始使用Android Gradle Plugin来帮助你进行项目的构建

当然, 在运用的运用仍需求在build.gradle中增加以下内容

apply plugin: 'com.clwater.plugin' // 这个姓名和咱们.properties文件的姓名共同

此刻咱们再sync项目, 能够看到如下输出

开始使用Android Gradle Plugin来帮助你进行项目的构建

这种办法具有必定的复用性, 在项目中的每个module能够运用, 可是对外仍不可运用.

Tips: module只能命名为buildSrc, 而且在settings.gradle将主动增加的include ':buildSrc'移除

GitHub文件地址:https://github.com/clwater/First_Gradle_Plugin_of_Android/blob/master/First_Gradle_Plugin_of_Android_Test_2/app/build.gradle

3.3 Android Gradle Plugin Project

顾名思义, 咱们能够在独立的Project中创立咱们的Android Gradle Plugin,

当然创立的办法也不止一种, 能够酌情依据时间状况来挑选假如进行创立, 以下引荐与否, 全凭主观臆断.

3.3.1 手动创立

纯主观臆断不引荐

因为自己创立的时分需求手动装备关联的内容, 可是这些关联的内容对刚刚触摸此部分的开发者来说, 又十分的零碎, 往往终究不得其法, 导致因为各种或了解或遗失的小问题造成的运转成果不达预期.

3.3.2 经过Gradle创立

既然是Android Gradle Plugin, 那得先是Gradle中能用的, 才干扩展到Android Gradle中, Gradle也提供了一个主动创立的办法, 来防止咱们手动创立时引发的各种问题. 也便利相关流程的规范化.

创立Android Gradle Plugin Project地点的文件夹

这儿咱们之间创立一个文件夹, 用作咱们Android Gradle Plugin对应Project的地点目录

运用gradle init构建项目

此章节运用的gradle指令时, 如无法找到相关指令, 查看gradle是否装置装备, 概况参阅Gradle | Installation: https://gradle.org/install/

咱们在终端中打开刚刚创立的目录, 并进行以下操作

gradle init
Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 4
# 挑选生成的Gradle项目类型, 咱们需求挑选4: Gradle plugin
Select implementation language:
  1: Groovy
  2: Java
  3: Kotlin
Enter selection (default: Java) [1..3] 1
# 挑选插件的编码运用言语, 这儿引荐运用Groovy(相关教程及内容最多)
Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2] 1
# 挑选项意图编译脚本言语(相似build.gradle文件), 引荐运用Groovy, 理由同上
Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no]                                                                                                                       no
# 是否启用一些新的实验性的APIs来进行构建, 假如没有特别需求的话不启用即可
Project name (default: First_Gradle_Plugin_of_Android_Test_3): plugin
# Project的称号, 默许为你对应文件的称号, 实践上是你对应module的称号
Source package (default: plugin): com.clwater.plugin
# 包名的完整地址, 更改为你的地址即可
> Task :init
Get more help with your project: https://docs.gradle.org/8.0/userguide/custom_plugins.html
BUILD SUCCESSFUL in 1m 19s
2 actionable tasks: 2 executed

终究的构建成果如下:

开始使用Android Gradle Plugin来帮助你进行项目的构建

[编写咱们自己的Plugin]

打开默许生成的.groovy文件, 我就就此文件进行简略的修正, 便利后面的步骤进行


// 更改下默许生成的文件名
class ClwaterPlugin implements Plugin<Project> {
    void apply(Project project) {
	    // 输出一下插件被调用
        println("Hello from plugin")
        // 注册一个名叫greeting的task
        project.tasks.register("greeting") {
            doLast {
                println("Hello from plugin 'com.clwater.plugin.greeting'")
            }
        }
    }
}

关于Gradle Task的相关内容, 这儿不再额外赘述, 假如我们感喜欢我的文章风格的话, 我能够再帮我们整理一下.

发布插件

当咱们在Project中创立好并完成了咱们自己的Android Gradle Plugin后, 下一步便是将它提供给第三方,

装备build.gradle

接下来便是在build.gradle中增加额外的发布装备, 构建出来的插件如不装备指定的repositories会在体系默许的mavenLocal中生成, 这儿为了更直观便捷的运用, 咱们将其生成在项目内.

...
plugins {
	...
    id 'maven-publish'
}
...
publishing {
    publications {
        // 此处clwater能够随便写,可是后面的MavenPublication不能随便写
        clwater(MavenPublication) {
            // 插件的组ID
            groupId = 'com.clwater.plugin'
            // 插件的ID
            artifactId = 'Plugin'
            // 插件的版别
            version = '1.0.0'
            // 插件的发布类型
            from components.java
        }
    }
    repositories {
        maven {
            // 插件的发布库房
            name = 'repo'
            // 插件的发布库房地址
            url = "../repo"
        }
    }
}
...

此刻咱们再运用gradle tasks就能够看到咱们的发布task了.

开始使用Android Gradle Plugin来帮助你进行项目的构建

装备插件调用信息

咱们能够看到在main文件夹下有groovyresources两个文件夹, groovy包含了咱们的插件代码, 而resources文件夹则需求包含咱们的插件声明文件.

为了使得咱们的插件能够被调用, 咱们需求装备相关的声明. 首要是在resources目录下增加META-INF, 并在META-INF中增加gradle-plugins, 在gradle-plugins增加com.clwater.plugin.properties, 当然这个文件的姓名咱们能够随便定义. 没有束缚性要求. 终究的文件目录成果如下:

main
├── groovy
│   └── com
│       └── clwater
│           └── plugin
│               └── ClwaterPlugin.groovy
└── resources
    └── META-INF
        └── gradle-plugins
            └── com.clwater.plugin.properties

关于com.clwater.plugin.properties, 咱们需求装备咱们插件的进口, 形式如下:

implementation-class=com.clwater.plugin.ClwaterPlugin
生成到本地

因为我的build.gradle文件中装备的称号为clwater, 所以我的终究的文件生成task应该运用 publishClwaterPublicationToRepoRepository, 当咱们自己开发的时分, 酌情依据自己的状况进行修正.

当然, 你也能够在Android Studio的Gradle窗口中找到相关的构建Task

开始使用Android Gradle Plugin来帮助你进行项目的构建

当咱们的发布task完成后, 就能够发现项目中多了一个repo的目录, 一起咱们的插件也发布到了此处.

开始使用Android Gradle Plugin来帮助你进行项目的构建

GitHub文件地址:https://github.com/clwater/First_Gradle_Plugin_of_Android/blob/master/First_Gradle_Plugin_of_Android_Test_3

项目引进和运用

当咱们的项目构建完成后, 就能够提供给别的项目进行运用了, 咱们先构建一个新的项目,

因为运用的gradle版别为gradle-7.5, 项目中gradle相关文件或许有所不必, 酌情依据项目实践状况来进行装备.

引进

首先是将咱们生成好的Android Gradle Plugin地点的repo文件夹仿制到项目中, 然后依次修正以下文件

settings.gradle
 pluginManagement {
     repositories {
+        gradlePluginPortal()
         google()
         mavenCentral()
-        gradlePluginPortal()
+        //增加本地库房
+        maven {
+            allowInsecureProtocol(true)
+            url uri('./repo')
+        }
     }
 }
 dependencyResolutionManagement {
@@ -10,6 +15,11 @@ dependencyResolutionManagement {
     repositories {
         google()
         mavenCentral()
+        //增加本地库房
+        maven {
+            allowInsecureProtocol(true)
+            url uri('./repo')
+        }
     }
 }
rootProject.name = "First_Gradle_Plugin_of_Android_Test_4"
include ':app'

简略来说便是向两个repositories中增加咱们运用的本地库房

project下的build.gradle
+ buildscript {
+     dependencies {
+         classpath('com.clwater.plugin:Plugin:1.0.0')
+     }
+ }
+ 
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id 'com.android.application' version '7.4.0' apply false
    id 'com.android.library' version '7.4.0' apply false
    id 'org.jetbrains.kotlin.android' version '1.7.21' apply false
}

增加咱们的插件(groupId:Id:version的格局)

app module下的build.gradle
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
+   id 'com.clwater.plugin'
}
android {
    namespace 'com.clwater.first_gradle_plugin_of_android_test_4'
    compileSdk 33
    ...
}

Tips: id ‘com.clwater.plugin’中的内容和咱们装备resources文件夹下的.properties需求完全共同

前期版别构建项目装备文件参阅

前期的项目修正起来比较容易, app下的build.gradle装备完全共同, 咱们要修正的只有项目根目录下的build.gradle


// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
+       maven {
+           url uri('./repo')
+       }
        mavenCentral()
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.2.1'
        classpath 'org.owasp:dependency-check-gradle:6.0.5' // ★追加
+       classpath 'com.clwater.plugin:Plugin:1.0.0'
    }
}
apply plugin: 'org.owasp.dependencycheck' // ★追加
allprojects {
    repositories {
+       maven {
+           url uri('./repo')
+       }
        mavenCentral()
        google()
        jcenter()
    }
}
运用

其实当然引进之后就现已再运用了, 当咱们从头构建项意图时分就能够在build视图中看到如下内容

开始使用Android Gradle Plugin来帮助你进行项目的构建

咱们能够看到在ClwaterPlugin中的输出语句被调用了. 说明咱们的插件现已被履行了.

一起咱们履行./gradlew tasks --all, 就能够找到咱们定义的greetingtask,

开始使用Android Gradle Plugin来帮助你进行项目的构建

当然, 直接履行的时分也是能够的.

开始使用Android Gradle Plugin来帮助你进行项目的构建

Tips: 截止此处, 咱们现已能够经过恣意方法来创立一个Android Gradle Plugin并将其在恣意的项目中运用了, 可是其实还有一个疑问, Android Gradle Plugin 能够协助咱们做什么?

GitHub文件地址:https://github.com/clwater/First_Gradle_Plugin_of_Android/blob/master/First_Gradle_Plugin_of_Android_Test_4

4.经过Android Gradle Plugin来协助咱们修正装备文件(以修正阿里云EMAS推送为例)

前文提到过”假如你打包过.apk, 那么你便是Android Gradle Plugin的头号潜在用户.”, 因为Android Gradle Plugin能够协助咱们做许多不得不做, 有具有重复性的事情.比如此章的状况: 经过Android Gradle Plugin来协助咱们修正装备文件(以修正阿里云EMAS推送为例)

阿里云EMAS是一个第三方的推送插件, 能够进行数据的推送, 接入进程不过去描述, 咱们运用Android Gradle Plugin来处理的问题是: EMAS的装备文件仅有, 无法装备多个文件, 假如不主动化装备的话, 那么上线和测验开发时运用的装备文件要不只能运用一个, 要不需求打包前手动修正, 而只有一套装备文件来开发上线显然是不应该的, 一起假如多套的话, 手动来进行打包前的修正也是不可靠的. 终究仍是需求进行主动化装备.

参阅代码如下:

package com.clwater.plugin
import org.gradle.TaskExecutionRequest
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.invocation.Gradle
class ClwaterPlugin implements Plugin<Project>{
    // 不同的途径
    enum BuildType {
        PRODUCT,
        DEV,
        TEST,
        NONE
        ;
    }
    // project持有
    private Project project
    // 装备文件途径
    // ali push 装备文件途径(preBuild运用)
    private String emasFromFile
    // ali push 装备文件途径(/app实践构建)
    private String emasToFile
    private buildType = BuildType.NONE;
    /**
     * 插件进口
     * step1: 查看xxx(装备文件途径)文件夹是否存在
     * step2: 查看是否构建为出产环境
     * step3: 仿制xxx内容文件夹到app文件夹
     *  step3.1: 仿制ali push装备文件
     * step4: 构建完成后删去相关文件
     * @param project
     */
    @Override
    void apply(Project project) {
        println('==================================================')
        println('ClwaterPlugin is applying')
        this.project = project
        buildType = getBuildType(project)
        println("ClwaterPlugin: buildType: $buildType")
        deleteEAMSCache()
        initBaseConfig()
        if (checkPreBuildFile()){
            throw new GradleException("File not found: $emasFromFile")
        }
        movePreBuildFile()
        project.gradle.buildFinished {
            println('==================================================')
            println('ClwaterPlugin: finished')
            println('ClwaterPlugin: delete cache file')
            deleteCache()
            println('==================================================')
        }
    }
    /**
     * 删去阿里推送emas缓存文件, 防止新的装备文件无法生效
     */
    private void deleteEAMSCache(){
        Gradle gradle = project.gradle
        println(" del:  $project.rootDir/app/build/generated/res/emas-services")
        project.delete("$project.rootDir/app/build/generated/res/emas-services")
        List<TaskExecutionRequest> taskExecutionRequests = gradle.getStartParameter().getTaskRequests()
        for (TaskExecutionRequest taskExecutionRequest: taskExecutionRequests) {
            if (taskExecutionRequest.args.toString().contains("assemble")){
                String variant = taskExecutionRequest.args.toString()
                variant = variant.replace("[", "")
                variant = variant.replace("]", "")
                variant = variant.replace(":app:", "")
                variant = variant.replace("assemble", "")
                variant = "merge" + variant + "Resources"
                // 提取构建variant
                println(" del:  $project.rootDir/app/build/intermediates/incremental/$variant/merger.xml")
                project.delete("$project.rootDir/app/build/intermediates/incremental/$variant/merger.xml")
            }
        }
    }
    /**
     * 初始化基础装备
     */
    private void initBaseConfig(){
        switch (buildType){
            case BuildType.PRODUCT:
                emasFromFile = "xxx/product/aliyun-emas-services.json"
                break
            case BuildType.DEV:
                emasFromFile = "xxx/dev/aliyun-emas-services.json"
                break
            case BuildType.TEST:
                emasFromFile = "xxx/test/aliyun-emas-services.json"
                break
        }
        emasToFile = "$project.rootDir/app"
    }
    /**
     * 仿制preBuild对应文件到app文件夹
     */
    private void movePreBuildFile(){
        moveEmas()
    }
    /**
     * 仿制文件
     * @param fromPath
     * @param toPath
     */
    private void copyFile(String fromPath, String toPath){
        project.copy {
            from fromPath
            into toPath
        }
    }
    /**
     * 仿制装备文件
     */
    private void moveEmas(){
        copyFile(emasFromFile, emasToFile)
    }
    /**
     * 查看是否构建为出产环境
     * @param project
     * @return
     */
    private static BuildType getBuildType(Project project){
        Gradle gradle = project.gradle
        List<TaskExecutionRequest> taskExecutionRequests = gradle.getStartParameter().getTaskRequests()
        for (TaskExecutionRequest taskExecutionRequest: taskExecutionRequests){
            if(taskExecutionRequest.args.toString().contains("assemble")){
                // 查看是否为Dev/Test/Product
            }
        }
        return BuildType.DEV
    }
    /**
     * 查看预构建文件是否存在
     * @return
     */
    private boolean checkPreBuildFile(){
        // 查看emas装备文件是否存在
        File emasFile = new File(emasFromFile)
        return !emasFile.isFile()
    }
    /**
     * 删去相关文件
     */
    private void deleteCache(){
        project.delete(emasToFile+ "/aliyun-emas-services.json")
    }
}

至此, 咱们就完成了针对阿里EMAS的多版别装备文件的主动化装备, 再也不必担心人工装备或许引起的各种问题了.

-1. 终究

相信至此我们现已对Android Gradle Plugin有了必定的了解, 也期望此文能够协助到我们, 更欢迎我们一起交流.

-2 终究的终究

项目完整代码能够拜访:我的GitHub: https://github.com/clwater/First_Gradle_Plugin_of_Android

Footnotes

  1. developer.android.com/studio/buil… ↩