Gradle之自定義插件的三種方式

Android Gradle插件中,包含了一些task可以幫我們做一些編譯、引入依賴、打包等工作,比如assembleBuild,clean等等??梢允褂枚喾N語言來實(shí)現(xiàn)Gradle插件,其實(shí)只要最終被編譯為JVM字節(jié)碼的都可以,常用的有Groovy、Java、Kotlin。

自定義gradle插件的官方網(wǎng)址

比如在模塊的build.gradle下需要引入的插件,這兩個(gè)插件就是兩個(gè)java程序。

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}
什么是 Gradle 插件

Gradle 和 Gradle 插件是兩個(gè)完全不同的概念,Gradle 提供的是一套核心的構(gòu)建機(jī)制,而 Gradle 插件則是運(yùn)行在這套機(jī)制上的一些具體構(gòu)建邏輯,本質(zhì)上和 .gradle 文件是相同。例如,我們熟悉的編譯 Java 代碼的能力,都是由插件提供的。

Gradle 插件的優(yōu)點(diǎn)

Gradle 插件使用了獨(dú)立模塊封裝構(gòu)建邏輯,無論是從開發(fā)開始使用來看,Gradle 插件的整體體驗(yàn)都更友好。

1.邏輯復(fù)用: 將相同的邏輯提供給多個(gè)相似項(xiàng)目復(fù)用,減少重復(fù)維護(hù)類似邏輯開銷。當(dāng)然 .gradle 文件也能做到邏輯復(fù)用,但 Gradle 插件的封裝性更好;
2.組件發(fā)布: 可以將插件發(fā)布到 Maven 倉庫進(jìn)行管理,其他項(xiàng)目可以使用插件 ID 依賴。當(dāng)然 .gradle 文件也可以放到一個(gè)遠(yuǎn)程路徑被其他項(xiàng)目引用;
3.構(gòu)建配置: Gradle 插件可以聲明插件擴(kuò)展來暴露可配置的屬性,提供定制化能力。當(dāng)然 .gradle 文件也可以做到,但實(shí)現(xiàn)會(huì)麻煩些。

Gradle 插件的核心類是 Plugin,一般使用 Project 作為泛型實(shí)參。當(dāng)使用方引入插件后,其實(shí)就是調(diào)用了 Plugin#apply() 方法,我們可以把 apply() 方法理解為插件的執(zhí)行入口。例如:

public class MyPlugin implements Plugin<Project> {

    @Override
    public void apply(Project project) {
        System.out.println("This is my plugin");
    }
}
應(yīng)用插件的步驟

1、將插件添加到 classpath: 將插件添加到構(gòu)建腳本的 classpath 中,我們的 Gradle 構(gòu)建腳本才能應(yīng)用插件。這里區(qū)分本地依賴和遠(yuǎn)程依賴兩種情況。
本地依賴: 指直接依賴本地插件源碼,一般在調(diào)試插件的階段是使用本地依賴的方式。例如:

buildscript {
    ...
    dependencies {
        // For Debug
        classpath project(":myplugin")
    }
}

遠(yuǎn)程依賴: 指依賴已發(fā)布到 Maven 倉庫的插件,一般我們都是用這種方式依賴官方或第三方實(shí)現(xiàn)的 Gradle 插件。例如:

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.0.2"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30"
    }
    ...
}

2、使用 apply 應(yīng)用插件: 在需要使用插件的 .gradle 腳本中使用 apply 應(yīng)用插件,這將創(chuàng)建一個(gè)新的 Plugin 實(shí)例,并執(zhí)行 Plugin#apply() 方法。例如:

apply plugin: 'com.android.application'
// 或者
plugins {
    id 'com.android.application'
}
開發(fā)Gradle插件有3種方式,如下:

以下示例Demo Github下載地址:

CreateGradlePlugin

1.直接引用插件

創(chuàng)建java模塊,寫一個(gè)自定義Plugin

public class MyPlugin implements Plugin<Project> {

    @Override
    public void apply(Project project) {
        System.out.println("This is my plugin");
    }
}

在引入模塊下比如app模塊下,引用該P(yáng)lugin

import com.github.buildsrc.MyPlugin

apply plugin: MyPlugin
2.通過特殊的 buildSrc 模塊寫插件

插件模塊的名稱是任意的,除非使用了一個(gè)特殊的名稱 “buildSrc”,buildSrc 模塊是 Gradle 默認(rèn)的插件模塊。buildSrc 模塊本質(zhì)上和普通的插件模塊是一樣的,有一些小區(qū)別:

1、buildSrc 模塊會(huì)被自動(dòng)識(shí)別為參與構(gòu)建的模塊,因此不需要在 settings.gradle 中使用 include 引入,就算引入了也會(huì)編譯出錯(cuò)
2、buildSrc 模塊會(huì)自動(dòng)被添加到構(gòu)建腳本的 classpath 中,不需要手動(dòng)添加
3、buildSrc 模塊的 build.gradle 執(zhí)行時(shí)機(jī)早于其他 Project

開發(fā)Gradle插件入門示例:

使用buildSrc目錄方法。

1.在項(xiàng)目下創(chuàng)建Directory,命名為buildSrc,然后創(chuàng)建目錄src/main/java。

2.同步一下項(xiàng)目,會(huì)在該目錄下生成build目錄。


3.在java目錄下創(chuàng)建包,并創(chuàng)建一個(gè)自己的插件類,比如命名BuildSrcPlugin。

class BuildSrcPlugin implements Plugin<Project> {
    @Override
    public void apply(Project target) {
            System.out.println("This is buildSrc plugin");
    }
}

在buildSrc目錄下寫代碼特殊性:
1.它是提供給各個(gè)模塊guild.gradle使用的
2.這個(gè)模塊默認(rèn)會(huì)有g(shù)radle所需庫
3.這個(gè)模塊不需要setting.gradle去引用

在模塊 build.gradle 文件中增加以下配置,gradlePlugin 定義了插件 ID 和插件實(shí)現(xiàn)類的映射關(guān)系:

gradlePlugin {
    plugins {
        buildsrc {
            // Plugin id.
            id = 'com.github.buildsrc'
            // Plugin implementation.
            implementationClass = 'com.github.buildsrc.BuildSrcPlugin'
        }
    }
}

這其實(shí)是 Java Gradle Plugin 提供的一個(gè)簡化 API,其背后會(huì)自動(dòng)幫我們創(chuàng)建一個(gè) [插件ID].properties 配置文件,Gradle 就是通過這個(gè)文件類進(jìn)行匹配的。如果你不使用 gradlePlugin API,直接手動(dòng)創(chuàng)建 [插件ID].properties 文件,作用是完全一樣的。


properties內(nèi)容:

implementation-class=com.github.customplugin.CustomPlugin

4.使用該插件,在需要引入插件的build文件中引入:

plugins {
    id 'com.github.buildsrc'  //引用buildSrc下聲明的插件
}

在執(zhí)行build文件時(shí),執(zhí)行該行代碼,會(huì)加載該類,去執(zhí)行BuildSrcPlugin的apply方法。

打印apply方法里的文本:

3.獨(dú)立模塊發(fā)布插件

1.新建Java module


2.在build.gradle中應(yīng)用gradle插件:

plugins {
    id 'java-gradle-plugin'
}

寫一個(gè)Plugin類

public class CustomPlugin implements Plugin<Project> {
    @Override
    public void apply(Project project) {
        System.out.println("This is a custom plugin");
    }
}

聲明一個(gè)插件id以及對(duì)應(yīng)Plugin類:

gradlePlugin {
    plugins {
        customplugin {
            // Plugin id.
            id = 'com.github.customplugin'
            // Plugin implementation.
            implementationClass = 'com.github.customplugin.CustomPlugin'
        }
    }
}

定義發(fā)布代碼到本地maven-push配置:

apply plugin: 'maven-publish'
publishing {
    publications{
        maven(MavenPublication) {
            groupId "com.github.custom"
            artifactId 'CustomPlugin'
            version "1.0.0"
            //如果是war包填寫components.web,如果是jar包填寫components.java
            from components.java
        }
    }

    repositories {
        maven {
            url = "../repo"
        }
    }
}

在task任務(wù)中使用pushing發(fā)布jar包到本地:


在本地生成jar包倉庫:


添加該插件classpath:

buildscript {
    dependencies {
        classpath "com.github.custom:CustomPlugin:1.0.0"
    }
}

在模塊級(jí) build.gradle 文件中 apply 插件:

plugins {
    id 'com.github.customplugin'
}

CustomPlugin的apply方法執(zhí)行:


Screenshot 2023-06-05 at 14.34.28.png

我們能做哪些gradle插件

  • Apk防破解插件:
    利用破解之后打包的簽名與我們自己的包簽名不相同來處理,這里RuntimeException的拋出也可以用字節(jié)碼插樁,將崩潰處理的代碼插到啟動(dòng)activity里,讓破解的包無法再正常運(yùn)行。
public class AntiCrackPlugin implements Plugin<Project> {

    @Override
    public void apply(Project project) {
        TaskProvider<Jar> jarTask = project.getTasks().named("jar", Jar.class);

        // 在打包任務(wù)之前執(zhí)行防破解邏輯
        jarTask.configure(task -> {
            task.doFirst("AntiCrack", t -> {
                System.out.println("Running anti-crack checks...");

                // 添加你的防破解邏輯,例如檢查簽名、防止二次打包等
                // 這里只是一個(gè)簡單的示例,實(shí)際應(yīng)用需要更加復(fù)雜的邏輯
                if (isCracked(project)) {
                    throw new RuntimeException("The application has been cracked!");
                }
            });
        });
    }

    private boolean isCracked(Project project) {
        // 添加你的防破解邏輯,例如檢查簽名、防止二次打包等
        // 這里只是一個(gè)簡單的示例,實(shí)際應(yīng)用需要更加復(fù)雜的邏輯

        // 獲取應(yīng)用的簽名信息
        String expectedSignature = "your_expected_signature"; // 期望的簽名信息,可以從安全的渠道獲取

        try {
            JarFile jarFile = new JarFile(project.getTasks().getByPath("jar").getOutputs().getFiles().getSingleFile());
            Certificate[] certificates = jarFile.getJarEntry("META-INF/MANIFEST.MF").getCodeSigners()[0].getSignerCertPath().getCertificates();
            StringBuilder actualSignature = new StringBuilder();
            for (Certificate certificate : certificates) {
                actualSignature.append(certificate.getPublicKey().toString());
            }

            // 比較實(shí)際簽名和期望簽名
            return !actualSignature.toString().equals(expectedSignature);
        } catch (Exception e) {
            e.printStackTrace();
            return true; // 發(fā)生異常時(shí)可能是被篡改過的APK,視為破解
        }
    }
}

  • 隱私合規(guī)處理的gradle插件
    在Android應(yīng)用中處理隱私合規(guī)性是非常重要的,特別是在涉及用戶隱私信息的處理方面。首先,讓我們考慮一些隱私合規(guī)性的基本要求:

權(quán)限檢查: 確保應(yīng)用只請(qǐng)求和使用了必要的權(quán)限。
隱私政策鏈接: 確保應(yīng)用中包含隱私政策鏈接,并提供用戶訪問的方式。
數(shù)據(jù)收集通知: 如果應(yīng)用收集用戶數(shù)據(jù),確保提供了適當(dāng)?shù)耐ㄖ陀脩敉狻?/strong>

我們可以創(chuàng)建一個(gè)Gradle插件,執(zhí)行這些檢查:

public class PrivacyCompliancePlugin implements Plugin<Project> {

    @Override
    public void apply(Project project) {
        TaskProvider<PrivacyComplianceCheckTask> privacyCheckTask = project.getTasks().register("privacyCheck", PrivacyComplianceCheckTask.class);

        project.afterEvaluate(p -> {
            // 在構(gòu)建前執(zhí)行隱私合規(guī)性檢查
            project.getTasks().getByName("assemble").dependsOn(privacyCheckTask);
        });
    }
}

然后,創(chuàng)建一個(gè)任務(wù)用于執(zhí)行實(shí)際的隱私合規(guī)性檢查:

public class PrivacyComplianceCheckTask extends DefaultTask {

    @TaskAction
    public void checkPrivacyCompliance() {
        // 添加你的隱私合規(guī)性檢查邏輯
        checkPermissions();
        checkPrivacyPolicyLink();
        checkDataCollectionNotice();
    }

    private void checkPermissions() {
    System.out.println("Checking permissions...");

    List<String> requiredPermissions = Arrays.asList(
            "android.permission.CAMERA",
            "android.permission.WRITE_EXTERNAL_STORAGE",
            // 添加其他需要的權(quán)限
    );

    Set<String> missingPermissions = new HashSet<>(requiredPermissions);
    missingPermissions.removeAll(getDeclaredPermissions());

    if (!missingPermissions.isEmpty()) {
        throw new RuntimeException("Missing required permissions: " + missingPermissions);
    }
}

private Set<String> getDeclaredPermissions() {
    AndroidManifest androidManifest = new AndroidManifest(getProject().file("src/main/AndroidManifest.xml"));
    return androidManifest.getPermissions();
}

private void checkPrivacyPolicyLink() {
    System.out.println("Checking privacy policy link...");

    String privacyPolicyLink = getPrivacyPolicyLink();
    
    if (privacyPolicyLink == null || privacyPolicyLink.isEmpty()) {
        throw new RuntimeException("Privacy policy link is missing or empty.");
    }
}

private String getPrivacyPolicyLink() {
    // 在這里,你可以通過讀取資源文件或其他配置方式獲取隱私政策鏈接
    return "https://www.example.com/privacy-policy";
}

   private void checkDataCollectionNotice() {
    System.out.println("Checking data collection notice...");

    boolean dataCollectionEnabled = isDataCollectionEnabled();
    
    if (!dataCollectionEnabled) {
        throw new RuntimeException("Data collection is not enabled.");
    }
}

private boolean isDataCollectionEnabled() {
    // 在這里,你可以通過讀取應(yīng)用的配置文件或其他方式獲取數(shù)據(jù)收集的狀態(tài)
    return true; // 假設(shè)數(shù)據(jù)收集是啟用的
}
}

參考:
https://blog.csdn.net/wumeixinjiazu/article/details/124691763
https://segmentfault.com/a/1190000041856822

Demo地址:

https://github.com/running-libo/CreateGradlePlugin

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容