From c8efc01e4527f3d24218c1dc01eee51f6411a2bc Mon Sep 17 00:00:00 2001 From: Fankesyooni Date: Tue, 29 Mar 2022 15:14:41 +0800 Subject: [PATCH] Added member cache function --- .../yukihookapi/demo_module/hook/HookEntry.kt | 4 + .../highcapable/yukihookapi/YukiHookAPI.kt | 33 ++++- .../hook/factory/ReflectionFactory.kt | 11 +- .../hook/store/MemberCacheStore.kt | 124 ++++++++++++++++ .../yukihookapi/hook/utils/ReflectionTool.kt | 137 ++++++++++-------- 5 files changed, 240 insertions(+), 69 deletions(-) create mode 100644 yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/store/MemberCacheStore.kt diff --git a/demo-module/src/main/java/com/highcapable/yukihookapi/demo_module/hook/HookEntry.kt b/demo-module/src/main/java/com/highcapable/yukihookapi/demo_module/hook/HookEntry.kt index 85b36f15..2293de3a 100644 --- a/demo-module/src/main/java/com/highcapable/yukihookapi/demo_module/hook/HookEntry.kt +++ b/demo-module/src/main/java/com/highcapable/yukihookapi/demo_module/hook/HookEntry.kt @@ -54,6 +54,10 @@ class HookEntry : YukiHookXposedInitProxy { // 若无和模块频繁交互数据在宿主重新启动之前建议开启 // 若需要实时交互数据建议关闭或从 [YukiHookModulePrefs] 中进行动态配置 isEnableModulePrefsCache = true + // 是否启用 [Member] 缓存功能 + // 为防止 [Member] 复用过高造成的系统 GC 问题 - 此功能默认启用 + // 除非缓存的 [Member] 发生了混淆的问题 - 否则建议启用 + isEnableMemberCache = true } } diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/YukiHookAPI.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/YukiHookAPI.kt index 228372a2..841ff8f1 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/YukiHookAPI.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/YukiHookAPI.kt @@ -35,14 +35,22 @@ import com.highcapable.yukihookapi.YukiHookAPI.configs import com.highcapable.yukihookapi.YukiHookAPI.encase import com.highcapable.yukihookapi.annotation.DoNotUseField import com.highcapable.yukihookapi.annotation.DoNotUseMethod +import com.highcapable.yukihookapi.hook.core.finder.ConstructorFinder +import com.highcapable.yukihookapi.hook.core.finder.FieldFinder +import com.highcapable.yukihookapi.hook.core.finder.MethodFinder import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker import com.highcapable.yukihookapi.hook.factory.processName import com.highcapable.yukihookapi.hook.log.* import com.highcapable.yukihookapi.hook.param.PackageParam import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper +import com.highcapable.yukihookapi.hook.store.MemberCacheStore import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookModulePrefs import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.callbacks.XC_LoadPackage +import java.lang.reflect.Constructor +import java.lang.reflect.Field +import java.lang.reflect.Member +import java.lang.reflect.Method /** * [YukiHookAPI] 的装载调用类 @@ -155,11 +163,11 @@ object YukiHookAPI { * * - ❗关闭后将会停用 [YukiHookAPI] 对全部日志的输出 * - * 但是不影响当你手动调用下面这些方法输出日志 + * 但是不影响当你手动调用下面这些方法输出日志 * - * [loggerD]、[loggerI]、[loggerW]、[loggerE] + * [loggerD]、[loggerI]、[loggerW]、[loggerE] * - * 当 [isAllowPrintingLogs] 关闭后 [isDebug] 也将同时关闭 + * 当 [isAllowPrintingLogs] 关闭后 [isDebug] 也将同时关闭 */ var isAllowPrintingLogs = true @@ -168,10 +176,27 @@ object YukiHookAPI { * * - 为防止内存复用过高问题 - 此功能默认启用 * - * 你可以手动在 [YukiHookModulePrefs] 中自由开启和关闭缓存功能以及清除缓存 + * 你可以手动在 [YukiHookModulePrefs] 中自由开启和关闭缓存功能以及清除缓存 */ var isEnableModulePrefsCache = true + /** + * 是否启用 [Member] 缓存功能 + * + * - 为防止 [Member] 复用过高造成的系统 GC 问题 - 此功能默认启用 + * + * 启用后会缓存已经找到的 [Class]、[Method]、[Constructor]、[Field] + * + * 缓存的 [Member] 都将处于 [MemberCacheStore] 的全局静态实例中 + * + * 推荐使用 [MethodFinder]、[ConstructorFinder]、[FieldFinder] 来获取 [Member] + * + * 详情请参考 [API 文档](https://github.com/fankes/YukiHookAPI/wiki/API-%E6%96%87%E6%A1%A3) + * + * 除非缓存的 [Member] 发生了混淆的问题 - 例如使用 R8 混淆后的 APP 的目标 [Member] - 否则建议启用 + */ + var isEnableMemberCache = true + /** 结束方法体 */ internal fun build() {} } diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.kt index 54c3409e..45075368 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.kt @@ -34,6 +34,7 @@ import com.highcapable.yukihookapi.hook.core.finder.ConstructorFinder import com.highcapable.yukihookapi.hook.core.finder.FieldFinder import com.highcapable.yukihookapi.hook.core.finder.MethodFinder import com.highcapable.yukihookapi.hook.core.finder.type.ModifierRules +import com.highcapable.yukihookapi.hook.store.MemberCacheStore import java.lang.reflect.Member /** @@ -63,9 +64,13 @@ val String.hasClass get() = hasClass(loader = null) * @return [Class] * @throws NoClassDefFoundError 如果找不到 [Class] 或设置了错误的 [ClassLoader] */ -fun classOf(name: String, loader: ClassLoader? = null): Class<*> = - if (loader == null) Class.forName(name) - else loader.loadClass(name) +fun classOf(name: String, loader: ClassLoader? = null): Class<*> { + val hashCode = ("[$name][$loader]").hashCode() + return MemberCacheStore.findClass(hashCode) ?: run { + (if (loader == null) Class.forName(name) + else loader.loadClass(name)).also { MemberCacheStore.putClass(hashCode, it) } + } +} /** * 通过字符串查找类是否存在 diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/store/MemberCacheStore.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/store/MemberCacheStore.kt new file mode 100644 index 00000000..7c9a6158 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/store/MemberCacheStore.kt @@ -0,0 +1,124 @@ +/* + * YukiHookAPI - An efficient Kotlin version of the Xposed Hook API. + * Copyright (C) 2019-2022 HighCapable + * https://github.com/fankes/YukiHookAPI + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This file is Created by fankes on 2022/3/29. + */ +package com.highcapable.yukihookapi.hook.store + +import com.highcapable.yukihookapi.YukiHookAPI +import java.lang.reflect.Constructor +import java.lang.reflect.Field +import java.lang.reflect.Member +import java.lang.reflect.Method + +/** + * 这是一个全局静态的 [Member] 缓存实例 + * + * 为防止 [Member] 复用过高造成的系统 GC 问题 + * + * 查询后的 [Member] 在 [YukiHookAPI.Configs.isEnableMemberCache] 启用后自动进入缓存 + */ +internal object MemberCacheStore { + + /** 缓存的 [Class] */ + private val classCacheDatas = HashMap?>() + + /** 缓存的 [Method] */ + private val methodCacheDatas = HashMap() + + /** 缓存的 [Constructor] */ + private val constructorCacheDatas = HashMap?>() + + /** 缓存的 [Field] */ + private val fieldCacheDatas = HashMap() + + /** + * 查找缓存中的 [Class] + * @param hashCode 标识符 + * @return [Class] or null + */ + internal fun findClass(hashCode: Int) = classCacheDatas[hashCode] + + /** + * 查找缓存中的 [Method] + * @param hashCode 标识符 + * @return [Method] or null + */ + internal fun findMethod(hashCode: Int) = methodCacheDatas[hashCode] + + /** + * 查找缓存中的 [Constructor] + * @param hashCode 标识符 + * @return [Constructor] or null + */ + internal fun findConstructor(hashCode: Int) = constructorCacheDatas[hashCode] + + /** + * 查找缓存中的 [Field] + * @param hashCode 标识符 + * @return [Field] or null + */ + internal fun findField(hashCode: Int) = fieldCacheDatas[hashCode] + + /** + * 写入 [Class] 到缓存 + * @param hashCode 标识符 + * @param instance 实例 + */ + internal fun putClass(hashCode: Int, instance: Class<*>?) { + if (YukiHookAPI.Configs.isEnableMemberCache.not()) return + classCacheDatas[hashCode] = instance + } + + /** + * 写入 [Method] 到缓存 + * @param hashCode 标识符 + * @param instance 实例 + */ + internal fun putMethod(hashCode: Int, instance: Method?) { + if (YukiHookAPI.Configs.isEnableMemberCache.not()) return + methodCacheDatas[hashCode] = instance + } + + /** + * 写入 [Constructor] 到缓存 + * @param hashCode 标识符 + * @param instance 实例 + */ + internal fun putConstructor(hashCode: Int, instance: Constructor<*>?) { + if (YukiHookAPI.Configs.isEnableMemberCache.not()) return + constructorCacheDatas[hashCode] = instance + } + + /** + * 写入 [Field] 到缓存 + * @param hashCode 标识符 + * @param instance 实例 + */ + internal fun putField(hashCode: Int, instance: Field?) { + if (YukiHookAPI.Configs.isEnableMemberCache.not()) return + fieldCacheDatas[hashCode] = instance + } +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/ReflectionTool.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/ReflectionTool.kt index 5c7e2f74..7be0ecc9 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/ReflectionTool.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/ReflectionTool.kt @@ -28,6 +28,7 @@ package com.highcapable.yukihookapi.hook.utils import com.highcapable.yukihookapi.hook.core.finder.type.ModifierRules +import com.highcapable.yukihookapi.hook.store.MemberCacheStore import java.lang.reflect.Constructor import java.lang.reflect.Field import java.lang.reflect.Member @@ -52,26 +53,30 @@ internal object ReflectionTool { * @throws NoSuchFieldError 如果找不到变量 */ internal fun findField(classSet: Class<*>?, name: String, modifiers: ModifierRules?, type: Class<*>?): Field { - var field: Field? = null - run { - classSet?.declaredFields?.forEach { - var conditions = name == it.name - if (type != null) conditions = conditions && it.type == type - if (modifiers != null) conditions = conditions && modifiers.contains(it) - if (conditions) { - field = it.apply { isAccessible = true } - return@run - } - } ?: error("Can't find this Field [$name] because classSet is null") + val hashCode = ("[$name][$type][$modifiers][$classSet]").hashCode() + return MemberCacheStore.findField(hashCode) ?: let { + var field: Field? = null + run { + classSet?.declaredFields?.forEach { + var conditions = name == it.name + if (type != null) conditions = conditions && it.type == type + if (modifiers != null) conditions = conditions && modifiers.contains(it) + if (conditions) { + field = it.apply { isAccessible = true } + return@run + } + } ?: error("Can't find this Field [$name] because classSet is null") + } + field?.also { MemberCacheStore.putField(hashCode, field) } + ?: throw NoSuchFieldError( + "Can't find this Field --> " + + "name:[$name] " + + "type:[$type] " + + "modifiers:${modifiers ?: "[]"} " + + "in Class [$classSet] " + + "by $TAG" + ) } - return field ?: throw NoSuchFieldError( - "Can't find this Field --> " + - "name:[$name] " + - "type:[$type] " + - "modifiers:${modifiers ?: "[]"} " + - "in Class [$classSet] " + - "by $TAG" - ) } /** @@ -94,30 +99,34 @@ internal object ReflectionTool { paramCount: Int, paramTypes: Array>? ): Method { - var method: Method? = null - run { - classSet?.declaredMethods?.forEach { - var conditions = name == it.name - if (returnType != null) conditions = conditions && it.returnType == returnType - if (paramCount >= 0) conditions = conditions && it.parameterTypes.size == paramCount - if (paramTypes != null) conditions = conditions && arrayContentsEq(paramTypes, it.parameterTypes) - if (modifiers != null) conditions = conditions && modifiers.contains(it) - if (conditions) { - method = it.apply { isAccessible = true } - return@run - } - } ?: error("Can't find this Method [$name] because classSet is null") + val hashCode = ("[$name][$paramCount][${paramTypes.typeOfString()}][$returnType][$modifiers][$classSet]").hashCode() + return MemberCacheStore.findMethod(hashCode) ?: let { + var method: Method? = null + run { + classSet?.declaredMethods?.forEach { + var conditions = name == it.name + if (returnType != null) conditions = conditions && it.returnType == returnType + if (paramCount >= 0) conditions = conditions && it.parameterTypes.size == paramCount + if (paramTypes != null) conditions = conditions && arrayContentsEq(paramTypes, it.parameterTypes) + if (modifiers != null) conditions = conditions && modifiers.contains(it) + if (conditions) { + method = it.apply { isAccessible = true } + return@run + } + } ?: error("Can't find this Method [$name] because classSet is null") + } + method?.also { MemberCacheStore.putMethod(hashCode, method) } + ?: throw NoSuchMethodError( + "Can't find this Method --> " + + "name:[$name] " + + "paramCount:[${paramCount.takeIf { it >= 0 } ?: "unspecified"}] " + + "paramTypes:[${paramTypes.typeOfString()}] " + + "returnType:[$returnType] " + + "modifiers:${modifiers ?: "[]"} " + + "in Class [$classSet] " + + "by $TAG" + ) } - return method ?: throw NoSuchMethodError( - "Can't find this Method --> " + - "name:[$name] " + - "paramCount:[${paramCount.takeIf { it >= 0 } ?: "unspecified"}] " + - "paramTypes:[${paramTypes.typeOfString()}] " + - "returnType:[$returnType] " + - "modifiers:${modifiers ?: "[]"} " + - "in Class [$classSet] " + - "by $TAG" - ) } /** @@ -136,27 +145,31 @@ internal object ReflectionTool { paramCount: Int, paramTypes: Array>? ): Constructor<*> { - var constructor: Constructor<*>? = null - run { - classSet?.declaredConstructors?.forEach { - var conditions = false - if (paramCount >= 0) conditions = it.parameterTypes.size == paramCount - if (paramTypes != null) conditions = arrayContentsEq(paramTypes, it.parameterTypes) - if (modifiers != null) conditions = conditions && modifiers.contains(it) - if (conditions) { - constructor = it.apply { isAccessible = true } - return@run - } - } ?: error("Can't find this Constructor because classSet is null") + val hashCode = ("[$paramCount][${paramTypes.typeOfString()}][$modifiers][$classSet]").hashCode() + return MemberCacheStore.findConstructor(hashCode) ?: let { + var constructor: Constructor<*>? = null + run { + classSet?.declaredConstructors?.forEach { + var conditions = false + if (paramCount >= 0) conditions = it.parameterTypes.size == paramCount + if (paramTypes != null) conditions = arrayContentsEq(paramTypes, it.parameterTypes) + if (modifiers != null) conditions = conditions && modifiers.contains(it) + if (conditions) { + constructor = it.apply { isAccessible = true } + return@run + } + } ?: error("Can't find this Constructor because classSet is null") + } + return constructor?.also { MemberCacheStore.putConstructor(hashCode, constructor) } + ?: throw NoSuchMethodError( + "Can't find this Constructor --> " + + "paramCount:[${paramCount.takeIf { it >= 0 } ?: "unspecified"}] " + + "paramTypes:[${paramTypes.typeOfString()}] " + + "modifiers:${modifiers ?: "[]"} " + + "in Class [$classSet] " + + "by $TAG" + ) } - return constructor ?: throw NoSuchMethodError( - "Can't find this Constructor --> " + - "paramCount:[${paramCount.takeIf { it >= 0 } ?: "unspecified"}] " + - "paramTypes:[${paramTypes.typeOfString()}] " + - "modifiers:${modifiers ?: "[]"} " + - "in Class [$classSet] " + - "by $TAG" - ) } /**