diff --git a/docs/api/public/YukiHookFactory.md b/docs/api/public/YukiHookFactory.md index a6f79cad..9e728aa6 100644 --- a/docs/api/public/YukiHookFactory.md +++ b/docs/api/public/YukiHookFactory.md @@ -108,6 +108,80 @@ val Context.processName: String > 获取当前进程名称。 +### injectModuleAppResources [method] + +```kotlin +fun Context.injectModuleAppResources() +``` + +**变更记录** + +`v1.0.93` `新增` + +**功能描述** + +> 向 Hook APP (宿主) `Context` 注入当前 Xposed 模块的资源。 + +注入成功后,你就可以直接使用例如 `ImageView.setImageResource` 或 `Resources.getString` 装载当前 Xposed 模块的资源 ID。 + +注入的资源作用域仅限当前 `Context`,你需要在每个用到宿主 `Context` 的地方重复调用此方法进行注入才能使用。 + +为防止资源 ID 互相冲突,你需要在当前 Xposed 模块项目的 `build.gradle` 中修改资源 ID。 + +- Kotlin Gradle DSL + +```kotlin +androidResources.additionalParameters("--allow-reserved-package-id", "--package-id", "0x64") +``` + +- Groovy + +```groovy +aaptOptions.additionalParameters '--allow-reserved-package-id', '--package-id', '0x64' +``` + +!> 提供的示例资源 ID 值仅供参考,为了防止当前宿主存在多个 Xposed 模块,建议自定义你自己的资源 ID。 + +!> 只能在 (Xposed) 宿主环境使用此功能,其它环境下使用将不生效且会打印警告信息。 + +**功能示例** + +在 Hook 宿主之后,我们可以直接在 Hooker 中得到的 `Context` 注入当前模块资源。 + +> 示例如下 + +```kotlin +injectMember { + method { + name = "onCreate" + param(BundleClass) + } + afterHook { + instance().also { + // 注入模块资源 + it.injectModuleAppResources() + // 直接使用模块资源 ID + it.getString(R.id.app_name) + } + } +} +``` + +你还可以直接在 `AppLifecycle` 中注入当前模块资源。 + +> 示例如下 + +```kotlin +onAppLifecycle { + onCreate { + // 全局注入模块资源,但仅限于全局生命周期,类似 ImageView.setImageResource 这样的方法在 Activity 中需要单独注入 + injectModuleAppResources() + // 直接使用模块资源 ID + getString(R.id.app_name) + } +} +``` + ### ~~isSupportResourcesHook [field]~~ **变更记录** diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt index 7835033a..3ce65499 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt @@ -31,12 +31,15 @@ package com.highcapable.yukihookapi.hook.factory import android.content.Context import android.content.Intent +import android.content.res.Resources import android.net.Uri import android.os.Bundle import android.os.Process +import android.widget.ImageView import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker import com.highcapable.yukihookapi.hook.param.PackageParam +import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookModulePrefs import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit @@ -97,6 +100,29 @@ val Context.processName } }.getOrNull() ?: packageName ?: "" +/** + * 向 Hook APP (宿主) [Context] 注入当前 Xposed 模块的资源 + * + * 注入成功后 - 你就可以直接使用例如 [ImageView.setImageResource] 或 [Resources.getString] 装载当前 Xposed 模块的资源 ID + * + * 注入的资源作用域仅限当前 [Context] - 你需要在每个用到宿主 [Context] 的地方重复调用此方法进行注入才能使用 + * + * 为防止资源 ID 互相冲突 - 你需要在当前 Xposed 模块项目的 build.gradle 中修改资源 ID + * + * - Kotlin Gradle DSL ↓ + * + * androidResources.additionalParameters("--allow-reserved-package-id", "--package-id", "0x64") + * + * - Groovy ↓ + * + * aaptOptions.additionalParameters '--allow-reserved-package-id', '--package-id', '0x64' + * + * - ❗提供的示例资源 ID 值仅供参考 - 为了防止当前宿主存在多个 Xposed 模块 - 建议自定义你自己的资源 ID + * + * - ❗只能在 (Xposed) 宿主环境使用此功能 - 其它环境下使用将不生效且会打印警告信息 + */ +fun Context.injectModuleAppResources() = YukiHookBridge.injectModuleAppResources(context = this) + /** * 仅判断模块是否在太极、无极中激活 * diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/YukiHookBridge.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/YukiHookBridge.kt index 39e0a3d4..3aa6e1e3 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/YukiHookBridge.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/YukiHookBridge.kt @@ -41,6 +41,7 @@ import android.content.res.Resources import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.annotation.YukiGenerateApi import com.highcapable.yukihookapi.hook.factory.hasClass +import com.highcapable.yukihookapi.hook.log.yLoggerE import com.highcapable.yukihookapi.hook.log.yLoggerW import com.highcapable.yukihookapi.hook.param.PackageParam import com.highcapable.yukihookapi.hook.param.type.HookEntryType @@ -91,7 +92,10 @@ object YukiHookBridge { private val loadedPackageNames = HashSet() /** 当前 [PackageParamWrapper] 实例数组 */ - private var packageParamWrappers = HashMap() + private val packageParamWrappers = HashMap() + + /** 已被注入到宿主 [Context] 中的当前 Xposed 模块资源 HashCode 数组 */ + private val injectedHostContextHashCodes = HashSet() /** 当前 [PackageParam] 方法体回调 */ internal var packageParamCallback: (PackageParam.() -> Unit)? = null @@ -322,6 +326,21 @@ object YukiHookBridge { } } + /** + * 向 Hook APP (宿主) [Context] 注入当前 Xposed 模块的资源 + * @param context 需要注入的 [Context] + */ + internal fun injectModuleAppResources(context: Context) { + if (injectedHostContextHashCodes.contains(context.hashCode())) return + if (hasXposedBridge) + runCatching { + YukiHookHelper.findMethod(AssetManagerClass, name = "addAssetPath", StringType) + .invoke(context.resources.assets, moduleAppFilePath) + injectedHostContextHashCodes.add(context.hashCode()) + }.onFailure { yLoggerE(msg = "Failed to inject module resources in context [$context]", e = it) } + else yLoggerW(msg = "You can only inject module resources in Xposed Environment") + } + /** 刷新当前 Xposed 模块自身 [Resources] */ internal fun refreshModuleAppResources() { dynamicModuleAppResources?.let { moduleAppResources = it }