diff --git a/docs/api/public/YukiMemberHookCreater.md b/docs/api/public/YukiMemberHookCreater.md index bd667fcb..2eef7add 100644 --- a/docs/api/public/YukiMemberHookCreater.md +++ b/docs/api/public/YukiMemberHookCreater.md @@ -565,6 +565,7 @@ injectMember { // Your code here. }.result { onHooked {} + onAlreadyHooked {} ignoredConductFailure() onHookingFailure {} // ... @@ -605,6 +606,24 @@ fun onHooked(initiate: (Member) -> Unit): Result 在首次 Hook 成功后回调。 +在重复 Hook 时会回调 `onAlreadyHooked`。 + +##### onAlreadyHooked [method] + +```kotlin +fun onAlreadyHooked(initiate: (Member) -> Unit): Result +``` + +**变更记录** + +`v1.0.89` `新增` + +**功能描述** + +> 监听 `member` 重复 Hook 的回调方法。 + +!> 同一个 `hookClass` 中的同一个 `member` 不会被 API 重复 Hook,若由于各种原因重复 Hook 会回调此方法。 + ##### onNoSuchMemberFailure [method] ```kotlin diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreater.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreater.kt index cbe534ed..a90e7de6 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreater.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreater.kt @@ -162,6 +162,9 @@ class YukiMemberHookCreater(@PublishedApi internal val packageParam: PackagePara /** Hook 成功时回调 */ private var onHookedCallback: ((Member) -> Unit)? = null + /** 重复 Hook 时回调 */ + private var onAlreadyHookedCallback: ((Member) -> Unit)? = null + /** 找不到 [member] 出现错误回调 */ private var onNoSuchMemberFailureCallback: ((Throwable) -> Unit)? = null @@ -502,11 +505,14 @@ class YukiMemberHookCreater(@PublishedApi internal val packageParam: PackagePara if (member != null) member.also { member -> runCatching { - (if (isReplaceHookMode) - YukiHookBridge.Hooker.hookMethod(member, replaceMent)?.also { onHookedCallback?.invoke(it) } - ?: error("Hook Member [$member] failed") - else YukiHookBridge.Hooker.hookMethod(member, beforeAfterHook)?.also { onHookedCallback?.invoke(it) } - ?: error("Hook Member [$member] failed")) + (if (isReplaceHookMode) YukiHookBridge.Hooker.hookMethod(member, replaceMent) + else YukiHookBridge.Hooker.hookMethod(member, beforeAfterHook)).also { + when { + it.first == null -> error("Hook Member [$member] failed") + it.second -> onAlreadyHookedCallback?.invoke(it.first!!) + else -> onHookedCallback?.invoke(it.first!!) + } + } }.onFailure { onHookingFailureCallback?.invoke(it) onAllFailureCallback?.invoke(it) @@ -527,24 +533,22 @@ class YukiMemberHookCreater(@PublishedApi internal val packageParam: PackagePara else runCatching { when (hookMemberMode) { HookMemberMode.HOOK_ALL_METHODS -> - if (isReplaceHookMode) - YukiHookBridge.Hooker.hookAllMethods(hookClass.instance, allMethodsName, replaceMent).also { - if (it.isEmpty()) throw NoSuchMethodError("No Method name \"$allMethodsName\" matched") - else it.forEach { e -> onHookedCallback?.invoke(e) } + (if (isReplaceHookMode) YukiHookBridge.Hooker.hookAllMethods(hookClass.instance, allMethodsName, replaceMent) + else YukiHookBridge.Hooker.hookAllMethods(hookClass.instance, allMethodsName, beforeAfterHook)).also { + when { + it.first.isEmpty() -> throw NoSuchMethodError("No Method name \"$allMethodsName\" matched") + it.second -> it.first.forEach { e -> onAlreadyHookedCallback?.invoke(e) } + else -> it.first.forEach { e -> onHookedCallback?.invoke(e) } } - else YukiHookBridge.Hooker.hookAllMethods(hookClass.instance, allMethodsName, beforeAfterHook).also { - if (it.isEmpty()) throw NoSuchMethodError("No Method name \"$allMethodsName\" matched") - else it.forEach { e -> onHookedCallback?.invoke(e) } } HookMemberMode.HOOK_ALL_CONSTRUCTORS -> - if (isReplaceHookMode) - YukiHookBridge.Hooker.hookAllConstructors(hookClass.instance, replaceMent).also { - if (it.isEmpty()) throw NoSuchMethodError("No Constructor matched") - else it.forEach { e -> onHookedCallback?.invoke(e) } + (if (isReplaceHookMode) YukiHookBridge.Hooker.hookAllConstructors(hookClass.instance, replaceMent) + else YukiHookBridge.Hooker.hookAllConstructors(hookClass.instance, beforeAfterHook)).also { + when { + it.first.isEmpty() -> throw NoSuchMethodError("No Constructor matched") + it.second -> it.first.forEach { e -> onAlreadyHookedCallback?.invoke(e) } + else -> it.first.forEach { e -> onHookedCallback?.invoke(e) } } - else YukiHookBridge.Hooker.hookAllConstructors(hookClass.instance, beforeAfterHook).also { - if (it.isEmpty()) throw NoSuchMethodError("No Constructor matched") - else it.forEach { e -> onHookedCallback?.invoke(e) } } else -> error("Hooked got a no error possible") } @@ -618,6 +622,8 @@ class YukiMemberHookCreater(@PublishedApi internal val packageParam: PackagePara * 监听 [member] Hook 成功的回调方法 * * 在首次 Hook 成功后回调 + * + * 在重复 Hook 时会回调 [onAlreadyHooked] * @param initiate 回调被 Hook 的 [Member] * @return [Result] 可继续向下监听 */ @@ -626,6 +632,18 @@ class YukiMemberHookCreater(@PublishedApi internal val packageParam: PackagePara return this } + /** + * 监听 [member] 重复 Hook 的回调方法 + * + * - ❗同一个 [hookClass] 中的同一个 [member] 不会被 API 重复 Hook - 若由于各种原因重复 Hook 会回调此方法 + * @param initiate 回调被重复 Hook 的 [Member] + * @return [Result] 可继续向下监听 + */ + fun onAlreadyHooked(initiate: (Member) -> Unit): Result { + onAlreadyHookedCallback = initiate + return this + } + /** * 监听 [member] 不存在发生错误的回调方法 * @param initiate 回调错误 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 d138b84f..5ff6edec 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 @@ -477,12 +477,12 @@ object YukiHookBridge { * 对接 [XposedBridge.hookMethod] * @param hookMethod 需要 Hook 的方法、构造方法 * @param callback 回调 - * @return [Member] or null + * @return [Pair] - ([Member] or null,[Boolean] 是否已经 Hook) */ - internal fun hookMethod(hookMethod: Member?, callback: YukiHookCallback): Member? { - if (hookedMembers.contains(hookMethod.toString())) return hookMethod + internal fun hookMethod(hookMethod: Member?, callback: YukiHookCallback): Pair { + if (hookedMembers.contains(hookMethod.toString())) return Pair(hookMethod, true) hookedMembers.add(hookMethod.toString()) - return XposedBridge.hookMethod(hookMethod, compatCallback(callback))?.hookedMethod + return Pair(XposedBridge.hookMethod(hookMethod, compatCallback(callback))?.hookedMethod, false) } /** @@ -492,17 +492,22 @@ object YukiHookBridge { * @param hookClass 当前 Hook 的 [Class] * @param methodName 方法名 * @param callback 回调 - * @return [HashSet] 成功 Hook 的方法数组 + * @return [Pair] - ([HashSet] 成功 Hook 的方法数组,[Boolean] 是否已经 Hook) */ - internal fun hookAllMethods(hookClass: Class<*>?, methodName: String, callback: YukiHookCallback) = HashSet().also { - val allMethodsName = "$hookClass$methodName" - if (hookedAllMethods.contains(allMethodsName)) { - hookClass?.allMethods { _, method -> if (method.name == methodName) it.add(method) } - return@also + internal fun hookAllMethods(hookClass: Class<*>?, methodName: String, callback: YukiHookCallback): Pair, Boolean> { + var isAlreadyHook = false + val hookedMembers = HashSet().also { + val allMethodsName = "$hookClass$methodName" + if (hookedAllMethods.contains(allMethodsName)) { + isAlreadyHook = true + hookClass?.allMethods { _, method -> if (method.name == methodName) it.add(method) } + return@also + } + hookedAllMethods.add(allMethodsName) + XposedBridge.hookAllMethods(hookClass, methodName, compatCallback(callback)).takeIf { it.isNotEmpty() } + ?.forEach { e -> it.add(e.hookedMethod) } } - hookedAllMethods.add(allMethodsName) - XposedBridge.hookAllMethods(hookClass, methodName, compatCallback(callback)).takeIf { it.isNotEmpty() } - ?.forEach { e -> it.add(e.hookedMethod) } + return Pair(hookedMembers, isAlreadyHook) } /** @@ -511,17 +516,22 @@ object YukiHookBridge { * 对接 [XposedBridge.hookAllConstructors] * @param hookClass 当前 Hook 的 [Class] * @param callback 回调 - * @return [HashSet] 成功 Hook 的构造方法数组 + * @return [Pair] - ([HashSet] 成功 Hook 的构造方法数组,[Boolean] 是否已经 Hook) */ - internal fun hookAllConstructors(hookClass: Class<*>?, callback: YukiHookCallback) = HashSet().also { - val allConstructorsName = "$hookClass" - if (hookedAllConstructors.contains(allConstructorsName)) { - hookClass?.allConstructors { _, constructor -> it.add(constructor) } - return@also + internal fun hookAllConstructors(hookClass: Class<*>?, callback: YukiHookCallback): Pair, Boolean> { + var isAlreadyHook = false + val hookedMembers = HashSet().also { + val allConstructorsName = "$hookClass" + if (hookedAllConstructors.contains(allConstructorsName)) { + isAlreadyHook = true + hookClass?.allConstructors { _, constructor -> it.add(constructor) } + return@also + } + hookedAllConstructors.add(allConstructorsName) + XposedBridge.hookAllConstructors(hookClass, compatCallback(callback)).takeIf { it.isNotEmpty() } + ?.forEach { e -> it.add(e.hookedMethod) } } - hookedAllConstructors.add(allConstructorsName) - XposedBridge.hookAllConstructors(hookClass, compatCallback(callback)).takeIf { it.isNotEmpty() } - ?.forEach { e -> it.add(e.hookedMethod) } + return Pair(hookedMembers, isAlreadyHook) } /**