mirror of
https://github.com/HighCapable/YukiHookAPI.git
synced 2025-09-04 09:45:19 +08:00
Added DexClassFinder and toClass, hasClass function using in reflection documentation
This commit is contained in:
@@ -8,15 +8,359 @@
|
||||
|
||||
### 对象转换
|
||||
|
||||
> 敬请期待。
|
||||
假设我们要得到一个不能直接调用的 `Class`,通常情况下,我们可以使用标准的反射 API 去查找这个 `Class`。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 默认 ClassLoader 环境下的 Class
|
||||
var instance = Class.forName("com.demo.Test")
|
||||
// 指定 ClassLoader 环境下的 Class
|
||||
val customClassLoader: ClassLoader? = ... // 假设这个就是你的 ClassLoader
|
||||
var instance = customClassLoader?.loadClass("com.demo.Test")
|
||||
```
|
||||
|
||||
这种写法大概不是很友好,此时 `YukiHookAPI` 就为你提供了一个可在任意地方使用的语法糖。
|
||||
|
||||
以上写法换做 `YukiHookAPI` 可写作如下形式。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 直接得到这个 Class
|
||||
// 如果当前正处于 PackageParam 环境,那么你可以不需要考虑 ClassLoader
|
||||
var instance = "com.demo.Test".toClass()
|
||||
// 自定义 Class 所在的 ClassLoader
|
||||
val customClassLoader: ClassLoader? = ... // 假设这个就是你的 ClassLoader
|
||||
var instance = "com.demo.Test".toClass(customClassLoader)
|
||||
```
|
||||
|
||||
我们还可以通过映射来得到一个存在的 `Class` 对象。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 假设这个 Class 是能够被直接得到的
|
||||
var instance = classOf<Test>()
|
||||
// 我们同样可以自定义 Class 所在的 ClassLoader,这对于 stub 来说非常有效
|
||||
val customClassLoader: ClassLoader? = ... // 假设这个就是你的 ClassLoader
|
||||
var instance = classOf<Test>(customClassLoader)
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
更多功能请参考 [String.toClass](../public/com/highcapable/yukihookapi/hook/factory/ReflectionFactory#string-toclass-ext-method)、[PackageParam → String+VariousClass.toClass](../public/com/highcapable/yukihookapi/hook/param/PackageParam#string-variousclass-toclass-i-ext-method)、[classOf](../public/com/highcapable/yukihookapi/hook/factory/ReflectionFactory#classof-method) 方法。
|
||||
|
||||
:::
|
||||
|
||||
### 存在判断
|
||||
|
||||
> 敬请期待。
|
||||
假设我们要判断一个 `Class` 是否存在,通常情况下,我们可以使用标准的反射 API 去查找这个 `Class` 通过异常来判断是否存在。
|
||||
|
||||
### 模糊查询
|
||||
> 示例如下
|
||||
|
||||
> 敬请期待。
|
||||
```kotlin
|
||||
// 默认 ClassLoader 环境下的 Class
|
||||
var isExist = try {
|
||||
Class.forName("com.demo.Test")
|
||||
true
|
||||
} catch (_: Throwable) {
|
||||
false
|
||||
}
|
||||
// 指定 ClassLoader 环境下的 Class
|
||||
val customClassLoader: ClassLoader? = ... // 假设这个就是你的 ClassLoader
|
||||
var isExist = try {
|
||||
customClassLoader?.loadClass("com.demo.Test")
|
||||
true
|
||||
} catch (_: Throwable) {
|
||||
false
|
||||
}
|
||||
```
|
||||
|
||||
这种写法大概不是很友好,此时 `YukiHookAPI` 就为你提供了一个可在任意地方使用的语法糖。
|
||||
|
||||
以上写法换做 `YukiHookAPI` 可写作如下形式。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 判断这个 Class 是否存在
|
||||
// 如果当前正处于 PackageParam 环境,那么你可以不需要考虑 ClassLoader
|
||||
var isExist = "com.demo.Test".hasClass()
|
||||
// 自定义 Class 所在的 ClassLoader
|
||||
val customClassLoader: ClassLoader? = ... // 假设这个就是你的 ClassLoader
|
||||
var isExist = "com.demo.Test".hasClass(customClassLoader)
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
更多功能请参考 [String.hasClass](../public/com/highcapable/yukihookapi/hook/factory/ReflectionFactory#string-hasclass-ext-method)、[PackageParam → String.hasClass](../public/com/highcapable/yukihookapi/hook/param/PackageParam#string-hasclass-i-ext-method) 方法。
|
||||
|
||||
:::
|
||||
|
||||
### 模糊查找 <Badge type="tip" text="Beta" vertical="middle" />
|
||||
|
||||
在 R8 等工具混淆后的宿主 **Dex** 中的 `Class` 名称将会难以分辨,且不确定其正确位置,不能直接通过 [对象转换](#对象转换) 来得到。
|
||||
|
||||
此时就有了 `DexClassFinder`,它的作用是通过需要查找的 `Class` 中的字节码特征来确定这个 `Class` 的实例。
|
||||
|
||||
::: warning
|
||||
|
||||
目前 **DexClassFinder** 的功能尚在试验阶段,由于仅通过 Java 层实现查找功能,在宿主 **Class** 过多时性能可能不能达到最佳水平,如果发生查找不到、定位有误的问题欢迎向我们反馈。
|
||||
|
||||
由于是反射层面的 API,目前它只能通过**类与成员**的特征来定位指定的 **Class**,不能通过指定字节码中的字符串和方法内容特征来进行定位。
|
||||
|
||||
查找 **Class** 的速度取决于当前设备的性能,目前主流的移动端处理器在 **10~15w** 数量的 **Class** 中条件不算复杂的情况下大概在 **3~10s** 区间,条件稍微复杂的情况下最快速度能达到 **25s** 以内,匹配到的同类型 **Class** 越多速度越慢。
|
||||
|
||||
:::
|
||||
|
||||
#### 开始使用
|
||||
|
||||
下面是一个简单的用法示例。
|
||||
|
||||
假设下面这个 `Class` 是我们想要得到的,其中的名称经过了混淆,在每个版本可能都不一样。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```java:no-line-numbers
|
||||
package com.demo;
|
||||
|
||||
public class a extends Activity implements Serializable {
|
||||
|
||||
public a(String var1) {
|
||||
// ...
|
||||
}
|
||||
|
||||
private String a;
|
||||
|
||||
private String b;
|
||||
|
||||
private boolean a;
|
||||
|
||||
protected void onCreate(Bundle var1) {
|
||||
// ...
|
||||
}
|
||||
|
||||
private static void a(String var1) {
|
||||
// ...
|
||||
}
|
||||
|
||||
private String a(boolean var1, String var2) {
|
||||
// ...
|
||||
}
|
||||
|
||||
private void a() {
|
||||
// ...
|
||||
}
|
||||
|
||||
public void a(boolean var1, a var2, b var3, String var4) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
此时,我们想得到这个 `Class`,可以直接使用 `ClassLoader.searchClass` 方法。
|
||||
|
||||
在 `PackageParam` 中,你可以直接使用 `searchClass` 方法,它将自动指定 `appClassLoader`。
|
||||
|
||||
下方演示的条件中每一个都是可选的,条件越复杂定位越精确,同时性能也会越差。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
searchClass {
|
||||
// 从指定的包名范围搜索,实际使用时,你可以同时指定多个包名范围
|
||||
from("com.demo")
|
||||
// 指定当前 Class 的 getSimpleName 的结果,你可以直接对这个字符串进行逻辑判断
|
||||
// 这里我们不确定它的名称是不是 a,可以只判断字符串长度
|
||||
simpleName { it.length == 1 }
|
||||
// 指定继承的父类对象,如果是存在的 stub,可以直接用泛型表示
|
||||
extends<Activity>()
|
||||
// 指定继承的父类对象,可以直接写为完整类名,你还可以同时指定多个
|
||||
extends("android.app.Activity")
|
||||
// 指定实现的接口,如果是存在的 stub,可以直接用泛型表示
|
||||
implements<Serializable>()
|
||||
// 指定实现的接口,可以直接写为完整类名,你还可以同时指定多个
|
||||
implements("java.io.Serializable")
|
||||
// 指定构造方法的类型与样式,以及在当前类中存在的个数 count
|
||||
constructor { param(StringType) }.count(num = 1)
|
||||
// 指定变量的类型与样式,以及在当前类中存在的个数 count
|
||||
field { type = StringType }.count(num = 2)
|
||||
// 指定变量的类型与样式,以及在当前类中存在的个数 count
|
||||
field { type = BooleanType }.count(num = 1)
|
||||
// 直接指定所有变量在当前类中存在的个数 count
|
||||
field().count(num = 3)
|
||||
// 如果你认为变量的个数是不确定的,还可以使用如下自定义条件
|
||||
field().count(1..3)
|
||||
field().count { it >= 3 }
|
||||
// 指定方法的类型与样式,以及在当前类中存在的个数 count
|
||||
method {
|
||||
name = "onCreate"
|
||||
param(BundleClass)
|
||||
}.count(num = 1)
|
||||
// 指定方法的类型与样式,同时指定修饰符,以及在当前类中存在的个数 count
|
||||
method {
|
||||
modifiers { isStatic && isPrivate }
|
||||
param(StringType)
|
||||
returnType = UnitType
|
||||
}.count(num = 1)
|
||||
// 指定方法的类型与样式,同时指定修饰符,以及在当前类中存在的个数 count
|
||||
method {
|
||||
modifiers { isPrivate && isStatic.not() }
|
||||
param(BooleanType, StringType)
|
||||
returnType = StringType
|
||||
}.count(num = 1)
|
||||
// 指定方法的类型与样式,同时指定修饰符,以及在当前类中存在的个数 count
|
||||
method {
|
||||
modifiers { isPrivate && isStatic.not() }
|
||||
emptyParam()
|
||||
returnType = UnitType
|
||||
}.count(num = 1)
|
||||
// 指定方法的类型与样式,同时指定修饰符和模糊类型 VagueType,以及在当前类中存在的个数 count
|
||||
method {
|
||||
modifiers { isPrivate && isStatic.not() }
|
||||
param(BooleanType, VagueType, VagueType, StringType)
|
||||
returnType = UnitType
|
||||
}.count(num = 1)
|
||||
// 直接指定所有方法在当前类中存在的个数 count
|
||||
method().count(num = 5)
|
||||
// 如果你认为方法的个数是不确定的,还可以使用如下自定义条件
|
||||
method().count(1..5)
|
||||
method().count { it >= 5 }
|
||||
// 直接指定所有成员 (Member) 在当前类中存在的个数 count
|
||||
// 成员包括:Field (变量)、Method (方法)、Constructor (构造方法)
|
||||
member().count(num = 9)
|
||||
// 所有成员中一定存在一个 static 修饰符,可以这样加入此条件
|
||||
member {
|
||||
modifiers { isStatic }
|
||||
}
|
||||
}.get() // 得到这个 Class 本身的实例,找不到会返回 null
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
上述用法中对于 **Field**、**Method**、**Constructor** 的条件用法与 [Member 扩展](#member-扩展) 中的相关用法是一致的,仅有小部分区别。
|
||||
|
||||
更多功能请参考 [MemberRules](../public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MemberRules)、[FieldRules](../public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/FieldRules)、[MethodRules](../public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MethodRules)、[ConstructorRules](../public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/ConstructorRules)。
|
||||
|
||||
:::
|
||||
|
||||
#### 异步查找
|
||||
|
||||
默认情况下 `DexClassFinder` 会使用同步方式查找 `Class`,会阻塞当前线程直到找到或找不到发生异常为止,若查找消耗的时间过长,可能会导致宿主发生 **ANR** 问题。
|
||||
|
||||
针对上述问题,我们可以启用异步,只需要加入参数 `async = true`,这将不需要你再次启动一个线程,API 已帮你处理好相关问题。
|
||||
|
||||
::: warning
|
||||
|
||||
对于异步情况下你需要使用 **wait** 方法来得到结果,**get** 方法将不再起作用。
|
||||
|
||||
:::
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
searchClass(async = true) {
|
||||
// ...
|
||||
}.wait { class1 ->
|
||||
// 得到异步结果
|
||||
}
|
||||
searchClass(async = true) {
|
||||
// ...
|
||||
}.wait { class2 ->
|
||||
// 得到异步结果
|
||||
}
|
||||
```
|
||||
|
||||
这样我们的查找过程就是异步运行了,它将不会阻塞主线程,每个查找都将在单独的线程同时进行,可达到并行任务的效果。
|
||||
|
||||
#### 本地缓存
|
||||
|
||||
由于每次重新打开宿主都会重新进行查找,在宿主版本不变的情况下这是一种重复性能浪费。
|
||||
|
||||
此时我们可以通过指定 `name` 参数来对当前宿主版本的查找结果进行本地缓存,下一次将直接从本地缓存中读取查找到的类名。
|
||||
|
||||
本地缓存使用的是 `SharedPreferences`,它将被保存到宿主的数据目录中,在宿主版本更新后会重新进行缓存。
|
||||
|
||||
启用本地缓存后,将同时设置 `async = true`,你可以不需要再手动进行设置。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
searchClass(name = "com.demo.class1") {
|
||||
// ...
|
||||
}.wait { class1 ->
|
||||
// 得到异步结果
|
||||
}
|
||||
searchClass(name = "com.demo.class2") {
|
||||
// ...
|
||||
}.wait { class2 ->
|
||||
// 得到异步结果
|
||||
}
|
||||
```
|
||||
|
||||
如果你想手动清除本地缓存,可以使用如下方法清除当前版本的宿主缓存。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 直接调用,在宿主的 appContext 为空时可能会失败,失败会打印警告信息
|
||||
DexClassFinder.clearCache()
|
||||
// 监听宿主的生命周期后调用
|
||||
onAppLifecycle {
|
||||
onCreate {
|
||||
DexClassFinder.clearCache(context = this)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
你还可以清除指定版本的宿主缓存。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 直接调用,在宿主的 appContext 为空时可能会失败,失败会打印警告信息
|
||||
DexClassFinder.clearCache(versionName = "1.0", versionCode = 1)
|
||||
// 监听宿主的生命周期后调用
|
||||
onAppLifecycle {
|
||||
onCreate {
|
||||
DexClassFinder.clearCache(context = this, versionName = "1.0", versionCode = 1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 多重查找
|
||||
|
||||
如果你需要使用固定的条件同时查找一组 `Class`,那么你只需要使用 `all` 或 `waitAll` 方法来得到结果。
|
||||
|
||||
```kotlin
|
||||
// 同步查找,使用 all 得到条件全部查找到的结果
|
||||
searchClass {
|
||||
// ...
|
||||
}.all().forEach { clazz ->
|
||||
// 得到每个结果
|
||||
}
|
||||
// 同步查找,使用 all { ... } 遍历每个结果
|
||||
searchClass {
|
||||
// ...
|
||||
}.all { clazz ->
|
||||
// 得到每个结果
|
||||
}
|
||||
// 异步查找,使用 waitAll 得到条件全部查找到的结果
|
||||
searchClass(async = true) {
|
||||
// ...
|
||||
}.waitAll { classes ->
|
||||
classes.forEach {
|
||||
// 得到每个结果
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
更多功能请参考 [ClassLoader.searchClass](../public/com/highcapable/yukihookapi/hook/factory/ReflectionFactory#classloader-searchclass-ext-method)、[PackageParam.searchClass](../public/com/highcapable/yukihookapi/hook/param/PackageParam#searchclass-method) 方法。
|
||||
|
||||
:::
|
||||
|
||||
## Member 扩展
|
||||
|
||||
|
Reference in New Issue
Block a user