写在前面:
在基础熟练的基础上,完全可以考虑基于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校验通过的就是合法的字节码文件,不问来源。
比如你通过文本编辑器写的字节码文件,只要符合字节码格式,那也是合法的字节码文件。
-
字节码格式:
[扩展]IDE借助jclasslib插件查看细节:
-
jvm指令集
-
如何修改字节码- 借助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构建过程-何时修改
- 整体架构图
-
详细的图:
使用gradle-如何开发插件
-
使用groovy语言开发,面向Java平台。也可以kotlin开发(趋势)。
-
Project基础概念
每一个 build.gradle 文件都会转换成一个 Project 对象。在 Gradle 术语中,Project 对象对应的是
Build Script
。加载插件其实是调用它的apply函数。
Project 包含若干 Tasks。另外,由于 Project 对应具体的工程,所以需要为 Project 加载所需要的插件,比如为 Java 工程加载 Java 插件。其实,一个 Project 包含多少 Task 往往是插件决定的。
-
Task基础概念:
一个Task表示构建的单个原子工作,例如编译类或生成javadoc。
每个Task都属于一个Project。
一组有依赖关系的Task,组成了Project。
查看gradle源码:通过Android Studio即可
-
适配gradle版本。这里选择支持有代表性的gradle 4.2和 gradle 7.2。
-
gradle相关资料:
Gradle API 文档。点击
INDEX
,然后搜索脚本名字即可。Android Gradle权威指南.pdf
Android之Gradle 深入理解.pdf
MotionEvent处理:自行将原始的MotionEvent合并成为我们常用的事件序列。
单击
双击
长按
多指触控
Cancel事件的处理
项目
项目地址
tinyvampirepudge/hook-touch-event
效果:
插件支持gradle4.2和gradle7.2
插件支持常见的依赖方式。module、aar、jar等
gradle插件优势:
对现有业务代码,基本上是无侵入式的修改。可以如果不需要,可以随时移除。
增加字节码的相关耗时主要是在编译期间,非运行时。运行时只是增加正常的代码调用耗时。
字节码增加代码之后,不会影响mapping文件中的行号。即不会影响现有代码的错误堆栈信息。
插件兼容性
项目架构图
插件代码
目标:修改字节码,将所有经过Activity的MotionEvent都给我们的TouchEventDispatcher发送一份。
整体目录:
找到目标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插件,查看对应的字节码。如下图所示
- 通过ASM插件查看对应的ASM代码:
- 接着查看MethodVisitor的api,找到与字节码对应的数据。示例结果如下:
事件处理的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下查看日志输出:
项目运行起来后,在logcat中查看:
搜索TouchEventDispatcher、TouchEventCollector、TouchEventClassification、TouchEventReporter等关键字,即可查看对应的日志
README:更多细节,请看README
https://github.com/tinyvampirepudge/hook-touch-event#readme
FAQ:
gradle版本从v4切换到v7后,修改了gradle版本和jdk版本之后,执行gradle命令发布仓库到本地,依旧报jdk version的错误。
- 查看项目配置,我们jdk版本是11。
- 执行的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
ingradle.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的方式来执行任务的方式。
Q: Could not find tools.jar. Please check that /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home contains a valid JDK installation.
A: https://blog.csdn.net/gongsunjinqian/article/details/121228000
Q:
A:
参考:
Chapter 4. The class
File Format