Gradle的构建过程都不会?带你全面了解Android如何自定义Gradle插件

Stella981
• 阅读 655

目前 Android 工程的默认构建工具为 Gradle,我们在构建 APK 的时候往往会执行 ./gradlew assembleDebug 这样的命令。。

那么这个命令到底代表着什么含义呢?命令的执行究竟是在做什么事情呢?我们能不能在命令执行的过程中做一些自己的操作呢?接下来我们来具体的进行分析。

Gradle 的构建过程

Gradle Wrapper 是个啥

当我们在 Android Studio 中新建一个工程时,你会发现在工程的根目录下会创建以下几个文件:

Gradle的构建过程都不会?带你全面了解Android如何自定义Gradle插件

在这里插入图片描述

实际上这几个文件是通过执行 $ Gradle Wrapper 生成的。Gradle Wrapper,顾名思义就是对 Gradle 构建工具的一层封装。

在和其他同事共同管理某个 Android 工程的时候,肯定会存在同事 A 电脑上的 Gradle 版本和同事 B 电脑上的 Gradle 版本不一样,那么这个不一样可能导致的问题是需要在 build.gradle 文件中添加不同的配置,甚至有的 Gradle 版本都无法成功跑通工程。

所以,Gradle 的工程师们将 Gradle 添加了一层简单的封装,Linux 用户可以通过执行 gradlew 来代替 gradle 命令,Windows 用户可以通过 gradlew.bat 来代替,实际上这俩文件就是个可执行脚本,我们可以直接打开这个脚本来看里面到底有什么。

# 只贴上主要代码 gradlew 文件 ... JAVACMD="java" ... CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar ... exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

从里面的代码我们可以看到,实际上 gradlew 的脚本也只是执行了一下 Java 命令,assembleDebug 只是执行脚本的参数。我们修改一下脚本打印出来这个命令:

java -Xdock:name=Gradle -Xdock:icon=/media/gradle.icns -Dorg.gradle.appname=gradlew -classpath /xxx/food/gradle/wrapper/gradle-wrapper.jar org.gradle.wrapper.GradleWrapperMain assembleDebug -s

从这里可以看到,脚本所做的事情就是通过 Java 命令来执行 /gradle/wrapper/ 下的 gradle-wrapper.jar 包。通过 JD-GUI 工具打开这个可执行 jar 包,找到那个 jar 包的入口类,也就是 org.gradle.wrapper.GradleWrapperMain,我们大致看一下这个 jar 包到底干了什么。

// org.gradle.wrapper.GradleWrapperMain文件 public static void main(String[] args) throws Exception {

File wrapperJar = wrapperJar(); // 获取这个可执行jar包的具体路径 File propertiesFile = wrapperProperties(wrapperJar); // 获取gradle-wrapper.properties的位置 File rootDir = rootDir(wrapperJar); // 获取工程的根目录

// 下面的一段代码用来解析命令行,我们暂时不去管它 CommandLineParser parser = new CommandLineParser();

...

WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile); // 下面这个方法便是解析gradle/wrapper/gradle-wrapper.properties文件里面的内容,然后根据里面的配置到相应的地址需下载另一个可执行jar包 wrapperExecutor.execute( args, new Install(logger, new Download(logger, "gradlew", wrapperVersion()), new PathAssembler(gradleUserHome)), new BootstrapMainStarter()); }

通过分析 execute() 方法的执行,我们会发现,这个方法执行了 gradle-wrapper.properties 文件的解析和下载工作,我们先看一下这个文件里面有什么:

// gradle/wrapper/gradle-wrapper.properties 文件配置 distributionBase=GRADLE_USER_HOME // GRADLE_USER_HOME的地址默认在用户目录下的.gradle/文件夹里 distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https://services.gradle.org/distributions/gradle-4.4-all.zip

如上所示,代码会找到 distributionUrl 这个路径,然后下载相应的文件。在这里,工程会指定固定的一个 Gradle 版本,然后所有的开发者都会在相同的路径下下载到相同的 ZIP 包,这样也就保证了在不同电脑上执行构建操作结果的一致性。下载文件的过程就不多述了,因为比较简单。

文件下载以后,当前就会去执行这个文件:

// org.gradle.wrapper.BootstrapMainStarter public class BootstrapMainStarter { public void start(String[] args, File gradleHome) throws Exception { File gradleJar = findLauncherJar(gradleHome); URLClassLoader contextClassLoader = new URLClassLoader(new URL[]{gradleJar.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent()); Thread.currentThread().setContextClassLoader(contextClassLoader); // 通过反射找到org.gradle.launcher.GradleMain 进行具体的调用 Class<?> mainClass = contextClassLoader.loadClass("org.gradle.launcher.GradleMain"); Method mainMethod = mainClass.getMethod("main", String[].class); mainMethod.invoke(null, new Object[]{args}); if (contextClassLoader instanceof Closeable) { ((Closeable) contextClassLoader).close(); } } }

如上所示,Java 代码找到下载的 jar 包,然后通过 ClassLoader 加载到内存里,加载以后通过反射调用里面的入口类。

到此,Gradle Wrapper 的整个调用过程结束了,它的功能就是保证多个电脑上能够以相同的 Gradle 版本构建 Android 的工程代码,后续的执行则是开启了 Gradle 的真正构建过程,我们接下来进行分析。

Gradle 的构建生命周期

Gradle 的整个构建过程共分为三个阶段:init 初始化阶段、config 配置阶段和 build 执行阶段。下面简单说一下这三个阶段分别做什么工作。

init 初始化阶段

初始化阶段主要是解析 settings.gradle 文件,查看该工程引入了多少个 module。如下所示,可以在 settings.gradle 文件下定义需要引入的 module 和其对应的目录:

include ':app'

include ':library' project(':library').projectDir = new File('../library')

config 阶段

在 config 阶段便是去解析每个 module 里的 build.gradle 文件,并逐行执行,完成对 project 的配置,并构造 Task 任务依赖关系图以便在执行阶段按照依赖关系执行 Task。

build 执行阶段

执行阶段便是根据 config 阶段生成的 Task 依赖关系图,来挨个地去执行各个 Task。每个 Task 可以看做是一个功能体,比如说,在构建过程中 Java 文件需要先转换为 class 文件,然后 class 文件要再次转换成 dex 文件,然后 dex 文件最终组合生成 APK,这个过程中每一步都是由一个 Task 来执行的。后续在介绍自定义 Gradle 插件的时候会讲到 Task 相关的东西。

Gradle 构建过程代码分析

刚才梳理了一下 Gradle 构建过程的生命周期,分为上面那三个阶段,那么,具体到代码是如何实现这三个声明周期的呢?我们具体进行一下分析。

在 Gradle Wrapper 的末尾,我们提到构建过程走到了通过反射找到 org.gradle.launcher.GradleMain 进行具体的调用。那么我们就继续跟着源码走。

// org.gradle.launcher.GradleMain public class GradleMain { public static void main(String[] args) throws Exception { new ProcessBootstrap().run("org.gradle.launcher.Main", args); } }

org.gradle.launcher.GradleMain 是真正执行构建过程的入口类,深入到 new ProcessBootstrap().run() 方法中继续执行。

private void runNoExit(String mainClassName, String[] args) throws Exception { ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry(new DefaultClassPathProvider(new DefaultModuleRegistry(CurrentGradleInstallation.get()))); ClassLoaderFactory classLoaderFactory = new DefaultClassLoaderFactory(); ClassPath antClasspath = classPathRegistry.getClassPath("ANT"); ClassPath runtimeClasspath = classPathRegistry.getClassPath("GRADLE_RUNTIME"); ClassLoader antClassLoader = classLoaderFactory.createIsolatedClassLoader(antClasspath); // 我们发现通过新建classLoader,在classLoader增加了新的依赖项,但这些依赖项不知道是什么。不过这不是我们关注的重点,我们继续代码的执行 ClassLoader runtimeClassLoader = new VisitableURLClassLoader(antClassLoader, runtimeClasspath); ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(runtimeClassLoader);

try { // 在此处通过反射调用了org.gradle.launcher.Main这个类的run方法, Class<?> mainClass = runtimeClassLoader.loadClass(mainClassName); Object entryPoint = mainClass.newInstance(); Method mainMethod = mainClass.getMethod("run", String[].class); mainMethod.invoke(entryPoint, new Object[]{args}); } finally { ... } }

主流程是通过反射调用了 org.gradle.launcher.Main 这个类的 run 方法,我们具体看一下这段代码。

// org.gradle.launcher.Main public class Main extends EntryPoint { public static void main(String[] args) { new Main().run(args); } protected void doAction(String[] args, ExecutionListener listener) { UnsupportedJavaRuntimeException.assertUsingVersion("Gradle", JavaVersion.VERSION_1_7); createActionFactory().convert(Arrays.asList(args)).execute(listener); } CommandLineActionFactory createActionFactory() { return new CommandLineActionFactory(); } }

org.gradle.launcher.Main 这个类实际上继承了 org.gradle.launcher.bootstrap.EntryPoint 这个类,而它的 run 方法实际也就是提供了对 Main 中 doAction 方法的回调。

// org.gradle.launcher.cli.CommandLineActionFactory public Action convert(List args) { ServiceRegistry loggingServices = createLoggingServices();

LoggingConfiguration loggingConfiguration = new DefaultLoggingConfiguration();

return new WithLogging(loggingServices, buildLayoutFactory, args, loggingConfiguration, new ExceptionReportingAction( new ParseAndBuildAction(loggingServices, args), new BuildExceptionReporter(loggingServices.get(StyledTextOutputFactory.class), loggingConfiguration, clientMetaData()))); }

CommandLineActionFactory.convert 返回 WithLogging 的实例,然后调用了 WithLogging 的 execute 方法。

然后经过各种回调等,最终调用的是 ParseAndBuildAction 的 execute 方法(中间的回调过程略过,有兴趣的同学可以自己查看一下,我们只分析主流程的代码):

// org.gradle.launcher.cli.CommandLineActionFactory#ParseAndBuildAction public void execute(ExecutionListener executionListener) { List actions = new ArrayList(); // BuildInActions 是处理help version 这些命令的 actions.add(new BuiltInActions()); // 如果不是上面的两条命令行参数,则执行BuildActionsFactory里的参数。 createActionFactories(loggingServices, actions);

CommandLineParser parser = new CommandLineParser(); for (CommandLineAction action : actions) { action.configureCommandLineParser(parser); }

Action<? super ExecutionListener> action; try { ParsedCommandLine commandLine = parser.parse(args); action = createAction(actions, parser, commandLine); } catch (CommandLineArgumentException e) { action = new CommandLineParseFailureAction(parser, e); }

action.execute(executionListener); }

private Action<? super ExecutionListener> createAction(Iterable factories, CommandLineParser parser, ParsedCommandLine commandLine) { for (CommandLineAction factory : factories) { // 根据命令行参数选中相关的处理的Action,比如说在gradle参数后面跟着 help 或者version 参数,则选中BuiltInActions Runnable action = factory.createAction(parser, commandLine); if (action != null) { return Actions.toAction(action); } } throw new UnsupportedOperationException("No action factory for specified command-line arguments."); }

如果在 gradlew 参数后面加 help 或者 version 时,将交给 BuiltInActions 进行处理,其它的则会交给 BuildActionsFactory 类处理。我们直接查看一下 BuildActionsFactory 的执行代码。

// org.gradle.launcher.cli.BuildActionsFactory public Runnable createAction(CommandLineParser parser, ParsedCommandLine commandLine) { Parameters parameters = parametersConverter.convert(commandLine, new Parameters()); parameters.getStartParameter().setInteractive(ConsoleStateUtil.isInteractive());

parameters.getDaemonParameters().applyDefaultsFor(jvmVersionDetector.getJavaVersion(parameters.getDaemonParameters().getEffectiveJvm())); // 下面是通过各种不同的参数 if (parameters.getDaemonParameters().isStop()) { / return stopAllDaemons(parameters.getDaemonParameters(), loggingServices); } if (parameters.getDaemonParameters().isStatus()) { return showDaemonStatus(parameters.getDaemonParameters(), loggingServices); } if (parameters.getDaemonParameters().isForeground()) { DaemonParameters daemonParameters = parameters.getDaemonParameters(); ForegroundDaemonConfiguration conf = new ForegroundDaemonConfiguration( UUID.randomUUID().toString(), daemonParameters.getBaseDir(), daemonParameters.getIdleTimeout(), daemonParameters.getPeriodicCheckInterval()); return new ForegroundDaemonAction(loggingServices, conf); } if (parameters.getDaemonParameters().isEnabled()) { return runBuildWithDaemon(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices); } if (canUseCurrentProcess(parameters.getDaemonParameters())) { return runBuildInProcess(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices); }

return runBuildInSingleUseDaemon(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices); }

后三个方法中都会调用到 runBuildAndCloseServices,这也是执行 Gradle 构建的方法。

最终代码会执行到 RunBuildAction 的 run 方法。

// org.gradle.launcher.cli.RunBuildAction public void run() { try { // 这个executer实际上是 InProcessBuildActionExecuter 的实例 executer.execute( new ExecuteBuildAction(startParameter), new DefaultBuildRequestContext(new DefaultBuildRequestMetaData(clientMetaData, startTime), new DefaultBuildCancellationToken(), new NoOpBuildEventConsumer()), buildActionParameters, sharedServices); } finally { if (stoppable != null) { stoppable.stop(); } } }

最终查看 InProcessBuildActionExecuter 执行的代码。

public Object execute(BuildAction action, BuildRequestContext buildRequestContext, BuildActionParameters actionParameters, ServiceRegistry contextServices) { // 最终通过调用获取到了GradleLauncher的实例,他的一个实现类DefaultGradleLauncher GradleLauncher gradleLauncher = gradleLauncherFactory.newInstance(action.getStartParameter(), buildRequestContext, contextServices); try { RootBuildLifecycleListener buildLifecycleListener = contextServices.get(ListenerManager.class).getBroadcaster(RootBuildLifecycleListener.class); buildLifecycleListener.afterStart(); try { GradleBuildController buildController = new GradleBuildController(gradleLauncher); buildActionRunner.run(action, buildController); return buildController.getResult(); } finally { buildLifecycleListener.beforeComplete(); } } finally { gradleLauncher.stop(); } }

在 GradleLauncher 的实现类中,我们看到了熟悉的东西:

private enum Stage { Load, Configure, Build }

然后代码便会调用到 DefaultGradleLauncher 的 run ()->doBuild () 方法

private BuildResult doBuild(final Stage upTo) { // TODO:pm Move this to RunAsBuildOperationBuildActionRunner when BuildOperationWorkerRegistry scope is changed final AtomicReference buildResult = new AtomicReference(); WorkerLeaseService workerLeaseService = buildServices.get(WorkerLeaseService.class); workerLeaseService.withLocks(workerLeaseService.getWorkerLease()).execute(new Runnable() { @Override public void run() { Throwable failure = null; try { // 开始构建之前 buildListener.buildStarted(gradle); // 开始构建 doBuildStages(upTo); } catch (Throwable t) { failure = exceptionAnalyser.transform(t); } buildResult.set(new BuildResult(upTo.name(), gradle, failure)); // 构建完成之后 buildListener.buildFinished(buildResult.get()); if (failure != null) { throw new ReportedException(failure); } } }); return buildResult.get(); }

然后我们看一下开始构建时的代码:

private void doBuildStages(Stage upTo) { if (stage == Stage.Build) { throw new IllegalStateException("Cannot build with GradleLauncher multiple times"); } if (stage == null) { // Evaluate init scripts initScriptHandler.executeScripts(gradle); // 初始化阶段,解析Settings.gradle文件夹 settings = settingsLoader.findAndLoadSettings(gradle); stage = Stage.Load; } if (upTo == Stage.Load) { return; } if (stage == Stage.Load) { // 配置阶段 buildOperationExecutor.run(new ConfigureBuild()); stage = Stage.Configure; }

if (upTo == Stage.Configure) { return; }

stage = Stage.Build; // 绘制task的依赖树 buildOperationExecutor.run(new CalculateTaskGraph()); // 执行task。 buildOperationExecutor.run(new ExecuteTasks()); }

到最后一步,感觉所有的努力都没有白费,终于看到了熟悉的 Gradle 构建的三个阶段。原来 Gradle 的构建也是用代码写出来的,并没有想象的那么高深。

Gradle 插件

什么是 Gradle 插件

我们上面讲解过,Gradle 的构建过程实际上就是各个 Task 的执行过程,那么这些执行的 Task 从哪里来呢?答案就是从 Gradle 插件里来。

我们发现当我们新建一个 Android 工程时,在 App 这个 module 的 build.gradle 文件的第一行里会有以下代码:

apply plugin: 'com.android.application'

这句代码的作用便是将构建 Android 应用的所有需要 Task 都加载进来了。所以我们看到,Gradle 生命周期的三个阶段仅仅是个壳子,如果想构建 Android 工程,那么就用 apply plugin: 'com.android.application' 引入所有的构建 Android 应用所需要的 Task;如果想要构建 Java 工程,那么只需要通过 apply plugin: 'java' 来引入 Java 工程所需要的 Task 便可。

那么知道了 Gradle 插件的强大功能,我们将如何按照自己的需要自定义 Gradle 插件呢?我们下面来进行讲解。

自定义 Gradle 插件

我们在自定义 Gradle 插件的时候,需要解决以下问题:

  • 问题一:如何自定义一个 Gradle Plugin?

  • 问题二:Gradle Plugin 怎么调试?

  • 问题三:Gradle Plugin 的 apply 方法是什么时候触发的?

下面以实际的例子来介绍如何自定义 Gradle 插件,并对 Gradle 插件进行调试。在这个实际的例子中,Gradle 插件的定义和使用分别在两个不同的工程中,这样定义出来的 plugin 能够供外部使用。

问题一:自定义一个插件

新建两个工程 CustomPlugin、GradleProject。前者是定义插件的地方,后者是使用插件的地方。我们先定义插件。

下面是完成插件自定义以后的目录结构,我们先来一个总览。

Gradle的构建过程都不会?带你全面了解Android如何自定义Gradle插件

在这里插入图片描述

新建一个 module,删除里面的所有文件,然后新建成如上图所示的目录结构。其中 MyCustomPlugin 是定义的插件类,而 mycustomplugin.properties 是配置的插件属性。

首先在 build.gradle 文件中添加如下代码。

// 应用另外两个插件 apply plugin:"groovy" apply plugin: "maven"

dependencies { // 使用gradle的api compile gradleApi() // 使用groovy的api compile localGroovy() }

repositories { // 下载api相关文件的仓库 mavenCentral() }

添加以后点击 Sync Project with Gradle 按钮,就是这个:

Gradle的构建过程都不会?带你全面了解Android如何自定义Gradle插件

在这里插入图片描述

然后 Android Studio 就会识别出 groovy 文件夹,groovy 文件夹就变成了蓝色。

然后在 MyCustomPlugin.groovy 添加如下代码,在 apply 方法中具体执行我们想要这个插件去做的事情。

class MyCustomPlugin implements Plugin {

@Override
void apply(Project project) {

    println("start mycustomplugin")

    println(project.name)
}

}

最后一步,在 mycustomplugin.properties 文件中添加如下代码,用来指明插件的处理类:

implementation-class=com.dianping.myplugin.MyCustomPlugin

其中 mycustomplugin.properties 中的 mycustomplugin,代表着这个插件在使用时的名称,例如,使用时就是 apply plugin:'mycustomplugin’。使用方通过名称找到这个插件的配置文件,然后根据配置文件找到这个插件具体执行的类。

到此,自定义一个插件的基本工作就完成了,下面就讲一讲如何使用。

如何使用

打包

在 myplugin 这个 module 中的 build.gradle 文件中添加一些代码,添加后整个代码结构如下。

apply plugin:"groovy" apply plugin: "maven"

dependencies { compile gradleApi() compile localGroovy() }

repositories { mavenCentral() } // 此处为新添加的代码 // 定义组 group='com.dianping.myplugin' //定义版本 version='1.0.0'

uploadArchives { repositories { mavenDeployer { // 定义插件打包后上传的位置,可以随意指定,但是在使用时需要指定同样的文件才能找到 repository(url: uri('../../repo')) } } }

添加完成后再次点击:

Gradle的构建过程都不会?带你全面了解Android如何自定义Gradle插件

在这里插入图片描述

然后会在 Gradle project 面板中出现 uploadArchives 的 Task。

Gradle的构建过程都不会?带你全面了解Android如何自定义Gradle插件

在这里插入图片描述

双击它,就会在 …/…/repo 目录下出现相关文件。

使用

在 GradleProject 的根级别的 build.gradle 中 buildscript 节点上添加代码,添加完后如下所示:

buildscript {

repositories {
    google()
    jcenter()
    // 添加的代码
    maven {
        url uri('../repo') // 指定路径,这个路径和上面的生成路径是一致的
    }
}
dependencies {
    classpath 'com.android.tools.build:gradle:3.0.0'
    // 添加的代码。myplugin是上面定义的时候module的名称,com.dianping.myplugin是group。
    classpath 'com.dianping.myplugin:myplugin:1.0.0'

}

}

最后在 app 这个 module 中添加插件使用。

apply plugin: 'mycustomplugin'

添加完后执行 ./gradlew :app:assembleDebug 就能看到打印结果:

start mycustomplugin app

至此,使用上也讲完了。下面该讲一讲如何调试了。

问题二:如何调试

在刚才的 CustomPlugin 工程下在菜单栏中选择 Run >> Edit Configurations。然后点击 remote,新建远程调试,其他东西都不用改,直接点击 OK 就行。

Gradle的构建过程都不会?带你全面了解Android如何自定义Gradle插件

在这里插入图片描述

新建完成以后再 GradleProject 中运行如下命令:

./gradlew :app:assembleDebug -Dorg.gradle.debug=true

然后在 CustomPlugin 需要断点的地方打上断点,点击下面红框里的按钮,启动调试。断点处就会终止执行。

Gradle的构建过程都不会?带你全面了解Android如何自定义Gradle插件

在这里插入图片描述

至此,插件的开发也能够调试了。

问题三:apply 方法什么时候执行

Gradle 构建的过程总共分为三个阶段:初始化阶段、配置阶段、运行阶段。初始化阶段是执行 settings.gradle 文件中的内容,看看这个 Project 需要构建哪几个 module。在配置阶段是从根 Project 依次遍历 module,并为每个 module 生成一个 Project 对象。配置阶段完成时就形成了一个完整的 Task 依赖图。然后就是执行阶段执行相关的 Task。

那么 apply 方法是什么时候执行的呢?是在配置阶段遇到 apply plugin:'mycustomplugin’ 就开始执行,我们可以在前后打 log 来验证。结果和预期一样。apply 方法中传入的 Project 对象就是某个使用该插件的 Project 的对象。

println 'before' apply plugin: 'mycustomplugin' println 'after'

非独立工程定义和使用插件

如果想要在自己的工程里面使用 Gradle 插件,那么更加简单。

新建一个 Project,叫做 PluginDemo, 在 app 的 build.gradle 中写上如下代码:

class ApkDistPlugin implements Plugin {

@Override
void apply(Project project) {
    project.task("apkdist") << {
        println 'hello world'
    }
}

} apply plugin: ApkDistPlugin 命令行输入: ./gradlew -q -p app/ apkdist

// 输出结果为:

hello world

让插件是可以配置的

大多数插件都需要在 build script 中获取到一定的配置信息。其中一个方法就是通过 Extension 类来进行,Project 类中持有了 ExtensionContainer 对象,包含了对这个 Project 所有的配置。那么我们就可以通过它来添加我们自己的配置。下面是一个例子。

class ApkDistExtension { Closure nameMap = null String destDir = null } class GreetingPluginExtension { String message = null } class ApkDistPlugin implements Plugin {

@Override
void apply(Project project) {
    project.extensions.create("apkdistconf",ApkDistExtension)
    def extension = project.extensions.create("greet",GreetingPluginExtension)
    project.task("apkdist") << {
        def closure = project\['apkdistconf'\].nameMap
        closure('hello world closure')
        println 'hello world'
        println project\['apkdistconf'\].destDir
        println extension.message
    }
}

} apply plugin: ApkDistPlugin

apkdistconf { nameMap { name -> println "$name haha" } destDir 'heiheihei'

} greet.message = "greet"

下面是运行结果

// 执行的命令 ./gradlew -q -p app/ apkdist

// 运行的结果

hello world closure haha hello world heiheihei greet

Gradle的构建过程都不会?带你全面了解Android如何自定义Gradle插件

现在都说互联网寒冬,其实只要自身技术能力够强,咱们就不怕!我这边专门针对Android开发工程师整理了一套【Android进阶学习视频】、【全套Android面试秘籍】、【Android知识点PDF】。如有需要获取资料文档的朋友,可以点击我GitHub免费获取!

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Souleigh ✨ Souleigh ✨
3年前
前端性能优化 - 雅虎军规
无论是在工作中,还是在面试中,web前端性能的优化都是很重要的,那么我们进行优化需要从哪些方面入手呢?可以遵循雅虎的前端优化35条军规,这样对于优化有一个比较清晰的方向.35条军规1.尽量减少HTTP请求个数——须权衡2.使用CDN(内容分发网络)3.为文件头指定Expires或CacheControl,使内容具有缓存性。4.避免空的
Stella981 Stella981
2年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
Gradle如何在任务失败后继续构建
如果我们运行Gradle构建并且其中一项任务失败,则整个构建将立即停止。因此,我们可以快速反馈构建状态。如果我们不想这样做,并且希望Gradle执行所有任务,即使某些任务可能失败了,我们也可以使用命令行选项continue。当我们使用continue命令行选项时,Gradle将执行从属任务没有失败的所有任务。这在多模块项目中也很有用,即使在某
Wesley13 Wesley13
2年前
35岁是技术人的天花板吗?
35岁是技术人的天花板吗?我非常不认同“35岁现象”,人类没有那么脆弱,人类的智力不会说是35岁之后就停止发展,更不是说35岁之后就没有机会了。马云35岁还在教书,任正非35岁还在工厂上班。为什么技术人员到35岁就应该退役了呢?所以35岁根本就不是一个问题,我今年已经37岁了,我发现我才刚刚找到自己的节奏,刚刚上路。
Python进阶者 Python进阶者
4个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这