mirror of
https://github.com/HighCapable/YukiHookAPI.git
synced 2025-09-06 10:45:47 +08:00
Added new documentations files
This commit is contained in:
275
docs-source/src/zh-cn/api/special-features/host-inject.md
Normal file
275
docs-source/src/zh-cn/api/special-features/host-inject.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# 宿主资源注入扩展
|
||||
|
||||
> 这是一个将模块资源、`Activity` 组件以及 `Context` 主题注入到宿主的扩展功能。
|
||||
|
||||
在使用以下功能之前,为防止资源 ID 互相冲突,你需要在当前 Xposed 模块项目的 `build.gradle` 中修改资源 ID。
|
||||
|
||||
- Kotlin Gradle DSL
|
||||
|
||||
```kotlin
|
||||
android {
|
||||
androidResources.additionalParameters("--allow-reserved-package-id", "--package-id", "0x64")
|
||||
}
|
||||
```
|
||||
|
||||
- Groovy
|
||||
|
||||
```groovy
|
||||
android {
|
||||
aaptOptions.additionalParameters '--allow-reserved-package-id', '--package-id', '0x64'
|
||||
}
|
||||
```
|
||||
|
||||
::: warning
|
||||
|
||||
提供的示例资源 ID 值仅供参考,不可使用 **0x7f**,默认为 **0x64**,为了防止当前宿主存在多个 Xposed 模块,建议自定义你自己的资源 ID。
|
||||
|
||||
:::
|
||||
|
||||
## 注入模块资源 (Resources)
|
||||
|
||||
在 Hook 宿主之后,我们可以直接在 Hooker 中得到的 `Context` 注入当前模块资源。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
injectMember {
|
||||
method {
|
||||
name = "onCreate"
|
||||
param(BundleClass)
|
||||
}
|
||||
afterHook {
|
||||
instance<Activity>().also {
|
||||
// <方案1> 通过 Context 注入模块资源
|
||||
it.injectModuleAppResources()
|
||||
// <方案2> 直接得到宿主 Resources 注入模块资源
|
||||
it.resources.injectModuleAppResources()
|
||||
// 直接使用模块资源 ID
|
||||
it.getString(R.id.app_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
你还可以直接在 `AppLifecycle` 中注入当前模块资源。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
onAppLifecycle {
|
||||
onCreate {
|
||||
// 全局注入模块资源,但仅限于全局生命周期
|
||||
// 类似 ImageView.setImageResource 这样的方法在 Activity 中需要单独注入
|
||||
// <方案1> 通过 Context 注入模块资源
|
||||
injectModuleAppResources()
|
||||
// <方案2> 直接得到宿主 Resources 注入模块资源
|
||||
resources.injectModuleAppResources()
|
||||
// 直接使用模块资源 ID
|
||||
getString(R.id.app_name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
更多功能请参考 [Context+Resources.injectModuleAppResources](../public/com/highcapable/yukihookapi/hook/factory/YukiHookFactory#context-resources-injectmoduleappresources-ext-method) 方法。
|
||||
|
||||
:::
|
||||
|
||||
## 注册模块 Activity
|
||||
|
||||
在 Android 系统中所有应用的 `Activity` 启动时,都需要在 `AndroidManifest.xml` 中进行注册,在 Hook 过程中,如果我们想通过宿主来直接启动模块中未注册的 `Activity` 要怎么做呢?
|
||||
|
||||
在 Hook 宿主之后,我们可以直接在 Hooker 中得到的 `Context` 注册当前模块的 `Activity` 代理。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
injectMember {
|
||||
method {
|
||||
name = "onCreate"
|
||||
param(BundleClass)
|
||||
}
|
||||
afterHook {
|
||||
instance<Activity>().registerModuleAppActivities()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
你还可以直接在 `AppLifecycle` 中注册当前模块的 `Activity` 代理。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
onAppLifecycle {
|
||||
onCreate {
|
||||
registerModuleAppActivities()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如果没有填写 `proxy` 参数,API 将会根据当前 `Context` 自动获取当前宿主的启动入口 `Activity` 进行代理。
|
||||
|
||||
通常情况下,它是有效的,但是以上情况在一些 APP 中会失效,例如一些 `Activity` 会在注册清单上加入启动参数,那么我们就需要使用另一种解决方案。
|
||||
|
||||
若未注册的 `Activity` 不能被正确启动,我们可以手动拿到宿主的 `AndroidManifest.xml` 进行分析,来得到一个注册过的 `Activity` 标签,获取其中的 `name`。
|
||||
|
||||
你需要选择一个当前宿主可能用不到的、不需要的 `Activity` 作为一个“傀儡”将其进行代理,通常是有效的。
|
||||
|
||||
比如我们已经找到了能够被代理的合适 `Activity`。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```xml
|
||||
<activity
|
||||
android:name="com.demo.test.activity.TestActivity"
|
||||
...>
|
||||
```
|
||||
|
||||
根据其中的 `name`,我们只需要在方法中加入这个参数进行注册即可。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
registerModuleAppActivities(proxy = "com.demo.test.activity.TestActivity")
|
||||
```
|
||||
|
||||
另一种情况,如果你对宿主的类编写了一个 `stub`,那么你可以直接通过 `Class` 对象来进行注册。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
registerModuleAppActivities(TestActivity::class.java)
|
||||
```
|
||||
|
||||
注册完成后,请将你需要使用宿主启动的模块中的 `Activity` 继承于 `ModuleAppActivity` 或 `ModuleAppCompatActivity`。
|
||||
|
||||
这些 `Activity` 现在无需注册即可无缝存活于宿主中。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
class HostTestActivity : ModuleAppActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// 模块资源已被自动注入,可以直接使用 xml 装载布局
|
||||
setContentView(R.layout.activity_main)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
若你需要继承于 `ModuleAppCompatActivity`,你需要手动设置 AppCompat 主题。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
class HostTestActivity : ModuleAppCompatActivity() {
|
||||
|
||||
// 这里的主题名称仅供参考,请填写你模块中已有的主题名称
|
||||
override val moduleTheme get() = R.style.Theme_AppCompat
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// 模块资源已被自动注入,可以直接使用 xml 装载布局
|
||||
setContentView(R.layout.activity_main)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
以上步骤全部完成后,你就可以在 (Xposed) 宿主环境任意存在 `Context` 的地方愉快地调用 `startActivity` 了。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
val context: Context = ... // 假设这就是你的 Context
|
||||
context.startActivity(context, HostTestActivity::class.java)
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
更多功能请参考 [Context.registerModuleAppActivities](../public/com/highcapable/yukihookapi/hook/factory/YukiHookFactory#context-registermoduleappactivities-ext-method) 方法。
|
||||
|
||||
:::
|
||||
|
||||
## 创建 ContextThemeWrapper 代理
|
||||
|
||||
有时候,我们需要使用 `MaterialAlertDialogBuilder` 来美化自己在宿主中的对话框,但是拿不到 AppCompat 主题就无法创建。
|
||||
|
||||
- 会得到如下异常
|
||||
|
||||
```:no-line-numbers
|
||||
The style on this component requires your app theme to be Theme.AppCompat (or a descendant).
|
||||
```
|
||||
|
||||
这时,我们想在宿主被 Hook 的当前 `Activity` 中使用 `MaterialAlertDialogBuilder` 来创建对话框,就可以有如下方法。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
injectMember {
|
||||
method {
|
||||
name = "onCreate"
|
||||
param(BundleClass)
|
||||
}
|
||||
afterHook {
|
||||
// 使用 applyModuleTheme 创建一个当前模块中的主题资源
|
||||
val appCompatContext = instance<Activity>().applyModuleTheme(R.style.Theme_AppCompat)
|
||||
// 直接使用这个包装了模块主题后的 Context 创建对话框
|
||||
MaterialAlertDialogBuilder(appCompatContext)
|
||||
.setTitle("AppCompat 主题对话框")
|
||||
.setMessage("我是一个在宿主中显示的 AppCompat 主题对话框。")
|
||||
.setPositiveButton("确定", null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
你还可以对当前 `Context` 通过 `uiMode` 设置原生的夜间模式和日间模式,至少需要 Android 10 及以上系统版本支持且当前主题包含夜间模式相关元素。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
injectMember {
|
||||
method {
|
||||
name = "onCreate"
|
||||
param(BundleClass)
|
||||
}
|
||||
afterHook {
|
||||
// 定义当前模块中的主题资源
|
||||
var appCompatContext: ModuleContextThemeWrapper
|
||||
// <方案1> 直接得到 Configuration 对象进行设置
|
||||
appCompatContext = instance<Activity>()
|
||||
.applyModuleTheme(R.style.Theme_AppCompat)
|
||||
.applyConfiguration { uiMode = Configuration.UI_MODE_NIGHT_YES }
|
||||
// <方案2> 创建一个新的 Configuration 对象
|
||||
// 此方案会破坏当前宿主中原有的字体缩放大小等设置,你需要手动重新传递 densityDpi 等参数
|
||||
appCompatContext = instance<Activity>().applyModuleTheme(
|
||||
theme = R.style.Theme_AppCompat,
|
||||
configuration = Configuration().apply { uiMode = Configuration.UI_MODE_NIGHT_YES }
|
||||
)
|
||||
// 直接使用这个包装了模块主题后的 Context 创建对话框
|
||||
MaterialAlertDialogBuilder(appCompatContext)
|
||||
.setTitle("AppCompat 主题对话框")
|
||||
.setMessage("我是一个在宿主中显示的 AppCompat 主题对话框。")
|
||||
.setPositiveButton("确定", null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这样,我们就可以在宿主中非常简单地使用 `MaterialAlertDialogBuilder` 创建对话框了。
|
||||
|
||||
::: warning 可能存在的问题
|
||||
|
||||
由于一些 APP 自身使用的 **androidx** 依赖库或自定义主题可能会对当前 **MaterialAlertDialog** 实际样式造成干扰,例如对话框的按钮样式,这种情况你可以参考 **模块 Demo** 中 [这里的示例代码](https://github.com/fankes/YukiHookAPI/tree/master/demo-module/src/main/java/com/highcapable/yukihookapi/demo_module/hook/factory/ComponentCompatFactory.kt) 来修复这个问题。
|
||||
|
||||
某些 APP 在创建时可能会发生 **ClassCastException** 异常,请手动指定新的 **Configuration** 实例来进行修复。
|
||||
|
||||
:::
|
||||
|
||||
::: tip
|
||||
|
||||
更多功能请参考 [Context.applyModuleTheme](../public/com/highcapable/yukihookapi/hook/factory/YukiHookFactory#context-applymoduletheme-ext-method) 方法。
|
||||
|
||||
:::
|
82
docs-source/src/zh-cn/api/special-features/host-lifecycle.md
Normal file
82
docs-source/src/zh-cn/api/special-features/host-lifecycle.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# 宿主生命周期扩展
|
||||
|
||||
> 这是一个自动 Hook 宿主 APP 生命周期的扩展功能。
|
||||
|
||||
## 监听生命周期
|
||||
|
||||
> 通过自动化 Hook 宿主 APP 的生命周期方法,来实现监听功能。
|
||||
|
||||
我们需要监听宿主 `Application` 的启动和生命周期方法,只需要使用以下方式实现。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
loadApp(name = "com.example.demo") {
|
||||
// 注册生命周期监听
|
||||
onAppLifecycle {
|
||||
// 你可以在这里实现 Application 中的生命周期方法监听
|
||||
attachBaseContext { baseContext, hasCalledSuper ->
|
||||
// 通过判断 hasCalledSuper 来确定是否已执行 super.attachBaseContext(base) 方法
|
||||
// ...
|
||||
}
|
||||
onCreate {
|
||||
// 通过 this 得到当前 Application 实例
|
||||
// ...
|
||||
}
|
||||
onTerminate {
|
||||
// 通过 this 得到当前 Application 实例
|
||||
// ...
|
||||
}
|
||||
onLowMemory {
|
||||
// 通过 this 得到当前 Application 实例
|
||||
// ...
|
||||
}
|
||||
onTrimMemory { self, level ->
|
||||
// 可在这里判断 APP 是否已切换到后台
|
||||
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
onConfigurationChanged { self, config ->
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
更多功能请参考 [AppLifecycle](../public/com/highcapable/yukihookapi/hook/param/PackageParam#applifecycle-class)。
|
||||
|
||||
:::
|
||||
|
||||
## 注册系统广播
|
||||
|
||||
> 通过 `Application.onCreate` 方法注册系统广播,来实现对系统广播的监听。
|
||||
|
||||
我们还可以在宿主 `Application` 中注册系统广播。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
loadApp(name = "com.example.demo") {
|
||||
// 注册生命周期监听
|
||||
onAppLifecycle {
|
||||
// 注册用户解锁时的广播监听
|
||||
registerReceiver(Intent.ACTION_USER_PRESENT) { context, intent ->
|
||||
// ...
|
||||
}
|
||||
// 注册多个广播监听 - 会同时回调多次
|
||||
registerReceiver(Intent.ACTION_PACKAGE_CHANGED, Intent.ACTION_TIME_TICK) { context, intent ->
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
更多功能请参考 [AppLifecycle](../public/com/highcapable/yukihookapi/hook/param/PackageParam#applifecycle-class)。
|
||||
|
||||
:::
|
124
docs-source/src/zh-cn/api/special-features/logger.md
Normal file
124
docs-source/src/zh-cn/api/special-features/logger.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 调试日志
|
||||
|
||||
> 日志是调试过程最重要的一环,`YukiHookAPI` 为开发者封装了一套稳定高效的调试日志功能。
|
||||
|
||||
## 普通日志
|
||||
|
||||
你可以调用 `loggerD`、`loggerI`、`loggerW` 来向控制台打印普通日志。
|
||||
|
||||
使用方法如下所示。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
loggerD(msg = "This is a log")
|
||||
```
|
||||
|
||||
此时,`YukiHookAPI` 会调用 `android.util.Log` 与 `XposedBridge.log` 同时打印这条日志。
|
||||
|
||||
日志默认的 `TAG` 为你在 `YukiHookAPI.Configs.debugTag` 中设置的值。
|
||||
|
||||
你也可以动态自定义这个值,但是不建议轻易修改 `TAG` 防止过滤不到日志。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
loggerD(tag = "YukiHookAPI", msg = "This is a log")
|
||||
```
|
||||
|
||||
打印的结果为如下所示。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```:no-line-numbers
|
||||
[YukiHookAPI][D][宿主包名]--> This is a log
|
||||
```
|
||||
|
||||
你还可以使用 `LoggerType` 自定义日志打印的类型,可选择使用 `android.util.Log` 还是 `XposedBridge.log` 来打印日志。
|
||||
|
||||
默认类型为 `LoggerType.BOTH`,含义为同时使用这两个方法来打印日志。
|
||||
|
||||
比如我们仅使用 `android.util.Log` 来打印日志。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
loggerD(tag = "YukiHookAPI", msg = "This is a log", type = LoggerType.LOGD)
|
||||
```
|
||||
|
||||
或又仅使用 `XposedBridge.log` 来打印日志,此方法仅可在 (Xposed) 宿主环境使用。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
loggerD(tag = "YukiHookAPI", msg = "This is a log", type = LoggerType.XPOSEDBRIDGE)
|
||||
```
|
||||
|
||||
若你想智能区分 (Xposed) 宿主环境与模块环境,可以写为如下形式。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
loggerD(tag = "YukiHookAPI", msg = "This is a log", type = LoggerType.SCOPE)
|
||||
```
|
||||
|
||||
这样 API 就会在不同环境智能选择指定的方法类型去打印这条日志。
|
||||
|
||||
::: tip
|
||||
|
||||
更多功能请参考 [loggerD](../public/com/highcapable/yukihookapi/hook/log/LoggerFactory#loggerd-method)、[loggerI](../public/com/highcapable/yukihookapi/hook/log/LoggerFactory#loggeri-method) 及 [loggerW](../public/com/highcapable/yukihookapi/hook/log/LoggerFactory#loggerw-method) 方法。
|
||||
|
||||
:::
|
||||
|
||||
## 错误日志
|
||||
|
||||
你可以调用 `loggerE` 来向控制台打印 `E` 级别的日志。
|
||||
|
||||
使用方法如下所示。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
loggerE(msg = "This is an error")
|
||||
```
|
||||
|
||||
错误日志的级别是最高的,无论你有没有过滤仅为 `E` 级别的日志。
|
||||
|
||||
对于错误级别的日志,你还可以在后面加上一个异常堆栈。
|
||||
|
||||
```kotlin
|
||||
// 假设这就是被抛出的异常
|
||||
val throwable = Throwable(...)
|
||||
// 打印日志
|
||||
loggerE(msg = "This is an error", e = throwable)
|
||||
```
|
||||
|
||||
打印的结果为如下所示。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```:no-line-numbers
|
||||
[YukiHookAPI][E][宿主包名]--> This is an error
|
||||
```
|
||||
|
||||
同时,日志会帮你打印整个异常堆栈。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```:no-line-numbers
|
||||
java.lang.Throwable
|
||||
at com.demo.Test.<init>(...)
|
||||
at com.demo.Test.doTask(...)
|
||||
at com.demo.Test.stop(...)
|
||||
at com.demo.Test.init(...)
|
||||
at a.a.a(...)
|
||||
... 3 more
|
||||
```
|
||||
|
||||
在错误日志中,你同样也可以使用 `LoggerType` 来指定当前打印日志所用到的方法类型。
|
||||
|
||||
::: tip
|
||||
|
||||
更多功能请参考 [loggerE](../public/com/highcapable/yukihookapi/hook/log/LoggerFactory#loggere-method) 方法。
|
||||
|
||||
:::
|
1373
docs-source/src/zh-cn/api/special-features/reflection.md
Normal file
1373
docs-source/src/zh-cn/api/special-features/reflection.md
Normal file
File diff suppressed because it is too large
Load Diff
181
docs-source/src/zh-cn/api/special-features/xposed-channel.md
Normal file
181
docs-source/src/zh-cn/api/special-features/xposed-channel.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# Xposed 模块与宿主通讯桥
|
||||
|
||||
> 这是一个使用系统无序广播在模块与宿主之间发送和接收数据的解决方案。
|
||||
|
||||
::: danger 需要满足的条件
|
||||
|
||||
模块与宿主需要保持存活状态,否则无法建立通讯。
|
||||
|
||||
:::
|
||||
|
||||
## 基本用法
|
||||
|
||||
> 这里描述了 `wait` 与 `put` 方法的基本使用方法。
|
||||
|
||||
通过使用 `dataChannel` 来实现模块与宿主之间的通讯桥,原理为发送接收系统无序广播。
|
||||
|
||||
> 模块示例如下
|
||||
|
||||
```kotlin
|
||||
// 从指定包名的宿主获取
|
||||
dataChannel(packageName = "com.example.demo").wait<String>(key = "key_from_host") { value ->
|
||||
// Your code here.
|
||||
}
|
||||
// 发送给指定包名的宿主
|
||||
dataChannel(packageName = "com.example.demo").put(key = "key_from_module", value = "I am module")
|
||||
```
|
||||
|
||||
> 宿主示例如下
|
||||
|
||||
```kotlin
|
||||
// 从模块获取
|
||||
dataChannel.wait<String>(key = "key_from_module") { value ->
|
||||
// Your code here.
|
||||
}
|
||||
// 发送给模块
|
||||
dataChannel.put(key = "key_from_host", value = "I am host")
|
||||
```
|
||||
|
||||
你可以不设置 `dataChannel` 的 `value` 来达到仅通知模块或宿主回调 `wait` 方法。
|
||||
|
||||
> 模块示例如下
|
||||
|
||||
```kotlin
|
||||
// 从指定包名的宿主获取
|
||||
dataChannel(packageName = "com.example.demo").wait(key = "listener_from_host") {
|
||||
// Your code here.
|
||||
}
|
||||
// 发送给指定包名的宿主
|
||||
dataChannel(packageName = "com.example.demo").put(key = "listener_from_module")
|
||||
```
|
||||
|
||||
> 宿主示例如下
|
||||
|
||||
```kotlin
|
||||
// 从模块获取
|
||||
dataChannel.wait(key = "listener_from_module") {
|
||||
// Your code here.
|
||||
}
|
||||
// 发送给模块
|
||||
dataChannel.put(key = "listener_from_host")
|
||||
```
|
||||
|
||||
::: danger
|
||||
|
||||
接收方需要保持存活状态才能收到通讯数据。
|
||||
|
||||
:::
|
||||
|
||||
::: tip
|
||||
|
||||
更多功能请参考 [YukiHookDataChannel](../public/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel)。
|
||||
|
||||
:::
|
||||
|
||||
## 判断模块与宿主版本是否匹配
|
||||
|
||||
> 通过通讯桥功能,`YukiHookAPI` 还为你提供了在用户更新模块后,判断模块是否与宿主版本匹配的解决方案。
|
||||
|
||||
我们只需要调用 `checkingVersionEquals` 方法,即可实现这个功能。
|
||||
|
||||
在模块与宿主中可进行双向判断。
|
||||
|
||||
你可以在模块中判断指定包名的宿主是否与当前模块的版本匹配。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 从指定包名的宿主获取
|
||||
dataChannel(packageName = "com.example.demo").checkingVersionEquals { isEquals ->
|
||||
// Your code here.
|
||||
}
|
||||
```
|
||||
|
||||
你还可以在宿主中判断是否自身与当前模块的版本匹配。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 从模块获取
|
||||
dataChannel.checkingVersionEquals { isEquals ->
|
||||
// Your code here.
|
||||
}
|
||||
```
|
||||
|
||||
::: warning 方法回调的条件
|
||||
|
||||
宿主、模块保持存活状态,并在激活模块后重启了作用域中的 Hook 目标宿主对象。
|
||||
|
||||
:::
|
||||
|
||||
::: tip
|
||||
|
||||
更多功能请参考 [YukiHookDataChannel](../public/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel)。
|
||||
|
||||
:::
|
||||
|
||||
## 回调事件响应的规则
|
||||
|
||||
这里只列出了在模块中使用的例子,在宿主中相同的 `key` 始终不允许重复创建。
|
||||
|
||||
::: danger
|
||||
|
||||
在模块和宿主中,每一个 **dataChannel** 对应的 **key** 的回调事件**都不允许重复创建**,若重复,之前的回调事件会被新增加的回调事件替换,若在模块中使用,在同一个 **Activity** 中不可以重复,不同的 **Activity** 中相同的 **key** 允许重复。
|
||||
|
||||
:::
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// 回调事件 A
|
||||
dataChannel(packageName = "com.example.demo").wait(key = "test_key") {
|
||||
// Your code here.
|
||||
}
|
||||
// 回调事件 B
|
||||
dataChannel(packageName = "com.example.demo").wait(key = "test_key") {
|
||||
// Your code here.
|
||||
}
|
||||
// 回调事件 C
|
||||
dataChannel(packageName = "com.example.demo").wait(key = "other_test_key") {
|
||||
// Your code here.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OtherActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// 回调事件 D
|
||||
dataChannel(packageName = "com.example.demo").wait(key = "test_key") {
|
||||
// Your code here.
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在上述示例中,回调事件 A 会被回调事件 B 替换掉,回调事件 C 的 `key` 不与其它重复,回调事件 D 在另一个 Activity 中,所以最终回调事件 B、C、D 都可被创建成功。
|
||||
|
||||
::: danger
|
||||
|
||||
一个相同 **key** 的回调事件只会回调当前模块正在显示的 **Activity** 中注册的回调事件,例如上述中的 **test_key**,如果 **OtherActivity** 正在显示,那么 **MainActivity** 中的 **test_key** 就不会被回调。
|
||||
|
||||
相同的 **key** 在同一个 **Activity** 不同的 **Fragment** 中注册 **dataChannel**,它们依然会在当前 **Activity** 中同时被回调。
|
||||
|
||||
在模块中,你只能使用 **Activity** 的 **Context** 注册 **dataChannel**,你不能在 **Application** 以及 **Service** 等地方使用 **dataChannel**,若要在 **Fragment** 中使用 **dataChannel**,请使用 **activity?.dataChannel(...)**。
|
||||
|
||||
:::
|
||||
|
||||
## 安全性说明
|
||||
|
||||
在模块环境中,你只能接收<u>**指定包名的宿主**</u>发送的通讯数据且只能发送给<u>**指定包名的宿主**</u>。
|
||||
|
||||
::: danger
|
||||
|
||||
为了进一步防止广播滥用,通讯数据中 API 会自动指定宿主和模块的包名,防止其它 APP 监听并利用广播做出超限行为。
|
||||
|
||||
:::
|
89
docs-source/src/zh-cn/api/special-features/xposed-prefs.md
Normal file
89
docs-source/src/zh-cn/api/special-features/xposed-prefs.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Xposed 模块数据存储
|
||||
|
||||
> 这是一个自动对接 `SharedPreferences` 和 `XSharedPreferences` 的高效模块数据存储解决方案。
|
||||
|
||||
我们需要存储模块的数据,以供宿主调用,这个时候会遇到原生 `Sp` 存储的数据互通阻碍。
|
||||
|
||||
原生的 `Xposed` 给我们提供了一个 `XSharedPreferences` 用于读取模块的 `Sp` 数据。
|
||||
|
||||
## 在 Activity 中使用
|
||||
|
||||
> 这里描述了在 `Activity` 中装载 `YukiHookModulePrefs` 的场景。
|
||||
|
||||
通常情况下我们可以这样在 Hook 内对其进行初始化。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
XSharedPreferences(BuildConfig.APPLICATION_ID)
|
||||
```
|
||||
|
||||
有没有方便快捷的解决方案呢,此时你就可以使用 `YukiHookAPI` 的扩展能力快速实现这个功能。
|
||||
|
||||
当你在模块中存储数据的时候,若当前处于 `Activity` 内,可以使用如下方法。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
modulePrefs.putString("test_name", "saved_value")
|
||||
```
|
||||
|
||||
当你在 Hook 中读取数据时,可以使用如下方法。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
val testName = prefs.getString("test_name", "default_value")
|
||||
```
|
||||
|
||||
你不需要考虑传入模块的包名以及一系列复杂的权限配置,一切都交给 `YukiHookModulePrefs` 来处理。
|
||||
|
||||
若要实现存储的区域划分,你可以指定每个 `prefs` 文件的名称。
|
||||
|
||||
在模块的 `Activity` 中这样使用。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 推荐用法
|
||||
modulePrefs("specify_file_name").putString("test_name", "saved_value")
|
||||
// 也可以这样用
|
||||
modulePrefs.name("specify_file_name").putString("test_name", "saved_value")
|
||||
```
|
||||
|
||||
在 Hook 中这样读取。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 推荐用法
|
||||
val testName = prefs("specify_file_name").getString("test_name", "default_value")
|
||||
// 也可以这样用
|
||||
val testName = prefs.name("specify_file_name").getString("test_name", "default_value")
|
||||
```
|
||||
|
||||
若你的项目中有大量的固定数据需要存储和读取,推荐使用 `PrefsData` 来创建模板。
|
||||
|
||||
::: tip
|
||||
|
||||
更多功能请参考 [YukiHookModulePrefs](../public/com/highcapable/yukihookapi/hook/xposed/prefs/YukiHookModulePrefs)、[PrefsData](../public/com/highcapable/yukihookapi/hook/xposed/prefs/data/PrefsData)。
|
||||
|
||||
:::
|
||||
|
||||
## 在 PreferenceFragment 中使用
|
||||
|
||||
> 这里描述了在 `PreferenceFragment` 中装载 `YukiHookModulePrefs` 的场景。
|
||||
|
||||
若你的模块使用了 `PreferenceFragmentCompat`,你现在可以将其继承类开始迁移到 `ModulePreferenceFragment`。
|
||||
|
||||
::: danger
|
||||
|
||||
你必须继承 **ModulePreferenceFragment** 才能实现 **YukiHookModulePrefs** 的模块存储功能。
|
||||
|
||||
:::
|
||||
|
||||
::: tip
|
||||
|
||||
更多功能请参考 [ModulePreferenceFragment](../public/com/highcapable/yukihookapi/hook/xposed/prefs/ui/ModulePreferenceFragment)。
|
||||
|
||||
:::
|
Reference in New Issue
Block a user