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插件的应用,赶紧学起来吧。

相关推荐
Geoking.5 分钟前
【设计模式】外观模式(Facade)详解
java·设计模式·外观模式
点云SLAM6 分钟前
C++设计模式之单例模式(Singleton)以及相关面试问题
c++·设计模式·面试·c++11·单例模式(singleton)
Mr -老鬼12 分钟前
Android studio 最新Gradle 8.13版本“坑点”解析与避坑指南
android·ide·android studio
xiaolizi5674898 小时前
安卓远程安卓(通过frp与adb远程)完全免费
android·远程工作
阿杰100018 小时前
ADB(Android Debug Bridge)是 Android SDK 核心调试工具,通过电脑与 Android 设备(手机、平板、嵌入式设备等)建立通信,对设备进行控制、文件传输、命令等操作。
android·adb
梨落秋霜9 小时前
Python入门篇【文件处理】
android·java·python
遥不可及zzz11 小时前
Android 接入UMP
android
Coder_Boy_13 小时前
基于SpringAI的在线考试系统设计总案-知识点管理模块详细设计
android·java·javascript
冬奇Lab14 小时前
【Kotlin系列03】控制流与函数:从if表达式到Lambda的进化之路
android·kotlin·编程语言
冬奇Lab14 小时前
稳定性性能系列之十二——Android渲染性能深度优化:SurfaceFlinger与GPU
android·性能优化·debug