上一篇文章提到了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代码很难实现的功能。一起加油。