Gradle基础与应用(插桩)

Gradle基础

Gradle生命周期

Gradle 构建进程能够分为三个不同的阶段,每个阶段具有特定的功用和使命。

1、初始化阶段:

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

settings.gradle文件指定了构建所需的项目结构。在这个阶段,您能够装备项目的包括联系和层次结构,以便Gradle知道有哪些子项目需要构建。

Gradle基础与应用(插桩)

2、装备阶段:

在装备阶段,Gradle对每个项目对象进行装备。这意味着履行构建脚本,其中包括了一切参加构建的项目的装备信息。在这个阶段,您能够界说使命(Tasks)、依靠联系、构建规矩和其他构建装备。

每个子项目的build.gradle文件在这个阶段被解析,Gradle会创立一切项目所属的使命(Tasks)以及它们之间的依靠联系,并生成一个使命有向图,这个图形表明了使命履行的次序和依靠联系。

Gradle基础与应用(插桩)

3、履行阶段:

在履行阶段,Gradle确认要履行的使命子集。这个子集由传递给Gradle命令以及当时目录中指定的使命名称参数来确认。Gradle根据装备阶段创立和装备的使命列表,顺次履行每个选定的使命。

在履行阶段,Gradle会履行从根项目到子项目的构建进程,逐个履行使命并满意使命之间的依靠联系。这是构建进程的实践履行阶段。

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

Gradle基础与应用(插桩)

经过这三个阶段,Gradle 能够完结从项目初始化到使命履行的全进程,供给了灵活而强壮的构建东西。清晰地了解这些阶段的功用有助于更好地运用和装备 Gradle 构建体系。

Gradle Task

在Gradle中,Task(使命)是构建进程的基本单位。每个Task代表一个构建阶段或操作,它能够履行编译、仿制文件、运转测验等各种构建使命。使命是Gradle构建脚本中最重要的组成部分之一。

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

1、创立Task/定位Task

//创立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类名。

/**
 * 自界说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 的履行。

Gradle基础与应用(插桩)

5、Task的一些监听办法

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

小事例

1、打印编译进程中Task的履行时长

//暂时存储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)
    }
}

Gradle基础与应用(插桩)
Gradle基础与应用(插桩)

2、打印编译进程中task的依靠联系

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插件

Gradle有三种创立插件的办法

1、build.gradle

直接在build.gradle中包括插件的源代码。这样做的优点是插件会主动编译并包括在构建脚本的类途径中,而无需履行任何操作。但是,该插件在构建脚本之外是不行见的,因而不能在界说它的构建脚本之外重用该插件。 1、在app下的build.gradle界说自界说插件MyPlugin 并完成Plugin接口 2、在app下的build.gradle运用该插件 apply plugin:MyPlugin

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、运用插件

Gradle基础与应用(插桩)

Gradle基础与应用(插桩)

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.weizhenbin.handleapk'
}

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

3、独立项目

能够为插件创立一个独自的项目。该项目生成并发布一个 JAR,然后您能够在多个构建中运用它并与他人同享。一般,这个 JAR 或许包括一些插件,或许将几个相关的使命类捆绑到一个库中。或许两者的某种组合。独立项目和buildSrc类似 仅仅要发布之后才干运用。

Gradle基础与应用(插桩)

Gradle基础与应用(插桩)

Gradle基础与应用(插桩)

小事例

1、模仿apk主动打包上传服务器

1、创立一个插件 2、在gradle装备完毕之后(这儿首要是可增加一些扩展装备信息)创立Task 3、依靠task,履行咱们自己的task之前先履行assembleDebug task 先把包打出来,打包之前先clean 最后的依靠次序 clean ->assembleDebug->handleApkTask

Gradle基础与应用(插桩)

插件的作业

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

Gradle基础与应用(插桩)

Gradle运用(asm插桩)

插桩简介

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

Gradle基础与应用(插桩)

浅析编译apk的gradle履行进程查看gradle源码 ,能够依靠在咱们的appmudule里 就能看到源码了 这儿以 implementation ‘com.android.tools.build:gradle:4.2.2’ 为例,代码进口:

plugins {
    id 'com.android.application'
}

装备gradle的debug模式,有助于剖析流程

1、Edit Configurations 2、增加 remote

Gradle基础与应用(插桩)

3、将仿制的指令 装备在gradle.properties中

Gradle基础与应用(插桩)

4、挑选debug 使命

Gradle基础与应用(插桩)

5、履行debug

Gradle基础与应用(插桩)

Gradle基础与应用(插桩)

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

Gradle基础与应用(插桩)

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文件

Gradle基础与应用(插桩)

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);

修正前

Gradle基础与应用(插桩)

修正后

Gradle基础与应用(插桩)

最后以一个根据okhttp网络监控功用插件的完成思路作为总结

布景:

移动互联网年代,极大部分的APP都需要依靠Server来获取数据,如果由于网络恳求慢或许恳求失利,导致用户无法顺利的运用业务功用,会对用户体会形成极大影响。所以咱们应该尽或许的监控网络恳求的各个阶段,以准对不同问题进行优化,咱们以hook okhttp网络库来完成一个简略的网络监控。

hook okhttp网络库是经过插桩的办法对一切运用okhttp的本地代码或许第三方库加入咱们的Interceptor来到达监控网络的能力

技能完成:

插件完成:这儿以独立项目的办法创立插件module

1、创立TransformPlugin

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包做遍历扫描,根据逻辑辨认方针类,进行字节码修正

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

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、接下来只要完成咱们自己的办法,增加拦截器

@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插件的运用,赶忙学起来吧。