diff --git a/docs/api/document.md b/docs/api/document.md index fbb087f9..64a7493c 100644 --- a/docs/api/document.md +++ b/docs/api/document.md @@ -20,6 +20,10 @@ [filename](public/PrefsData.md ':include') +[filename](public/YukiHookDataChannel.md ':include') + +[filename](public/ChannelData.md ':include') + [filename](public/ModuleApplication.md ':include') [filename](public/YukiModuleResources.md ':include') diff --git a/docs/api/public/ChannelData.md b/docs/api/public/ChannelData.md new file mode 100644 index 00000000..53580060 --- /dev/null +++ b/docs/api/public/ChannelData.md @@ -0,0 +1,64 @@ +## ChannelData [class] + +```kotlin +data class ChannelData(var key: String, var value: T?) +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 数据通讯桥键值构造类。 + +这个类是对 `YukiHookDataChannel` 的一个扩展用法。 + +**功能示例** + +建立一个模板类定义模块与宿主需要发送的键值数据。 + +> 示例如下 + +```kotlin +object DataConst { + + val TEST_KV_DATA_1 = ChannelData("test_data_1", "defalut value") + val TEST_KV_DATA_2 = ChannelData("test_data_2", 0) +} +``` + +键值数据定义后,你就可以方便地在模块和宿主中调用所需要发送的数据。 + +> 模块示例如下 + +```kotlin +// 从指定包名的宿主获取 +dataChannel(packageName = "com.example.demo").wait(DataConst.TEST_KV_DATA_1) { value -> + // Your code here. +} +// 发送给指定包名的宿主 - 未填写 value 时将使用模板提供的默认值 +dataChannel(packageName = "com.example.demo").put(DataConst.TEST_KV_DATA_1, value = "sending value") +``` + +> 宿主示例如下 + +```kotlin +// 从模块获取 +dataChannel.wait(DataConst.TEST_KV_DATA_1) { value -> + // Your code here. +} +// 发送给模块 - 未填写 value 时将使用模板提供的默认值 +dataChannel.put(DataConst.TEST_KV_DATA_1, value = "sending value") +``` + +你依然可以不使用模板定义的默认值,随时修改你的默认值。 + +> 示例如下 + +```kotlin +// 获取 - 此时 value 取到的默认值将会是 2 - 并不是模板提供的 0 +dataChannel.wait(DataConst.TEST_KV_DATA_2, value = 2) { value -> + // Your code here. +} +``` \ No newline at end of file diff --git a/docs/api/public/ModuleApplication.md b/docs/api/public/ModuleApplication.md index fdd844bd..5ecf08a6 100644 --- a/docs/api/public/ModuleApplication.md +++ b/docs/api/public/ModuleApplication.md @@ -22,6 +22,8 @@ open class ModuleApplication: Application() - 在模块与宿主中装载 `YukiHookAPI.Config` 以确保 `YukiHookAPI.Configs.debugTag` 不需要重复定义 +- 在模块与宿主中使用 `YukiHookDataChannel` 进行通讯 + - 在模块中使用系统隐藏 API,核心技术引用了开源项目 [FreeReflection](https://github.com/tiann/FreeReflection) **功能示例** diff --git a/docs/api/public/PackageParam.md b/docs/api/public/PackageParam.md index 8c9845c8..e56fb758 100644 --- a/docs/api/public/PackageParam.md +++ b/docs/api/public/PackageParam.md @@ -54,7 +54,7 @@ val appContext: Application > 获取当前 Hook APP 的 `Application`。 -!> 首次装载可能是空的,请延迟一段时间再获取。 +!> 首次装载可能是空的,请延迟一段时间再获取或通过设置 `onAppLifecycle` 监听来完成。 ### appResources [field] @@ -200,6 +200,22 @@ fun prefs(name: String): YukiHookModulePrefs !> 作为 Hook API 装载时无法使用,会抛出异常。 +### dataChannel [field] + +```kotlin +val dataChannel: YukiHookDataChannel.NameSpace +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 获得当前使用的数据通讯桥命名空间对象。 + +!> 作为 Hook API 装载时无法使用,会抛出异常。 + ### resources [method] ```kotlin @@ -230,6 +246,22 @@ fun refreshModuleAppResources() > 刷新当前 Xposed 模块自身 `Resources`。 +### onAppLifecycle [method] + +```kotlin +inline fun onAppLifecycle(initiate: AppLifecycle.() -> Unit) +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 监听当前 Hook APP 生命周期装载事件。 + +!> 在 `loadZygote` 中不会被装载,仅会在 `loadSystem`、`loadApp` 中装载。 + ### loadApp [method] ```kotlin @@ -603,4 +635,102 @@ resources().hook { !> 这是固定用法,为了防止发生问题,你不可手动实现任何 `HookResources` 实例执行 `hook` 调用。 -将 Resources 的 Hook 设置为这样是为了与 `findClass(...).hook` 做到统一,使得调用起来逻辑不会混乱。 \ No newline at end of file +将 Resources 的 Hook 设置为这样是为了与 `findClass(...).hook` 做到统一,使得调用起来逻辑不会混乱。 + +### onAppLifecycle [class] + +```kotlin +inner class AppLifecycle internal constructor() +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 当前 Hook APP 的生命周期实例处理类。 + +#### attachBaseContext [method] + +```kotlin +fun attachBaseContext(initiate: (baseContext: Context, hasCalledSuper: Boolean) -> Unit) +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 监听当前 Hook APP 装载 `Application.attachBaseContext`。 + +#### onCreate [method] + +```kotlin +fun onCreate(initiate: Application.() -> Unit) +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 监听当前 Hook APP 装载 `Application.onCreate`。 + +#### onTerminate [method] + +```kotlin +fun onTerminate(initiate: Application.() -> Unit) +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 监听当前 Hook APP 装载 `Application.onTerminate`。 + +#### onLowMemory [method] + +```kotlin +fun onLowMemory(initiate: Application.() -> Unit) +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 监听当前 Hook APP 装载 `Application.onLowMemory`。 + +#### onTrimMemory [method] + +```kotlin +fun onTrimMemory(initiate: (self: Application, level: Int) -> Unit) +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 监听当前 Hook APP 装载 `Application.onTrimMemory`。 + +#### onConfigurationChanged [method] + +```kotlin +fun onConfigurationChanged(initiate: (self: Application, config: Configuration) -> Unit) +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 监听当前 Hook APP 装载 `Application.onConfigurationChanged`。 \ No newline at end of file diff --git a/docs/api/public/YukiHookAPI.md b/docs/api/public/YukiHookAPI.md index 00818c61..f56f455d 100644 --- a/docs/api/public/YukiHookAPI.md +++ b/docs/api/public/YukiHookAPI.md @@ -172,12 +172,48 @@ var isEnableModuleAppResourcesCache: Boolean !> 关闭后每次使用 `PackageParam.moduleAppResources` 都会重新创建,可能会造成运行缓慢。 +#### isEnableHookModuleStatus [field] + +```kotlin +var isEnableHookModuleStatus: Boolean +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 是否启用 Hook Xposed 模块激活等状态功能. + +为原生支持 Xposed 模块激活状态检测,此功能默认启用。 + +!> 关闭后你将不能再使用 `YukiHookModuleStatus` 中的功能。 + +#### isEnableDataChannel [field] + +```kotlin +var isEnableDataChannel: Boolean +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 是否启用当前 Xposed 模块与宿主交互的 `YukiHookDataChannel` 功能。 + +请确保 Xposed 模块的 `Application` 继承于 `ModuleApplication` 才能有效。 + +此功能默认启用,关闭后将不会在功能初始化的时候装载 `YukiHookDataChannel`。 + #### isEnableMemberCache [field] ```kotlin var isEnableMemberCache: Boolean ``` -ø + **变更记录** `v1.0.68` `新增` @@ -228,10 +264,12 @@ class HookEntryClass : IYukiHookXposedInit { override fun onInit() { YukiHookAPI.configs { debugTag = "YukiHookAPI" - isDebug = true + isDebug = BuildConfig.DEBUG isAllowPrintingLogs = true isEnableModulePrefsCache = true isEnableModuleAppResourcesCache = true + isEnableHookModuleStatus = true + isEnableDataChannel = true isEnableMemberCache = true } } @@ -251,10 +289,12 @@ class HookEntryClass : IYukiHookXposedInit { override fun onInit() = configs { debugTag = "YukiHookAPI" - isDebug = true + isDebug = BuildConfig.DEBUG isAllowPrintingLogs = true isEnableModulePrefsCache = true isEnableModuleAppResourcesCache = true + isEnableHookModuleStatus = true + isEnableDataChannel = true isEnableMemberCache = true } @@ -273,10 +313,12 @@ class HookEntryClass : IYukiHookXposedInit { override fun onInit() { YukiHookAPI.Configs.debugTag = "YukiHookAPI" - YukiHookAPI.Configs.isDebug = true + YukiHookAPI.Configs.isDebug = BuildConfig.DEBUG YukiHookAPI.Configs.isAllowPrintingLogs = true YukiHookAPI.Configs.isEnableModulePrefsCache = true YukiHookAPI.Configs.isEnableModuleAppResourcesCache = true + YukiHookAPI.Configs.isEnableHookModuleStatus = true + YukiHookAPI.Configs.isEnableDataChannel = true YukiHookAPI.Configs.isEnableMemberCache = true } diff --git a/docs/api/public/YukiHookDataChannel.md b/docs/api/public/YukiHookDataChannel.md new file mode 100644 index 00000000..fae1bccb --- /dev/null +++ b/docs/api/public/YukiHookDataChannel.md @@ -0,0 +1,223 @@ +## YukiHookDataChannel [class] + +```kotlin +class YukiHookDataChannel private constructor() +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 实现 Xposed 模块的数据通讯桥。 + +通过模块与宿主相互注册 `BroadcastReceiver` 来实现数据的交互。 + +模块需要将 `Application` 继承于 `ModuleApplication` 来实现此功能。 + +!> 模块与宿主需要保持存活状态,否则无法建立通讯。 + +### NameSpace [class] + +```kotlin +inner class NameSpace internal constructor(private val context: Context?, private val packageName: String) +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> `YukiHookDataChannel` 命名空间。 + +#### with [method] + +```kotlin +inline fun with(initiate: NameSpace.() -> Unit): NameSpace +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 创建一个调用空间。 + +#### put [method] + +```kotlin +fun put(key: String, value: T) +``` + +```kotlin +fun put(data: ChannelData, value: T?) +``` + +```kotlin +fun put(vararg data: ChannelData<*>) +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 发送键值数据。 + +**功能示例** + +通过使用 `dataChannel` 来实现模块与宿主之间的通讯桥,原理为发送接收系统无序广播。 + +> 模块示例如下 + +```kotlin +// 从指定包名的宿主获取 +dataChannel(packageName = "com.example.demo").wait(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(key = "key_from_module") { value -> + // Your code here. +} +// 发送给模块 +dataChannel.put(key = "key_from_host", value = "I am host") +``` + +!> 接收方需要保持存活状态才能收到通讯数据。 + +#### put [method] + +```kotlin +fun put(key: String) +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 仅发送键值监听,使用默认值 `VALUE_WAIT_FOR_LISTENER` 发送键值数据。 + +**功能示例** + +你可以不设置 `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") +``` + +!> 接收方需要保持存活状态才能收到通讯数据。 + +#### wait [method] + +```kotlin +fun wait(key: String, value: T?, result: (value: T) -> Unit) +``` + +```kotlin +fun wait(data: ChannelData, value: T?, result: (value: T) -> Unit) +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 获取键值数据。 + +**功能示例** + +参考第一个 `put` 方法的功能示例。 + +#### wait [method] + +```kotlin +fun wait(key: String, result: () -> Unit) +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 仅获取监听结果,不获取键值数据。 + +!> 仅限使用 `VALUE_WAIT_FOR_LISTENER` 发送的监听才能被接收。 + +**功能示例** + +参考第二个 `put` 方法的功能示例。 + +#### checkingVersionEquals [method] + +```kotlin +fun checkingVersionEquals(result: (Boolean) -> Unit) +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 获取模块与宿主的版本是否匹配。 + +通过此方法可原生判断 Xposed 模块更新后宿主并未重新装载造成两者不匹配的情况。 + +**功能示例** + +你可以在模块中判断指定包名的宿主是否与当前模块的版本匹配。 + +> 示例如下 + +```kotlin +// 从指定包名的宿主获取 +dataChannel(packageName = "com.example.demo").checkingVersionEquals { isEquals -> + // Your code here. +} +``` + +你还可以在宿主中判断是否自身与当前模块的版本匹配。 + +> 示例如下 + +```kotlin +// 从模块获取 +dataChannel.checkingVersionEquals { isEquals -> + // Your code here. +} +``` + +!> 方法回调的条件为宿主、模块保持存活状态,并在激活模块后重启了作用域中的 Hook 目标宿主对象。 \ No newline at end of file diff --git a/docs/api/public/YukiHookFactory.md b/docs/api/public/YukiHookFactory.md index eeabe2c0..a06b4e18 100644 --- a/docs/api/public/YukiHookFactory.md +++ b/docs/api/public/YukiHookFactory.md @@ -52,20 +52,6 @@ fun IYukiHookXposedInit.encase(vararg hooker: YukiBaseHooker) > 在 `IYukiHookXposedInit` 中调用 `YukiHookAPI`。 -### resources [method] - -```kotlin -fun IYukiHookXposedInit.resources(initiate: ResourcesParam.() -> Unit) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 在 `IYukiHookXposedInit` 中调用 `YukiHookAPI.resources`。 - ### modulePrefs [field] ```kotlin @@ -94,6 +80,20 @@ fun Context.modulePrefs(name: String): YukiHookModulePrefs > 获取模块的存取对象,可设置 `name` 为自定义 Sp 存储名称。 +### dataChannel [method] + +```kotlin +fun Context.dataChannel(packageName: String): YukiHookDataChannel.NameSpace +``` + +**变更记录** + +`v1.0.88` `新增` + +**功能描述** + +> 获取模块的数据通讯桥命名空间对象。 + ### processName [field] ```kotlin diff --git a/docs/config/api-exception.md b/docs/config/api-exception.md index 64b8c1ea..d9f06550 100644 --- a/docs/config/api-exception.md +++ b/docs/config/api-exception.md @@ -499,11 +499,36 @@ class MyApplication : Application() { 你只能在 [作为 Xposed 模块使用](config/xposed-using) 时使用 `YukiHookModulePrefs`,在 Hook 自身 APP 中请使用原生的 `Sp` 存储。 +!> `IllegalStateException` YukiHookDataChannel not allowed in Custom Hook API + +**异常原因** + +在 Hook 自身 APP(非 Xposed 模块) 中使用了 `YukiHookDataChannel`。 + +> 示例如下 + +```kotlin +class MyApplication : Application() { + + override fun attachBaseContext(base: Context?) { + YukiHookAPI.encase(base) { + // ❗不能在这种情况下使用 channel + channel.get("test_data", "default_data") + } + super.attachBaseContext(base) + } +} +``` + +**解决方案** + +你只能在 [作为 Xposed 模块使用](config/xposed-using) 时使用 `YukiHookDataChannel`。 + !> `IllegalStateException` Xposed modulePackageName load failed, please reset and rebuild it **异常原因** -在 Hook 过程中使用 `YukiHookModulePrefs` 时无法读取装载时的 `modulePackageName` 导致不能确定自身模块的包名。 +在 Hook 过程中使用 `YukiHookModulePrefs` 或 `YukiHookDataChannel` 时无法读取装载时的 `modulePackageName` 导致不能确定自身模块的包名。 **解决方案** @@ -523,6 +548,7 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ❗错误的使用方法 + // 构造方法已在 API 1.0.88 及以后的版本中设置为 private YukiHookModulePrefs().getBoolean("test_data") } } @@ -539,9 +565,7 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // ❗为防止存在多个实例 - 不要这样使用 - YukiHookModulePrefs(this).getBoolean("test_data") - // ✅ 推荐的使用方法 + // ✅ 正确的使用方法 modulePrefs.getBoolean("test_data") } } @@ -551,12 +575,33 @@ class MainActivity : AppCompatActivity() { **异常原因** -在使用 `YukiHookModulePrefs` 的 `get` 或 `put` 方法时传入了不支持的存储类型。 +在使用 `YukiHookModulePrefs` 的 `get` 或 `put` 方法或 `YukiHookDataChannel` 的 `wait` 或 `put` 方法时传入了不支持的存储类型。 **解决方案** `YukiHookModulePrefs` 支持的类型只有 `String`、`Set`、`Int`、`Float`、`Long`、`Boolean`,请传入支持的类型。 +`YukiHookDataChannel` 支持的类型为 `Intent.putExtra` 限制的类型,请传入支持的类型。 + +!> `IllegalStateException` YukiHookDataChannel cannot used in zygote + +**异常原因** + +在 `loadZygote` 中使用了 `YukiHookDataChannel`。 + +> 示例如下 + +```kotlin +loadZygote { + // 调用了此变量 + dataChannel... +} +``` + +**解决方案** + +`YukiHookDataChannel` 只能在 `loadSystem`、`loadApp` 中使用。 + !> `IllegalStateException` HookParam Method args index must be >= 0 **异常原因** diff --git a/docs/guide/example.md b/docs/guide/example.md index 94c9a94f..73ab01ab 100644 --- a/docs/guide/example.md +++ b/docs/guide/example.md @@ -490,4 +490,6 @@ if(isModuleActive) { 若要了解更多可 [点击这里](api/document?id=ismoduleactive-field) 进行查看。 +!> 需要确保 `YukiHookAPI.Configs.isEnableHookModuleStatus` 是启用状态。 + !> 除了提供标准 API 的 Hook 框架之外,其它情况下模块可能都将无法判断自己是否被激活。 \ No newline at end of file diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md index 4f53f5b6..48be303c 100644 --- a/docs/guide/quick-start.md +++ b/docs/guide/quick-start.md @@ -63,7 +63,7 @@ dependencies { } ``` -请将 **<version>** 修改为 [这里](about/changelog) 的最新版本 **(请打开后再次刷新页面确保获取最新数据)**。 +请将 **<version>** 修改为 [这里](about/changelog) 的最新版本。 **← 请打开后再次刷新页面确保获取最新数据** !> `YukiHookAPI` 的 `api` 与 `ksp-xposed` 依赖的版本必须一一对应,否则将会造成版本不匹配错误。 @@ -173,4 +173,4 @@ override fun attachBaseContext(base: Context?) { ### 特别说明 -!> 由于你使用了自定义的 Hook 框架而并非模块,~~`YukiHookModuleStatus`~~ ~~`YukiHookModulePrefs`~~ 以及 Resources Hook 功能将失效。 \ No newline at end of file +!> 由于你使用了自定义的 Hook 框架而并非模块,~~`YukiHookModuleStatus`~~ ~~`YukiHookModulePrefs`~~~ ~~`YukiHookDataChannel`~~ 以及 Resources Hook 功能将失效。 \ No newline at end of file diff --git a/docs/guide/special-feature.md b/docs/guide/special-feature.md index 26692727..bb84396f 100644 --- a/docs/guide/special-feature.md +++ b/docs/guide/special-feature.md @@ -1037,4 +1037,24 @@ val testName = prefs.name("specify_file_name").getString("test_name", "default_v !> 你必须继承 `ModulePreferenceFragment` 才能实现 `YukiHookModulePrefs` 的模块存储功能。 -详情请参考 [ModulePreferenceFragment](api/document?id=modulepreferencefragment-class)。 \ No newline at end of file +详情请参考 [ModulePreferenceFragment](api/document?id=modulepreferencefragment-class)。 + +## Xposed 模块与宿主通讯桥功能 + +> 这是一个使用系统无序广播在模块与宿主之间发送和接收数据的解决方案。 + +!> 需要满足的条件:模块与宿主需要保持存活状态,否则无法建立通讯。 + +### 基本用法 + +请 [点击这里](api/document?id=put-method-1) 查看详细的使用方法示例。 + +### 判断模块与宿主版本是否匹配 + +> 通过通讯桥功能,`YukiHookAPI` 还为你提供了在用户更新模块后,判断模块是否与宿主版本匹配的解决方案。 + +我们只需要调用 `checkingVersionEquals` 方法,即可实现这个功能。 + +在模块与宿主中可进行双向判断。 + +请 [点击这里](api/document?id=checkingversionequals-method) 查看详细的使用方法示例。 \ No newline at end of file