Mastering Gradle: Lifecycle, Tasks, and Plugin Development for Android
This comprehensive guide walks through Gradle fundamentals—including the initialization, configuration, and execution phases—explains task creation, ordering, and incremental builds, demonstrates custom tasks and small examples, and shows three ways to build and publish Gradle plugins with ASM bytecode instrumentation for Android projects.
Gradle Basics
Gradle Lifecycle
Gradle's build process consists of three distinct phases: initialization, configuration, and execution.
1. Initialization Phase
In this phase Gradle determines which projects participate in the build and creates a Project instance for each. It reads the settings.gradle file to identify the project structure.
2. Configuration Phase
Gradle configures each project object by executing the build scripts. Tasks, dependencies, and build rules are defined, and the build.gradle file is parsed to generate a task dependency graph.
3. Execution Phase
Gradle selects the subset of tasks to run based on the command line and executes them in order, respecting dependencies. The clean task is an example that does not trigger the lifecycle when Gradle syncs.
Gradle Tasks
A Task is the basic unit of work in a Gradle build. It can compile code, copy files, run tests, etc.
1. Creating/Locating Tasks
// create task in several ways
tasks.register("taskName") { /* ... */ }
task taskName { /* ... */ }
tasks.create("taskName") { /* ... */ }2. Custom Tasks
Extend DefaultTask, annotate a method with @TaskAction, and specify the task class when creating the task.
/**
* Custom task
*/
class MyTask extends org.gradle.api.DefaultTask {
@TaskAction
void run() {
println "custom task execution"
}
}
task myTask(type: MyTask)3. Task Ordering
Use dependsOn, finalizedBy, and mustRunAfter to control execution order.
hello.dependsOn(test1)
hello.finalizedBy(test2)
hello.setMustRunAfter(test3)4. Task Input/Output (Incremental Build)
Gradle checks if task inputs have changed; if not, the task is marked UP-TO-DATE and skipped, improving build speed.
5. Task Listeners
Register callbacks before and after task execution.
void beforeTask(Action<Task> action);
void afterTask(Closure closure);Gradle Plugins
Three Ways to Create Plugins
1. In build.gradle
Define a plugin class implementing Plugin<Project> and apply it with apply plugin: MyPlugin.
class MyPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println "apply plugin"
project.task('pluginTask') {
doLast { println 'execute plugin task' }
}
}
}
apply plugin: MyPlugin2. buildSrc Project
Place plugin source code under buildSrc/src/main/java (or groovy/kotlin). Gradle compiles it and makes it available to the build scripts.
Steps:
Create a buildSrc directory.
Add a build.gradle file with a dependency on the Gradle API.
Create the plugin class.
Add a META-INF/gradle-plugins descriptor.
3. Standalone Project
Develop a separate project that produces a JAR containing the plugin, publish it, and apply it in other builds.
Small Example: Print Task Execution Time
def map = new HashMap<String, Long>()
tasks.beforeTask { Task task ->
map.put(task.name, System.currentTimeMillis())
}
tasks.afterTask { Task task ->
def time = map.get(task.name)
map.put(task.name, System.currentTimeMillis() - time)
}
gradle.buildFinished {
println "buildFinished"
map.forEach { k, v -> println "taskName:" + k + " time:" + v }
}Small Example: Print Task Dependency Graph
gradle.projectsEvaluated {
println "projectsEvaluated"
TaskExecutionGraph tasks = gradle.getTaskGraph()
tasks.whenReady {
println "TaskExecutionGraph whenReady"
TaskExecutionGraph tasks2 = gradle.getTaskGraph()
println "digraph pic { "
List<Task> taska = tasks2.getAllTasks()
for (i in (taska.size())..<0) {
Task t = taska.get(i - 1)
Set<? extends Task> dependsOn = t.getTaskDependencies().getDependencies()
dependsOn.forEach { dt ->
println dt.name + " -> " + t.name
}
}
println "}"
}
}Gradle Application (ASM Instrumentation)
Instrumentation Overview
ASM is a bytecode instrumentation library. In Android builds, before converting classes to dex, a transform task can modify class files.
Debugging Gradle
Configure remote debugging in the IDE, add JVM arguments to gradle.properties, and run the debug task.
Transform logic resides in TaskManager.createPostCompilationTasks
Transform Implementation
Register a custom Transform in the plugin, iterate over class files and JARs, and modify bytecode using ASM.
public class TransformPlugin implements Plugin<Project> {
@Override
public void apply(Project target) {
BaseExtension android = target.getExtensions().getByType(BaseExtension.class);
android.registerTransform(new TransformTemplate("MyTransform", false, new MyTransform()));
}
}
public class MyTransform implements BiConsumer<InputStream, OutputStream> {
@Override
public void accept(InputStream inputStream, OutputStream outputStream) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor visitor = writer;
visitor = new OkHttpAdapter(visitor);
try {
ClassReader cr = new ClassReader(inputStream);
cr.accept(visitor, ClassReader.EXPAND_FRAMES);
outputStream.write(writer.toByteArray());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
// additional helper methods omitted for brevity
}Method Adapter Example
private static final class MethodAdapter extends MethodVisitor implements Opcodes {
public MethodAdapter(MethodVisitor mv) {
super(ASM7, mv);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if (owner.equals(OKHTTP3_BUILDER_CLASS) && isConstructor(opcode, name, desc) && !itf) {
super.visitInsn(DUP);
super.visitMethodInsn(opcode, owner, name, desc, itf);
invoke(OKHTTP3_WRAPPER, "addInterceptorToBuilder", "(Ljava/lang/Object;)V");
} else if (owner.equals(OKHTTP2_CLIENT_CLASS) && isConstructor(opcode, name, desc) && !itf) {
super.visitInsn(DUP);
super.visitMethodInsn(opcode, owner, name, desc, itf);
invoke(OKHTTP2_WRAPPER, "addInterceptorToClient", "(Ljava/lang/Object;)V");
} else {
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
private static boolean isConstructor(int opcode, String name, String desc) {
return opcode == INVOKESPECIAL && name.equals("<init>") && desc.equals("()V");
}
private void invoke(String wrapper, String method, String desc) {
super.visitMethodInsn(INVOKESTATIC, wrapper, method, desc, false);
}
}Interceptor Insertion
Static helper methods add interceptors to OkHttp builders or clients.
@JvmStatic
fun addInterceptorToBuilder(builder: Any?) {
if (builder is OkHttpClient.Builder) {
builder.addInterceptor(OkHttp3TrackInterceptor())
}
}
@JvmStatic
fun addInterceptorToClient(client: Any?) {
if (client is OkHttpClient) {
client.networkInterceptors().add(OkHttp2Interceptor())
}
}With these hooks you can implement network monitoring, logging, or any custom logic.
Finally, publish the plugin using the standard Gradle plugin publishing workflow; once published it can be applied in other projects.
Now you have covered Gradle fundamentals, task management, plugin creation, and bytecode instrumentation for Android.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
