Gradle基础与应用(插桩)

Gradle基础

Gradle生命周期

Gradle 构建过程可以分为三个不同的阶段,每个阶段具有特定的功能和任务。

1、初始化阶段:

在这个阶段,Gradle确定要参与构建的项目,并为每个项目创建一个Project实例。这是构建的开始阶段,Gradle根据项目根目录下的settings.gradle文件来确定哪些项目参与构建。

settings.gradle文件指定了构建所需的项目结构。在这个阶段,您可以配置项目的包含关系和层次结构,以便Gradle知道有哪些子项目需要构建。

2、配置阶段:

在配置阶段,Gradle对每个项目对象进行配置。这意味着执行构建脚本,其中包含了所有参与构建的项目的配置信息。在这个阶段,您可以定义任务(Tasks)、依赖关系、构建规则和其他构建配置。

每个子项目的build.gradle文件在这个阶段被解析,Gradle会创建所有项目所属的任务(Tasks)以及它们之间的依赖关系,并生成一个任务有向图,这个图形表示了任务执行的顺序和依赖关系。

3、执行阶段:

在执行阶段,Gradle确定要执行的任务子集。这个子集由传递给Gradle命令以及当前目录中指定的任务名称参数来确定。Gradle根据配置阶段创建和配置的任务列表,依次执行每个选定的任务。

在执行阶段,Gradle会执行从根项目到子项目的构建过程,逐个执行任务并满足任务之间的依赖关系。这是构建过程的实际执行阶段。

执行具体的task,如clean。注:gradle同步的时候不会触发执行阶段生命周期

通过这三个阶段,Gradle 可以完成从项目初始化到任务执行的全过程,提供了灵活而强大的构建工具。清晰地理解这些阶段的功能有助于更好地使用和配置 Gradle 构建系统。

Gradle Task

在Gradle中,Task(任务)是构建过程的基本单位。每个Task代表一个构建阶段或操作,它可以执行编译、复制文件、运行测试等各种构建任务。任务是Gradle构建脚本中最重要的组成部分之一。

以下从这几个方面介绍Task:

1、创建Task/定位Task

javascript 复制代码
//创建task的几种方式
tasks.register("taskName"){

}
task taskName{

}
task "taskName"{

}
tasks.create("taskName"){

}
//获取task的几种方式
tasks.findByName("")
tasks.findByPath("")
tasks.named("").get()

2、自定义Task

1、继承DefaultTask。 2、声明task执行方法,方法名随意,必须加上@TaskAction注解。 3、创建自定义task的时候 指定task类名。

scala 复制代码
/**
 * 自定义task
 */
class MyTask extends org.gradle.api.DefaultTask {
    //方法名随意 只要加上TaskAction注解
    @org.gradle.api.tasks.TaskAction
    void run() {
        println("自定义task 执行")

    }
}
task myTask(type: MyTask)

3、Task 顺序

设置依赖的task,只有test1 task执行完后才会执行hello task。 hello.dependsOn(test1)

设置终结者任务,执行完hello task之后会执行test2 task,通常可以用该方法做一些清理操作。 hello.finalizedBy(test2) 如果同时执行hello、test3这2个task,会确保test3执行完之后才执行hello这个task,用这个来保证执行顺序 hello.setMustRunAfter([test3])

4、Task输入输出

Gradle 支持一种叫做 up-to-date 检查的功能,也就是常说的增量构建。Gradle 的 Task 会把每次运行的结果缓存下来,当下次运行时,会检查输出结果有没有变更,如果没有变更则跳过运行,这样可以提高 Gradle 的构建速度。 图中表示一个 java 编译的 task,它的输入有2种,一是 JDK 版本号,一是源文件,它的输出结果为 class 文件,只要 JDK 版本号与源文件有任何变动,最终编译出的 class 文件肯定是不同的。当我们执行过一次编译任务后,再次运行该 task ,如果发现它的输入没有任何改变,那么它编译后的结果肯定也是不会变化的,可以直接从缓存里获取输出,这样 Gradle 会标识该 Task 为 UP-TO-DATE,进而跳过该 Task 的执行。

5、Task的一些监听方法

Task执行前后回调方法 void beforeTask(Action<Task> action); void beforeTask(Closure closure); void afterTask(Action<Task> action); void afterTask(Closure closure);

小案例

1、打印编译过程中Task的执行时长

arduino 复制代码
//临时存储task对应的时间
def map = new HashMap<String, Long>()
tasks.beforeTask {
        Task task ->
            map.put(task.getName(), System.currentTimeMillis())
    }
 tasks.afterTask {
        Task task ->
            def time = map.get(task.getName())
            map.put(task.getName(), System.currentTimeMillis() - time)       
    }
//构建结束
gradle.buildFinished {
    println("buildFinished")
    map.forEach {
        k, v ->
               println("taskName:" + k + " time:" + v)
    }
}

2、打印编译过程中task的依赖关系

scss 复制代码
gradle.projectsEvaluated {
    println("projectsEvaluated")
    //配置完成后 生成task 有向图
    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)
            try {
                Set<? extends Task> dependsOn = t.getTaskDependencies().getDependencies()
                dependsOn.forEach {
                    Task dt = it
                    println(dt.name + " -> " + t.name)
                }
            } catch (Exception e) {
                println("异常了" + e.toString())
            }
        }
        println("}")
    }
}

Gradle插件

Gradle有三种创建插件的方式

1、build.gradle

直接在build.gradle中包含插件的源代码。这样做的好处是插件会自动编译并包含在构建脚本的类路径中,而无需执行任何操作。但是,该插件在构建脚本之外是不可见的,因此不能在定义它的构建脚本之外重用该插件。 1、在app下的build.gradle定义自定义插件MyPlugin 并实现Plugin接口 2、在app下的build.gradle使用该插件 apply plugin:MyPlugin

typescript 复制代码
class MyPlugin implements Plugin<Project>{

    @Override
    void apply(Project project) {
        println("应用插件")
        project.task('pluginTask') {
            doLast {
                println '执行插件创建的任务'
            }
        }
    }
}
apply plugin:MyPlugin

2、buildSrc项目

可以将插件的源代码放在rootProjectDir/buildSrc/src/main/java目录中(rootProjectDir/buildSrc/src/main/groovy或rootProjectDir/buildSrc/src/main/kotlin根据喜欢的语言)。Gradle 将负责编译和测试插件,并使其在构建脚本的类路径中可用。该插件对构建使用的每个构建脚本都是可见的。但是,它在构建之外是不可见的,因此不能在定义它的构建之外重用插件。

步骤

1、新建一个buildSrc目录,特殊目录 2、编译后会自动生成build、.gradle相关文件夹 3、创建一个build.gradle文件,从其他地方拷贝过来就好 4、依赖gradle api 5、创建存放源码的目录 6、创建插件类 7、创建resources目录 ,创建properties文件配置插件索引 8、使用插件

bash 复制代码
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.weizhenbin.handleapk'
}

注意:META-INF.gradle-plugins 这是两个文件夹META-INF/gradle-plugins

3、独立项目

可以为插件创建一个单独的项目。该项目生成并发布一个 JAR,然后您可以在多个构建中使用它并与他人共享。通常,这个 JAR 可能包含一些插件,或者将几个相关的任务类捆绑到一个库中。或者两者的某种组合。独立项目和buildSrc类似 只是要发布之后才能使用。

小案例

1、模拟apk自动打包上传服务器

1、创建一个插件 2、在gradle配置完毕之后(这里主要是可增加一些扩展配置信息)创建Task 3、依赖task,执行我们自己的task之前先执行assembleDebug task 先把包打出来,打包之前先clean 最后的依赖顺序 clean ->assembleDebug->handleApkTask

插件的工作

1、遍历编译变体,取得输出文件apk 2、接下来就是对apk做文件操作,修改、加固、上传,随意操作

Gradle应用(asm插桩)

插桩简介

Asm是一种字节码插桩技术,在android编译过程中,在class 打包带dex文件之前,对class进行操作,而这个过程需要借助transform这个task来完成,大致如图:

浅析编译apk的gradle执行过程查看gradle源码 ,可以依赖在我们的appmudule里 就能看到源码了 这里以 implementation 'com.android.tools.build:gradle:4.2.2' 为例,代码入口:

bash 复制代码
plugins {
    id 'com.android.application'
}

配置gradle的debug模式,有助于分析流程

1、Edit Configurations 2、添加 remote

3、将复制的指令 配置在gradle.properties中

4、选择debug 任务

5、执行debug

关于Transform的主要逻辑在TaskManager.createPostCompilationTasks

1、创建transform Task 2、与其他task一样 在相应顺序执行

一个小知识点 Gradle自定义了一个transform ,CustomClassTransform 几乎就是asm插桩的模版代码了,这里主要是用来让Androidstudio通过插桩来实现profiler相关监控用的 android.googlesource.com/platform/to...

一个小案例 (hook隐私 api

1、定一个类 继承 Transform 2、实现getName() 、getInputTypes()、getScopes()、isIncremental()、transform() 3、遍历jar和项目里的class文件 4、修改class文件 输出修改后的class文件

1、读取类 ClassReader cr = new ClassReader(inputStream); 2、遍历类的每个方法 visitMethod 3、扫描每个方法里的所有指令 MethodVisitor.visitMethodInsn 4、找到目标方法 if (owner.equals("android/telephony/TelephonyManager")&&name.equals("getDeviceId")) 5、修改方法 mv.visitMethodInsn(INVOKESTATIC,"com/example/gradledemo/MyTelephonyManager","getDeviceId","(Landroid/telephony/TelephonyManager;)Ljava/lang/String;",isInterface);

修改前

修改后

最后以一个基于okhttp网络监控功能插件的实现思路作为总结

背景:

移动互联网时代,极大部分的APP都需要依赖Server来获取数据,如果因为网络请求慢或者请求失败,导致用户无法顺畅的使用业务功能,会对用户体验造成极大影响。所以我们应该尽可能的监控网络请求的各个阶段,以准对不同问题进行优化,我们以hook okhttp网络库来实现一个简单的网络监控。

hook okhttp网络库是通过插桩的方式对所有使用okhttp的本地代码或者第三方库加入我们的Interceptor来达到监控网络的能力

技术实现:

插件实现:这里以独立项目的方式创建插件module

1、创建TransformPlugin

java 复制代码
public class TransformPlugin implements Plugin<Project> {
    @Override
    public void apply(Project target) {
        BaseExtension android = target.getExtensions().getByType(BaseExtension.class);
        //注册 registerTransform
        android.registerTransform(new TransformTemplate("MyTransform",false,new MyTransform()));
    }
}

2、在MyTransform中对项目的代码和jar包做遍历扫描,根据逻辑识别目标类,进行字节码修改

java 复制代码
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);
        }
    }

    @NotNull
    @Override
    public BiConsumer<InputStream, OutputStream> andThen(@NotNull BiConsumer<? super InputStream, ? super OutputStream> after) {
        return null;
    }

}

3、寻找到okhttp的okhttp2的 com/squareup/okhttp/OkHttpClient 或者okhttp3的 okhttp3/OkHttpClient$Builder

的构造函数,插入我们的代码,插入一个静态方法 addInterceptorToBuilder 或者 addInterceptorToClient

typescript 复制代码
private static final class MethodAdapter extends MethodVisitor implements Opcodes {
    public MethodAdapter(MethodVisitor mv) {
        super(ASM7, mv);
    }
    /**
     * Looks for a callsite to the non-parameter constructor ("<init>") of two specific classes,
     * When one is found, inserts a call into our interceptor after the constructor call.
     */
    @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");
        }
    }
    private static boolean isConstructor(int opcode, String name, String desc) {
        return (opcode == INVOKESPECIAL && name.equals("<init>") && desc.equals("()V"));
    }
    private static boolean isConstructor2(int opcode, String name, String desc) {
        return (opcode == INVOKESPECIAL && name.equals("<init>") && desc.equals("(Ljava/util/List;)V"));
    }

    /** Invokes a static method on our wrapper class. */
    private void invoke(String wrapper, String method, String desc) {
        super.visitMethodInsn(INVOKESTATIC, wrapper, method, desc, false);
    }
}

4、接下来只要实现我们自己的方法,添加拦截器

kotlin 复制代码
@JvmStatic
fun addInterceptorToBuilder(builder: Any?) {
    OkHttp3TrackInterceptor.addToBuilder(builder)
}
@JvmStatic
fun addToBuilder(builder: Any?) {
    if (builder is OkHttpClient.Builder) {
        builder.addInterceptor(OkHttp3MockInterceptor())
    }
}


@JvmStatic
fun addInterceptorToBuilder(client: Any) {
    addToClient(client)
}

@JvmStatic
fun addToClient(client: Any?) {
    if (client is OkHttpClient) {
        client.networkInterceptors().add(OkHttp2Interceptor())
    }
}

这样我们就能在自己的拦截器上实现自己的逻辑,网络监控,日志打印等。

最后,只要按照上述的插件篇的发布流程,发布之后就能在其他项目上用起来了。

以上从gradle的基础知识点到gradle插件的应用,赶紧学起来吧。

相关推荐
晚秋贰拾伍3 小时前
每天学点小知识之设计模式的艺术-策略模式
运维·设计模式·系统安全·运维开发·策略模式
dal118网工任子仪5 小时前
91,【7】 攻防世界 web fileclude
android·前端
taopi20245 小时前
android java 用系统弹窗的方式实现模拟点击动画特效
android
fanged5 小时前
Android学习19 -- 手搓App
android
dal118网工任子仪5 小时前
99,[7] buuctf web [羊城杯2020]easyphp
android·前端·android studio
SomeB1oody6 小时前
【Rust自学】19.5. 高级类型
开发语言·后端·设计模式·rust
村口老王11 小时前
鸿蒙开发——应用程序包及模块化设计
android·前端·harmonyos
6v6博客11 小时前
如何在 Typecho 中实现 Joe 编辑器标签自动填充
android·编辑器
纪元A梦13 小时前
Java设计模式:行为型模式→状态模式
java·设计模式·状态模式
程序员牛肉15 小时前
为什么网络上一些表情包在反复传播之后会变绿?“电子包浆”到底是怎么形成的?
android