Gradle 自定义插件

     上一篇文章提到了gradle的相关知识和自定义task,这一节我们就讲一下自定义插件的开发。自定义插件在Android中的使用尤为重要。在gradle的相关文档中说,我们可以构建插件的相关代码,直接写在构建脚本中,无需任何操作就能够编译使用插件。但是这样的话,该脚本插件只能在当前构建脚本中可见,不能外部可见,也不能重用。所以要达到重用的目的,就需要将其编写为插件。

buildSrc project

     像上一篇文章讲的我们可以新建一个module,注意得选择java or Kotlin Library 类型的module,并且module的名称必须是buildSrc,这样gradle就会直接编译使用里面的插件,并且可以直接使用里面的类和方法。

新建一个插件类 名称为FirstPlugin 实现Plugin接口:

package com.android.buildsrc;

import org.gradle.api.Plugin;
import org.gradle.api.Project;

public class FirstPlugin implements Plugin<Project> {
    @Override
    public void apply(Project project) {
        System.out.println("插件配置开始执行了额");
    }
}

这其实就是一个简单的插件,当gradle生命周期配置阶段的时候,就会执行里面的apply方法,看到这个apply方法,是不是有点眼熟,我们在build.gradle里面引用插件的时候,用的就是apply 关键字:

import com.android.buildsrc.FirstPlugin

apply plugin: 'com.android.application'
apply plugin: FirstPlugin

上面是直接写的apply plugin:FirstPlugin ,后面的类名,本来是全路径,这里上面用了import 就可以直接写类名了。插件实现的是Plugin接口,泛型的参数类型为Project,这样我们就可以在apply方法里面得到参数为Project类型,可以使用Project得到很多属性,方法,任务,进行一系列的操作。

看下面的代码,得到配置的buildToolsVersion和compileSdkVersion 

AppExtension appExtension = project.getExtensions().getByType(AppExtension.class);
String buildToolsVersion = appExtension.getBuildToolsVersion();
String compileSdkVersion = appExtension.getCompileSdkVersion();

重新编译运行之后,我们会发新,得不到这些参数值,得到的为null,这是为啥呢?.......

因为apply里面的方法是在gradle 生命周期配置阶段执行的,所以我们得不到配置也很正常,如果我们将代码修改为下面这样的:

project.afterEvaluate(new Action<Project>() {
            @Override
            public void execute(Project project) {
                AppExtension appExtension = project.getExtensions().getByType(AppExtension.class);
                String buildToolsVersion = appExtension.getBuildToolsVersion();
                String compileSdkVersion = appExtension.getCompileSdkVersion();
                System.err.println("buildToolsVersion="+buildToolsVersion+"  compileSdkVersion="+compileSdkVersion);
            }
        });

gradle重新编译之后执行效果如下:

gradle生命周期的配置阶段,配置每个模块并不会执行task,或者插件; 配置完了以后,有一个重要的回调project.afterEvaluate,它表示所有的模块都已经配置完了,可以准备执行task了。所以我们我们的AppExtention里面的内容,也需要在配置完成之后才可以获取相关的参数值。

      看到使用AppExtention获取compileSdkVersion或者buildToolsVersion,我们会想到这不就是我在app  module下面android里面配置的相关的值么

其实我们获得的AppExtention获取的就是在android 下面配置的相关内容。上面这个 android 打包配置,就是 Gradle 的 Extension,翻译成中文意思就叫扩展。它的作用就是通过实现自定义的 Extension,可以在 Gradle 脚本中增加类似 android 这样命名空间的配置,Gradle 可以识别这种配置,并读取里面的配置内容。

     我们看到github上很多开源库,在引用他们的时候,他们都要求在build.gradle中配置一些参数,配置参数之后,才能正常使用,这就是他们插件定义的Extention。我们也可以为自己所编写的插件定义一个Extention,让使用插件的人,配置好这些信息,我们就可以从Extention中获取相关的信息。

final PluginExtention pluginExt = project.getExtensions().create("pluginExt", PluginExtention.class);

定义了一个名称为“pluginExt”的 PluginExtention,实体类PluginExtention里面就是存放配置信息。

package com.android.buildsrc;

public class PluginExtention {
    private String userName;
    private String passWord;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    @Override
    public String toString() {
        return "PluginExtention{" +
                "userName='" + userName + '\'' +
                ", passWord='" + passWord + '\'' +
                '}';
    }
}

我们这样就可以在build.gradle中配置pluginExt了

就可以在project.afterEvaluate 回调里面获取用户配置的参数的值了。这样一个简单的自定义插件就完成了。但是感觉这样一个自定义插件好像啥事都干不了啊,我们好像只能在gradle配置阶段执行一些操作,或者获取在build.gradle中配置的一些参数信息。目前是这样的,但是我们可以在自己的插件当中定义自己的任务。

以一个集成360加固到项目中为例,来说明一个插件的运用和编写。

我们都知道360加固是对apk进行加固的,我们一般会将自己需要上传到的市场上的app-release.apk,使用乐固或者360加固工具进行加固,以确保上传到市场的apk更安全,而且现在各个应用市场不接受没有进行加固的apk上传。所以我们可以编写一个插件,在编译打包生成release包的时候,直接去加固。

我们的Extention的配置对应的java文件如下:

package com.android.buildsrc;

public class PluginExtention {
    private String userName;
    private String jiaGuPluginPath;  // '/Users/we/Documents/360jiagubao_mac/jiagu/' //  加固保安装的路劲

    private String storeFilePath;  // file("../store.jks").absolutePath                                                // 签名文件位置

    private String storePassword;   //'123456'                                                             //  密码

    private String keyAlias;   // = 'android'                                                                            // 别名

    private String keyPassword;  // '123456'

    private String jiaGuUserName;  //                                                            //  360加固保用户名

    private String  jiaGuPwd;   //

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getJiaGuPluginPath() {
        return jiaGuPluginPath;
    }

    public void setJiaGuPluginPath(String jiaGuPluginPath) {
        this.jiaGuPluginPath = jiaGuPluginPath;
    }

    public String getStoreFilePath() {
        return storeFilePath;
    }

    public void setStoreFilePath(String storeFilePath) {
        this.storeFilePath = storeFilePath;
    }

    public String getStorePassword() {
        return storePassword;
    }

    public void setStorePassword(String storePassword) {
        this.storePassword = storePassword;
    }

    public String getKeyAlias() {
        return keyAlias;
    }

    public void setKeyAlias(String keyAlias) {
        this.keyAlias = keyAlias;
    }

    public String getKeyPassword() {
        return keyPassword;
    }

    public void setKeyPassword(String keyPassword) {
        this.keyPassword = keyPassword;
    }

    public String getJiaGuUserName() {
        return jiaGuUserName;
    }

    public void setJiaGuUserName(String jiaGuUserName) {
        this.jiaGuUserName = jiaGuUserName;
    }

    public String getJiaGuPwd() {
        return jiaGuPwd;
    }

    public void setJiaGuPwd(String jiaGuPwd) {
        this.jiaGuPwd = jiaGuPwd;
    }

    @Override
    public String toString() {
        return "JiaGuExt{" +
                "userName='" + userName + '\'' +
                ", jiaGuPluginPath='" + jiaGuPluginPath + '\'' +
                ", storeFilePath='" + storeFilePath + '\'' +
                ", storePassword='" + storePassword + '\'' +
                ", keyAlias='" + keyAlias + '\'' +
                ", keyPassword='" + keyPassword + '\'' +
                ", jiaGuUserName='" + jiaGuUserName + '\'' +
                ", jiaGuPwd='" + jiaGuPwd + '\'' +
                '}';
    }
}

在app module下的build.gradle文件下添加Extention的配置:

pluginExt{
    userName 'lingjianglin'
    jiaGuPluginPath '/Users/we/Documents/360jiagubao_mac/jiagu/jiagu.jar' //  加固保安装的路劲
    storeFilePath file("../store.jks").absolutePath                                                // 签名文件位置
    storePassword '123456'                                                             //  密码
    keyAlias 'android'                                                                            // 别名
    keyPassword '123456'
    jiaGuUserName '******'       //这里配置的是 你的360加固的用户名   替换****                                                       
    jiaGuPwd '******'    //360加固的密码  用自己的密码替换****
} 

我们分析,我们的加固必须在打出release包之后才能进行,因此我们的加固操作必须在执行release任务之后完成,这个时候我们就需要去看执行打包任务的是啥了?我们知道打包任务就是assembleDebug 或者assembleRelease了,因此我们需要将我们的加入的加入任务放在这个打包任务之后执行,这时候就可以用到doLast了。加固插件中apply方法的主要实现逻辑:

//获取到用户配置的加固需要的相关信息,供加固的时候使用
 final PluginExtention pluginExt = project.getExtensions().create("pluginExt", PluginExtention.class);
        project.afterEvaluate(new Action<Project>() {
            @Override
            public void execute(final Project project) {
   // AppExtension获取的时候Application插件的配置信息
                AppExtension appExtension = project.getExtensions().getByType(AppExtension.class);
   //得到ApplicationVariants 然后遍历
                appExtension.getApplicationVariants().all(new Action<ApplicationVariant>() {
                    @Override
                    public void execute(ApplicationVariant applicationVariant) {
                 //得到app的 outputs的相关目录
                        applicationVariant.getOutputs().all(new Action<BaseVariantOutput>() {
                            @Override
                            public void execute(BaseVariantOutput baseVariantOutput) {

                                final  File outPutFile = baseVariantOutput.getOutputFile();
                                System.err.println(outPutFile.getAbsolutePath());
                                //这里的name  就是我们配置的debug release或者flavor相关配置
                                String name = baseVariantOutput.getName();
                                String taskName = "assemble" + Character.toUpperCase(name.charAt(0))+name.substring(1);
                                final Task assembleTask =
                                        project.getTasks().findByName(taskName);
                                assembleTask.doLast(new Action<Task>() {
                                    @Override
                                    public void execute(Task task) {
                                        jiagu(project,pluginExt,outPutFile);
                                    }
                                });
                            }
                        });
                    }
                });
            }
        });

根据注释可以看的很清楚了,我们可以根据名称找到对应的task任务,然后设置Task任务的doLast,我们在doLast中执行我们的加固任务。加固任务执行很容易,就直接根据 360加固相关文档上的命令行执行加固一样。

private void jiagu(Project project,final PluginExtention jiaGuExt,final File apk){
        project.exec(new Action<ExecSpec>() {
            @Override
            public void execute(ExecSpec execSpec) {
                execSpec.commandLine("java","-jar",jiaGuExt.getJiaGuPluginPath(),
                        "-login",jiaGuExt.getJiaGuUserName(),jiaGuExt.getJiaGuPwd());
            }
        });
        project.exec(new Action<ExecSpec>() {
            @Override
            public void execute(ExecSpec execSpec) {
                execSpec.commandLine("java","-jar",jiaGuExt.getJiaGuPluginPath(),
                        "-importsign",jiaGuExt.getStoreFilePath(),jiaGuExt.getStorePassword(),jiaGuExt.getKeyAlias(),jiaGuExt.getKeyPassword());
            }
        });
        project.exec(new Action<ExecSpec>() {
            @Override
            public void execute(ExecSpec execSpec) {
                execSpec.commandLine("java","-jar",jiaGuExt.getJiaGuPluginPath(),
                        "-jiagu",apk.getAbsolutePath(),apk.getParent());
            }
        });
    }

Project提供专门的exec 的闭包来执行相关的命令行,不止只能执行加固,我们可以执行java的相关命令,git的相关命令,都可以的,这样写完之后,我们就可执行assembleRelease或者assembleDebug任务,执行这个任务之后,就直接执行360加固了。

这样会存在一个问题,就是我们执行打包之后,就立马执行加固,有的时候,我们自己测试使用,或者打包,不需要发布到应用市场的时候,就不需要加固,这样可以节省打包的时间,我们可以将插件的任务独立出来,等我们需要加固的时候,自己单独点击加固就行了。这样更符合需求。这个时候,我们需要将加固的操作单独抽离到任务当中来。这时候Plugin中的代码变成下面的了,需要创建一个任务了

 @Override
    public void apply(Project project) {
        final PluginExtention pluginExt = project.getExtensions().create("pluginExt", PluginExtention.class);
        project.afterEvaluate(new Action<Project>() {
            @Override
            public void execute(final Project project) {
                AppExtension appExtension = project.getExtensions().getByType(AppExtension.class);
                appExtension.getApplicationVariants().all(new Action<ApplicationVariant>() {
                    @Override
                    public void execute(ApplicationVariant applicationVariant) {
                        applicationVariant.getOutputs().all(new Action<BaseVariantOutput>() {
                            @Override
                            public void execute(BaseVariantOutput baseVariantOutput) {
                                final  File outPutFile = baseVariantOutput.getOutputFile();
                                System.err.println(outPutFile.getAbsolutePath());
                                String name = baseVariantOutput.getName();
                                project.getTasks().create("jiagu_"+name,JiaGuTask.class,pluginExt,outPutFile);
                            }
                        });
                    }
                });
            }
        });
    }

创建了jiagu_debug 或者jiagu_release任务,将360加固的代码放在任务当中执行。

package com.android.buildsrc;

import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;
import org.gradle.process.ExecSpec;

import java.io.File;

import javax.inject.Inject;


public class JiaGuTask extends DefaultTask {

    private final PluginExtention jiaGuExt;
    private final File apk;
    @Inject
    public JiaGuTask(PluginExtention jiaGuExt, File outputsFile){
        setGroup("jiagu");
        this.jiaGuExt = jiaGuExt;
        this.apk = outputsFile;
    }

   //@TaskAction 注解标注的方法,才是任务执行的时候需要执行的代码
    @TaskAction
    private void doJiaGu(){
        getProject().exec(new Action<ExecSpec>() {
            @Override
            public void execute(ExecSpec execSpec) {
                execSpec.commandLine("java","-jar",jiaGuExt.getJiaGuPluginPath(),
                        "-login",jiaGuExt.getJiaGuUserName(),jiaGuExt.getJiaGuPwd());
            }
        });
        getProject().exec(new Action<ExecSpec>() {
            @Override
            public void execute(ExecSpec execSpec) {
                execSpec.commandLine("java","-jar",jiaGuExt.getJiaGuPluginPath(),
                        "-importsign",jiaGuExt.getStoreFilePath(),jiaGuExt.getStorePassword(),jiaGuExt.getKeyAlias(),jiaGuExt.getKeyPassword());
            }
        });
        getProject().exec(new Action<ExecSpec>() {
            @Override
            public void execute(ExecSpec execSpec) {
                execSpec.commandLine("java","-jar",jiaGuExt.getJiaGuPluginPath(),
                        "-jiagu",apk.getAbsolutePath(),apk.getParent());
            }
        });
    }


}

在构造Task的时候,将需要的参数传递过来,我们还设置了task任务所属的group,group就是’jiagu‘,我们编译项目指挥,就会在右侧gradle栏下生成任务:

这个时候,我们点击jiagu_release就可以对已经生成的apk进行加固了。在构造函数中设置了所属的group,其实我们也可以给jiagu_release或者jiagu_debug设置dependsOn,比如设置dependsOn 为assembleRelease的话,就可以在执行jiagu_release之前执行assembleRelease,就可以保证能够得到未加固的apk,来完成加固。

提倡的插件编写方式

      除了上面写的在buildSrc  module下编写相关的插件代码的方式,还有一种,更为标准的编写插件的方法。下面就讲讲这种方式。新建一个java or Kotlin Libary 的module ,此时module的名称 不需要固定为buildSrc,随便起一个另外的名字就行,我们就可以将自己的plugin的相关代码写在这么module中。写插件的方式和上面 在buildSrc中的方式完全一样,只是我们需要配置一些其他东西。

我们需要在module的src/main下面新建resources目录,里面的文件必须是按照上面框框里面的层级顺序,只是properties文件的名称可以自己随便起,其他的都是固定的,我们可以写XXXX.properties文件也可以。

implementation-class=com.android.jiaguplugin.JiaGuPlugin

在properties 文件中,我们需要配置插件的全路径名称,前面的imlementation-class= 是固定的,后面的值是你的插件的全路径名称。还有一个需要在我们的插件module的build.gradle之中配置上传插件的代码,我这是直接上传到我的maven本地仓库:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation gradleApi()
    implementation 'com.android.tools.build:gradle:3.4.1'
}

sourceCompatibility = "7"
targetCompatibility = "7"
//这下面是上传 插件的代码
apply plugin:'maven-publish'
publishing{
    publications{
        JiaGuPlugin(MavenPublication){
            from components.java
            groupId 'com.android'   //插件
            artifactId 'jiagu'    //
            version '1.0'     //插件版本号
        }
    }
}

加上述maven的配置之后,我们可以在右侧gradle中看到下面的目录

可以执行相关的任务,发布插件,我们点击箭头所指的任务,就可以将其发布到本地仓库,我们也可以选择其他方式发布插件到jcenter或者maven,只要正确引用就行。这个时候,需要在项目的根目录下的build.gradle的文件中增加插件的引用(需要加上mavenLocal)

在app  module下的build.gradle 下直接应用插件

apply plugin: 'jiagu-plugin'

这个’jiagu-plugin‘就是插件的名称,这个名称其实就是上面定义的properties文件的文件名称,这两个名称必须一致,才能正确引用。这样也同样实现了我们上面的那件的功能,只是这里我们可以上传到jcenter或者maven之后,别人就可以直接通过

classpath : 'com.android:jiagu:1.0'

apply plugin:'jiagu-plugin'

来直接引用我们的插件了。

总结

      学好Gradle ,对我们Android开发其实非常有必要的。它能够写出一些我们平时直接用java代码很难实现的功能。一起加油。

Demo传送门

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值