gradle插件分享-手把手教你写gradle插件

写在前面:

  • 在基础熟练的基础上,完全可以考虑基于Booster、ByteX等框架来开发,效率应该会高一些。
  • 修改字节码的插件不止asm一个,还有javaassist等,可以多做一些尝试,按照需求选择适合自己项目的。
  • 本次分享的目的旨在展示gradle插件开发的过程、思路、需要的基础、遇到问题后如何分析等,核心在于打好基础。

整体目标:hook应用内所有的手势,还原成操作手势事件,交由服务端进行轨迹还原等操作。

核心任务拆分

核心任务聚焦:拿到应用内所有手势事件的MotionEvent

关键节点:

事件分发-何处hook
字节码基础-如何修改
apk构建过程-何时修改
使用gradle-如何开发插件
MotionEvent处理:自行将原始的MotionEvent合并成为我们常用的事件序列。

事件分发-何处hook

hook点:activity的时机比较合适

  • 触摸事件最终都会交由Acivity来处理
  • Acivity#dispatchTouchEvent方法负责分发给对应的view进行处理
  • Activity -> PhoneWindow -> DecorView -> ViewGroup -> View

最终做到的如下所示:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    TouchEventDispatcher.dispatchTouchEvent(MainActivity.this, ev);
    return super.dispatchTouchEvent(ev);
}

适配不同版本的Activity

  • 对特定的根Activity做处理,这样可以对其子类进行处理,可以覆盖所有的Activity。
  • AppCompatActivity:
v7_AppCompat_Activity: "android/support/v7/app/AppCompatActivity";
androidx_AppCompat_Activity: "androidx/appcompat/app/AppCompatActivity";

字节码基础-如何修改

  • 字节码本质:二进制文件
  • jvm校验通过的就是合法的字节码文件,不问来源。
  • 比如你通过文本编辑器写的字节码文件,只要符合字节码格式,那也是合法的字节码文件。
  • 字节码格式:

android怎么集成插件 安卓插件怎么写_开发语言

  • 如何修改字节码- 借助ASM等工具.
  • ASM官网
  • 1、ClassReader:对class文件进行读取与解析;
  • 2、ClassWriter:参与字节码修改,并将修改后的字节码内容以字节流的形式返回。
  • 3、使用ClassNode与MethodNode配合判断:目标Class中如果没有目标方法,就使用ClassWriter + MethodVisitor,无中生有的增加目标方法(dispatchTouchEvent)的默认实现。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    return super.dispatchTouchEvent(ev);
}
  • 2、ClassVisitor和MethodVisitor:找到目标Class(CompatActivity的子类),如果存在目标方法(dispatchTouchEvent),则直接在方法入口处增加我们的工具方法调用。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    TouchEventDispatcher.dispatchTouchEvent(MainActivity.this, ev);
    return super.dispatchTouchEvent(ev);
}
  • class会遍历两遍。

apk构建过程-何时修改

  • 整体架构图

android怎么集成插件 安卓插件怎么写_android_02

  • 详细的图:

使用gradle-如何开发插件

  • 使用groovy语言开发,面向Java平台。也可以kotlin开发(趋势)。
  • groovy官网
  • groovy api 网址
  • The Groovy Development Kit
  • Project基础概念
  • Project
  • 每一个 build.gradle 文件都会转换成一个 Project 对象。在 Gradle 术语中,Project 对象对应的是 Build Script
  • 加载插件其实是调用它的apply函数。
  • Project 包含若干 Tasks。另外,由于 Project 对应具体的工程,所以需要为 Project 加载所需要的插件,比如为 Java 工程加载 Java 插件。其实,一个 Project 包含多少 Task 往往是插件决定的
  • Task基础概念:
  • Task
  • 一个Task表示构建的单个原子工作,例如编译类或生成javadoc。
  • 每个Task都属于一个Project。
  • 一组有依赖关系的Task,组成了Project。
  • 查看gradle源码:通过Android Studio即可

android怎么集成插件 安卓插件怎么写_开发语言_03

  • 适配gradle版本。这里选择支持有代表性的gradle 4.2和 gradle 7.2。
  • gradle相关资料:

MotionEvent处理:自行将原始的MotionEvent合并成为我们常用的事件序列。

  • 单击
  • 双击
  • 长按
  • 多指触控
  • Cancel事件的处理

项目

项目地址


效果:

  • 插件支持gradle4.2和gradle7.2
  • 插件支持常见的依赖方式。module、aar、jar等

gradle插件优势:

  • 对现有业务代码,基本上是无侵入式的修改。可以如果不需要,可以随时移除。
  • 增加字节码的相关耗时主要是在编译期间,非运行时。运行时只是增加正常的代码调用耗时。
  • 字节码增加代码之后,不会影响mapping文件中的行号。即不会影响现有代码的错误堆栈信息。

android怎么集成插件 安卓插件怎么写_android_04

插件兼容性

android怎么集成插件 安卓插件怎么写_java_05

项目架构图

android怎么集成插件 安卓插件怎么写_java_06

插件代码

目标:修改字节码,将所有经过Activity的MotionEvent都给我们的TouchEventDispatcher发送一份。
整体目录:

android怎么集成插件 安卓插件怎么写_gradle_07

找到目标class
  • TouchEventTransform:遍历并找到我们的目标class,然后修改
  • getInputTypes:TransformManager.CONTENT_CLASS
  • getScopes:[实测] TransformManager.PROJECT_ONLY 配合每个子module下都apply插件,则可以遍历所有module下的class。
  • transform:遍历class文件,找到我们的目标class。
  • 借助asm插件中的ClassVisitor查找到目标类
修改目标class
  • PluginUtils.genDispatchTouchEvent:class中没有目标方法,增加对应方法默认实现的字节码
  • 借助asm插件中的MethodVisitor,在目标方法中通过字节码的方式添加我们的代码
  • 将字节码翻译成我们对应的asm插件(gradle)的代码
  • 先写一个测试文件,里面写好我们的方法
  • 然后在IDE中,借助IDE的ASM插件,查看对应的字节码。如下图所示

android怎么集成插件 安卓插件怎么写_开发语言_08

android怎么集成插件 安卓插件怎么写_java_09

  • 通过ASM插件查看对应的ASM代码:

android怎么集成插件 安卓插件怎么写_gradle_10

  • 接着查看MethodVisitor的api,找到与字节码对应的数据。示例结果如下:

android怎么集成插件 安卓插件怎么写_gradle_11

事件处理的sdk:

TouchEventDispatcher:事件分发的入口。事件接收、校验
  • 数据校验
  • 数据包装成自己的对象(自行定义)
  • 通过WeakReference解除对原有的ctx的强引用
TouchEventCollector:采集原始的事件序列。
  • 生成事件序列:
  • 以MotionEvent.ACTION_DOWN开始
  • 以MotionEvent.ACTION_UP、MotionEvent.ACTION_CANCEL、MotionEvent.ACTION_POINTER_UP结束
  • 多指触控数据剔除掉。具体看业务需求
TouchEventClassification:事件序列归类。
  • MotionEvent.ACTION_CANCEL结尾的事件序列丢弃
  • 对MotionEvent.ACTION_MOVE事件做采样处理
  • 归类为我们需要的:单击、双击、长按事件序列
TouchEventReporter:事件上报。具体如何上报到服务器,自行实现

如何查看日志:

开发或者构建过程中,在Run/Build下查看日志输出:

android怎么集成插件 安卓插件怎么写_gradle_12

android怎么集成插件 安卓插件怎么写_gradle_13

项目运行起来后,在logcat中查看:

搜索TouchEventDispatcher、TouchEventCollector、TouchEventClassification、TouchEventReporter等关键字,即可查看对应的日志

android怎么集成插件 安卓插件怎么写_gradle_14

README:更多细节,请看README

https://github.com/tinyvampirepudge/hook-touch-event#readme

FAQ:

gradle版本从v4切换到v7后,修改了gradle版本和jdk版本之后,执行gradle命令发布仓库到本地,依旧报jdk version的错误。

  • 查看项目配置,我们jdk版本是11。

android怎么集成插件 安卓插件怎么写_android怎么集成插件_15

  • 执行的gradle命令
./gradlew clean touch-event-gradle-plugin-v7:publishToLocalRepoPublicationToMavenRepository
  • 报错信息
  • What went wrong: A problem occurred evaluating project ‘:aar-module’. > Failed to apply plugin ‘com.android.internal.library’. > Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8. Your current JDK is located in /Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre You can try some of the following options: - changing the IDE settings. - changing the JAVA_HOME environment variable. - changing org.gradle.java.home in gradle.properties. * Try: > Run with --stacktrace option to get the stack trace. > Run with --info or --debug option to get more log output. > Run with --scan to get full insights.

从报错信息可以看出,在gradle命令行的环境下,jdk版本依旧不是期望的11。我们通过./gradlew -v来查看下:

(base) tinytongtong@tinytonongdembp hook-touch-event % ./gradlew -v  

------------------------------------------------------------
Gradle 7.3.3
------------------------------------------------------------

Build time:   2021-12-22 12:37:54 UTC
Revision:     6f556c80f945dc54b50e0be633da6c62dbe8dc71

Kotlin:       1.5.31
Groovy:       3.0.9
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          1.8.0_261 (Oracle Corporation 25.261-b12)
OS:           Mac OS X 10.16 x86_64

A:

  • 此时我们有两种解决方式,一种是想办法修改全局的jdk环境变量中的版本,另一种就比较简单,我们通过双击Gradle视图中对应Task的方式来执行任务(而不是./gradlew命令)。
  • 这里推荐双击Gradle视图中对应Task的方式来执行任务的方式。

android怎么集成插件 安卓插件怎么写_java_16

Q: Could not find tools.jar. Please check that /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home contains a valid JDK installation.

android怎么集成插件 安卓插件怎么写_java_17