Files
YukiHookAPI/docs-source/src/zh-cn/guide/example.md

677 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 用法示例
> 这里介绍了 `YukiHookAPI` 的基本工作方式以及列举了简单的 Hook 例子和常用功能。
## 结构图解
> 下方的结构描述了 `YukiHookAPI` 的基本工作方式和原理。
```:no-line-numbers
Host Environment
└── YukiMemberHookCreator
└── Class
└── MemberHookCreator
└── Member
├── Before
└── After
MemberHookCreator
└── Member
├── Before
└── After
...
YukiResourcesHookCreator
└── Resources
└── ResourcesHookCreator
└── Drawable
└── Replace
ResourcesHookCreator
└── Layout
└── Inject
...
```
> 上方的结构换做代码将可写为如下形式。
```kotlin
TargetClass.hook {
injectMember {
method {
// Your code here.
}
beforeHook {
// Your code here.
}
afterHook {
// Your code here.
}
}
}
resources().hook {
injectResource {
conditions {
// Your code here.
}
replaceTo(...)
}
}
```
## Demo
> 你可以在下方找到 API 提供的 Demo 来学习 `YukiHookAPI` 的使用方法。
- 宿主 APP Demo [点击这里查看](https://github.com/fankes/YukiHookAPI/tree/master/demo-app)
- 模块 APP Demo [点击这里查看](https://github.com/fankes/YukiHookAPI/tree/master/demo-module)
同时安装宿主和模块 Demo通过激活模块来测试宿主中被 Hook 的功能。
## 一个简单的 Hook 例子
> 这里给出了 Hook APP、Hook 系统框架与 Hook Resources 等例子,可供参考。
### Hook APP
假设,我们要 Hook `com.android.browser` 中的 `onCreate` 方法并弹出一个对话框。
在 `encase` 方法体中添加代码。
> 示例如下
```kotlin
loadApp(name = "com.android.browser") {
ActivityClass.hook {
injectMember {
method {
name = "onCreate"
param(BundleClass)
returnType = UnitType
}
afterHook {
AlertDialog.Builder(instance())
.setTitle("Hooked")
.setMessage("I am hook!")
.setPositiveButton("OK", null)
.show()
}
}
}
}
```
至此,`onCreate` 方法将被成功 Hook 并在 `com.android.browser` 中的每个 `Activity` 启动时弹出此对话框。
那么,我想继续 Hook `onStart` 方法要怎么做呢?
在刚刚的代码中,继续插入一个 `injectMember` 方法体即可。
> 示例如下
```kotlin
loadApp(name = "com.android.browser") {
ActivityClass.hook {
injectMember {
method {
name = "onCreate"
param(BundleClass)
returnType = UnitType
}
afterHook {
AlertDialog.Builder(instance())
.setTitle("Hooked")
.setMessage("I am hook!")
.setPositiveButton("OK", null)
.show()
}
}
injectMember {
method {
name = "onStart"
emptyParam()
returnType = UnitType
}
afterHook {
// Your code here.
}
}
}
}
```
对于当前项目下没有的 `Class`,你可以使用 `stub` 方式或 `findClass` 方法来得到需要 Hook 的类。
比如,我要得到 `com.example.demo.TestClass`。
> 示例如下
```kotlin
findClass(name = "com.example.demo.TestClass").hook {
injectMember {
// Your code here.
}
}
```
若 `com.example.demo` 是你要 Hook 的 APP那么写法可以更简单。
> 示例如下
```kotlin
findClass(name = "$packageName.TestClass").hook {
injectMember {
// Your code here.
}
}
```
到这里有些同学可能就开始说了,在某些场景下 `findClass` 显得有些繁琐。
因为可能有些同学有如下需求。
> 示例如下
```kotlin
const val TestClass = "com.example.demo.TestClass"
TestClass.hook {
injectMember {
// Your code here.
}
}
```
没关系,你还可以使用字符串类名直接创建一个 Hook。
> 示例如下
```kotlin
"$packageName.TestClass".hook {
injectMember {
// Your code here.
}
}
```
::: tip
更多功能请参考 [MemberHookCreator](../api/public/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreator#memberhookcreator-class)。
:::
### Hook Zygote
在 APP 启动时,新的进程被 fork 后的第一个事件 `initZygote`。
假设我们要全局 Hook 一个 APP `Activity` 的 `onCreate` 事件
在 `encase` 方法体中添加代码。
> 示例如下
```kotlin
loadZygote {
ActivityClass.hook {
injectMember {
method {
name = "onCreate"
param(BundleClass)
returnType = UnitType
}
afterHook {
// Your code here.
}
}
}
}
```
::: warning
在 **loadZygote** 中进行的功能十分有限,几乎很少的情况下需要用到 **loadZygote** 方法。
:::
### Hook 系统框架
在 `YukiHookAPI` 中Hook 系统框架的实现非常简单。
假设,你要得到 `ApplicationInfo` 与 `PackageInfo` 并对它们进行一些操作。
在 `encase` 方法体中添加代码。
> 示例如下
```kotlin
loadSystem {
ApplicationInfoClass.hook {
// Your code here.
}
PackageInfoClass.hook {
// Your code here.
}
}
```
::: danger
**loadZygote** 与 **loadSystem** 有直接性区别,**loadZygote** 会在 **initZygote** 中装载,系统框架被视为 **loadApp(name = "android")** 而存在,若要 Hook 系统框架,可直接使用 **loadSystem**。
:::
### Hook Resources
假设,我们要 Hook `com.android.browser` 中 `string` 类型的 `app_name` 内容替换为 `123`。
在 `encase` 方法体中添加代码。
> 示例如下
```kotlin
loadApp(name = "com.android.browser") {
resources().hook {
injectResource {
conditions {
name = "app_name"
string()
}
replaceTo("123")
}
}
}
```
若当前 APP 使用 `app_name` 设置了标题栏文本,则它就会变成我们的 `123`。
你还可以使用当前 Xposed 模块的 Resources 替换 Hook APP 的 Resources。
假设,我们要继续 Hook `com.android.browser` 中 `mipmap` 类型的 `ic_launcher`。
> 示例如下
```kotlin
loadApp(name = "com.android.browser") {
resources().hook {
injectResource {
conditions {
name = "ic_launcher"
mipmap()
}
replaceToModuleResource(R.mipmap.ic_launcher)
}
}
}
```
至此目标 APP 的图标将会被替换为我们设置的图标。
若你想替换系统框架的资源,同样也可以这样实现,只需要把 `loadApp` 换成 `loadZygote` 即可。
> 示例如下
```kotlin
loadZygote {
resources().hook {
// Your code here.
}
}
```
::: tip
更多功能请参考 [ResourcesHookCreator](../api/public/com/highcapable/yukihookapi/hook/core/YukiResourcesHookCreator#resourceshookcreator-class)。
:::
### 解除 Hook
原生的 Xposed 为我们提供了一个 `XC_MethodHook.Unhook` 功能,可以从 Hook 队列中将当前 Hook 移除,`YukiHookAPI` 同样可以实现此功能。
第一种方法,保存当前注入对象的 `Result` 实例,在适当的时候和地方调用 `remove` 即可解除该注入对象。
> 示例如下
```kotlin
// 设置一个变量保存当前实例
val hookResult = injectMember {
method {
name = "test"
returnType = UnitType
}
afterHook {
// ...
}
}
// 在适当的时候调用如下方法即可
hookResult.remove()
```
第二种方法,在 Hook 回调方法中调用 `removeSelf` 移除自身。
> 示例如下
```kotlin
injectMember {
method {
name = "test"
returnType = UnitType
}
afterHook {
// 直接调用如下方法即可
removeSelf()
}
}
```
::: tip
更多功能请参考 [MemberHookCreator](../api/public/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreator#memberhookcreator-class)。
:::
## 异常处理
> `YukiHookAPI` 重新设计了对异常的监听,任何异常都不会在 Hook 过程中抛出,避免打断下一个 Hook 流程导致 Hook 进程“死掉”。
### 监听异常
你可以处理 Hook 方法过程发生的异常。
> 示例如下
```kotlin
injectMember {
// Your code here.
}.result {
// 处理 Hook 开始时的异常
onHookingFailure {}
// 处理 Hook 过程中的异常
onConductFailure { param, throwable -> }
// 处理全部异常
onAllFailure {}
// ...
}
```
在 Resources Hook 时此方法同样适用。
> 示例如下
```kotlin
injectResource {
// Your code here.
}.result {
// 处理 Hook 时的任意异常
onHookingFailure {}
// ...
}
```
你还可以处理 Hook 的 `Class` 不存在时发生的异常。
> 示例如下
```kotlin
TargetClass.hook {
injectMember {
// Your code here.
}
}.onHookClassNotFoundFailure {
// Your code here.
}
```
你还可以处理查找方法时的异常。
> 示例如下
```kotlin
method {
// Your code here.
}.onNoSuchMethod {
// Your code here.
}
```
::: tip
更多功能请参考 [MemberHookCreator.Result](../api/public/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreator#result-class)、[ResourcesHookCreator.Result](../api/public/com/highcapable/yukihookapi/hook/core/YukiResourcesHookCreator#result-class)。
:::
这里介绍了可能发生的常见异常,若要了解更多请参考 [API 异常处理](../config/api-exception)。
### 抛出异常
在某些情况下,你可以**手动抛出异常**来达到提醒某些功能存在问题的目的。
上面已经介绍过,在 `hook` 方法体内抛出的异常会被 `YukiHookAPI` 接管,避免打断下一个 Hook 流程导致 Hook 进程“死掉”。
以下是 `YukiHookAPI` 接管时这些异常的运作方式。
> 示例如下
```kotlin
// <情景1>
injectMember {
method {
throw RuntimeException("Exception Test")
}
afterHook {
// ...
}
}.result {
// 能够捕获到 RuntimeException
onHookingFailure {}
}
// <情景2>
injectMember {
method {
// ...
}
afterHook {
throw RuntimeException("Exception Test")
}
}.result {
// 能够捕获到 RuntimeException
onConductFailure { param, throwable -> }
}
```
以上情景只会在 (Xposed) 宿主环境被处理,不会对宿主自身造成任何影响。
若我们想将这些异常直接抛给宿主,原生的 Xposed 为我们提供了 `param.throwable` 方法,`YukiHookAPI` 同样可以实现此功能。
若想在 Hook 回调方法体中将一个异常直接抛给宿主,可以有如下实现方法。
> 示例如下
```kotlin
injectMember {
method {
// ...
}
afterHook {
RuntimeException("Exception Test").throwToApp()
}
}
```
你也可以直接在 Hook 回调方法体中抛出异常,然后标识将异常抛给宿主。
> 示例如下
```kotlin
injectMember {
method {
// ...
}
afterHook {
throw RuntimeException("Exception Test")
}.onFailureThrowToApp()
}
```
以上两种方法均可在宿主接收到异常从而使宿主进程崩溃。
::: warning
为了保证 Hook 调用域与宿主内调用域相互隔离,异常只有在 **beforeHook** 与 **afterHook** 回调方法体中才能抛给宿主。
:::
::: tip
更多功能请参考 [Throwable.throwToApp](../api/public/com/highcapable/yukihookapi/hook/param/HookParam#throwable-throwtoapp-i-ext-method)、[YukiMemberHookCreator.MemberMookCreator.HookCallback](../api/public/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreator#hookcallback-class)。
:::
## 状态监听
在使用 `XposedHelpers` 的同学往往会在 Hook 后打印 `Unhook` 的方法确定是否 Hook 成功。
在 `YukiHookAPI` 中,你可以用以下方法方便地重新实现这个功能。
首先我们可以监听 Hook 已经准备开始。
> 示例如下
```kotlin
YourClass.hook {
// Your code here.
}.onPrepareHook {
loggerD(msg = "$instanceClass hook start")
}
```
::: danger
**instanceClass** 建议只在 **onPrepareHook** 中使用,否则被 Hook 的 **Class** 不存在会抛出无法拦截的异常导致 Hook 进程“死掉”。
:::
然后,我们还可以对 Hook 的方法结果进行监听是否成功。
> 示例如下
```kotlin
injectMember {
// Your code here.
}.onHooked { member ->
loggerD(msg = "$member has hooked")
}
```
## 扩展用法
> 你可以在 Hook 过程中使用下面的方法方便地实现各种判断和功能。
### 多个宿主
如果你的模块需要同时处理多个 APP 的 Hook 事件,你可以使用 `loadApp` 方法体来区分你要 Hook 的 APP。
> 示例如下
```kotlin
loadApp(name = "com.android.browser") {
// Your code here.
}
loadApp(name = "com.android.phone") {
// Your code here.
}
```
::: tip
更多功能请参考 [PackageParam.loadApp](../api/public/com/highcapable/yukihookapi/hook/param/PackageParam#loadapp-method)。
:::
### 多个进程
如果你 Hook 的宿主 APP 有多个进程,你可以使用 `withProcess` 方法体来对它们分别进行 Hook。
> 示例如下
```kotlin
withProcess(mainProcessName) {
// Your code here.
}
withProcess(name = "$packageName:tool") {
// Your code here.
}
```
::: tip
更多功能请参考 [PackageParam.withProcess](../api/public/com/highcapable/yukihookapi/hook/param/PackageParam#withprocess-method)。
:::
## 写法优化
为了使代码更加简洁,你可以删去 `YukiHookAPI` 的名称,将你的 `onHook` 入口写作 `lambda` 形式。
> 示例如下
```kotlin
override fun onHook() = encase {
// Your code here.
}
```
## Xposed 模块判断自身激活状态
通常情况下,我们会选择写一个方法,使其返回 `false`,然后 Hook 掉这个方法使其返回 `true` 来证明 Hook 已经生效。
在 `YukiHookAPI` 中你完全不需要再这么做了,`YukiHookAPI` 已经帮你封装好了这个操作,你可以直接进行使用。
现在,你可以直接使用 `YukiHookAPI.Status.isXposedModuleActive` 在模块中判断自身是否被激活。
> 示例如下
```kotlin
if(YukiHookAPI.Status.isXposedModuleActive) {
// Your code here.
}
```
由于一些特殊原因,在太极、无极中的模块无法使用标准方法检测激活状态。
此时你可以使用 `YukiHookAPI.Status.isTaiChiModuleActive` 判断自身是否被激活。
> 示例如下
```kotlin
if(YukiHookAPI.Status.isTaiChiModuleActive) {
// Your code here.
}
```
若你想使用两者得兼的判断方案,`YukiHookAPI` 同样为你封装了便捷的方式。
此时你可以使用 `YukiHookAPI.Status.isModuleActive` 判断自身是否在 Xposed 或太极、无极中被激活。
> 示例如下
```kotlin
if(YukiHookAPI.Status.isModuleActive) {
// Your code here.
}
```
::: tip
更多功能请参考 [YukiHookAPI.Status](../api/public/com/highcapable/yukihookapi/YukiHookAPI#status-object)。
:::
::: warning
若模块激活判断中包含太极、无极中的激活状态,就必须将模块的 **Application** 继承于 **ModuleApplication** 或直接使用 **ModuleApplication**
**1.0.91** 版本后的 API 修改了激活逻辑判断方式,现在你可以在模块与 Hook APP (宿主) 中同时使用此 API
需要确保 **YukiHookAPI.Configs.isEnableHookModuleStatus** 是启用状态;
除了提供标准 API 的 Hook 框架之外,其它情况下模块可能都将无法判断自己是否被激活。
:::