diff --git a/README.md b/README.md index a41da96e..8a9b8476 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ > 你或许可能会遇到浏览器缓存造成文档不是最新版本的问题,若已经查看过一次文档,请手动在每个页面上刷新,以获取最新版本或清除浏览器缓存。 -最新版本更新时间:2022-04-13 13:57 +最新版本更新时间:2022-04-15 04:30 ## Contacts @@ -49,6 +49,11 @@ - 工作不易,无意外情况此项目将继续维护下去,提供更多可能,欢迎打赏。

+## Third-Party Open Source Usage Statement + +- [Kotlin Symbol Processing API](https://github.com/google/ksp) +- [FreeReflection](https://github.com/tiann/FreeReflection) + ## License - [MIT](https://choosealicense.com/licenses/mit) diff --git a/demo-module/src/main/AndroidManifest.xml b/demo-module/src/main/AndroidManifest.xml index 6b859da0..ff213c17 100644 --- a/demo-module/src/main/AndroidManifest.xml +++ b/demo-module/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ package="com.highcapable.yukihookapi.demo_module"> `更新时间 2022-04-13 13:57` +`更新时间 2022-04-15 04:30` [GitHub](https://github.com/fankes/YukiHookAPI) [Get Started](#介绍) diff --git a/docs/api/document.md b/docs/api/document.md index 5b40cb69..8a3e87c8 100644 --- a/docs/api/document.md +++ b/docs/api/document.md @@ -18,6 +18,8 @@ [filename](public/PrefsData.md ':include') +[filename](public/ModuleApplication.md ':include') + [filename](public/ComponentTypeFactory.md ':include') [filename](public/GraphicsTypeFactory.md ':include') diff --git a/docs/api/public/ModuleApplication.md b/docs/api/public/ModuleApplication.md new file mode 100644 index 00000000..0f50f9fa --- /dev/null +++ b/docs/api/public/ModuleApplication.md @@ -0,0 +1,76 @@ +## ModuleApplication [class] + +```kotlin +open class ModuleApplication: Application() +``` + +变更记录 + +`v1.0.76` `新增` + +功能描述 + +> 这是对使用 `YukiHookAPI` Xposed 模块实现中的一个扩展功能。 + +在你的 Xposed 模块的 `Application` 中继承此类。 + +或在 `AndroidManifest.xml` 的 `application` 标签中指定此类。 + +目前可实现功能如下 + +- 全局共享模块中静态的 `appContext` + +- 在模块与宿主中装载 `YukiHookAPI.Config` 以确保 `YukiHookAPI.Configs.debugTag` 不需要重复定义 + +- 在模块中使用系统隐藏 API,核心技术引用了开源项目 [FreeReflection](https://github.com/tiann/FreeReflection) + +功能示例 + +将此类继承到你的自定义 `Application` 上。 + +> 示例如下 + +```kotlin +package com.demo + +class MyApplication: ModuleApplication() { + + override fun onCreate() { + super.onCreate() + } +} +``` + +在 `AndroidManifest.xml` 的 `application` 标签中指定自定义的 `Application`。 + +> 示例如下 + +```xml + +``` + +如果你不需要自定义 `Application` 可以直接将 `ModuleApplication` 设置到 `AndroidManifest.xml` 的 `application` 标签中。 + +> 示例如下 + +```xml + +``` + +### appContext [field] + +```kotlin +val appContext: ModuleApplication +``` + +变更记录 + +`v1.0.76` `新增` + +功能描述 + +> 全局静态 `Application` 实例。 \ No newline at end of file diff --git a/docs/config/api-exception.md b/docs/config/api-exception.md index 4f7d1f9e..de673020 100644 --- a/docs/config/api-exception.md +++ b/docs/config/api-exception.md @@ -355,6 +355,23 @@ method { > 这些异常会直接导致 APP 停止运行(FC),同时会在控制台打印 `E` 级别的日志,还会造成 Hook 进程“死掉”。 +!> `IllegalStateException` App is dead, You cannot call to appContext + +异常原因 + +使用 `ModuleApplication` 时调用了 `appContext` 功能但是 APP 可能已经被销毁或没有正确启动。 + +> 示例如下 + +```kotlin +// 调用了此变量但是 APP 可能已被销毁或没有正确启动 +ModuleApplication.appContext +``` + +解决方案 + +这种情况基本不存在,由于 `appContext` 是在 `onCreate` 中被赋值的,除非遇到多进程并发启动或 APP 没有启动完成前被反射调用了父类的 `onCreate` 方法。 + !> `IllegalStateException` YukiHookModulePrefs not allowed in Custom Hook API 异常原因 diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md index bae8f48a..9001a241 100644 --- a/docs/guide/quick-start.md +++ b/docs/guide/quick-start.md @@ -104,6 +104,10 @@ class MainHook : YukiHookXposedInitProxy { } ``` +你还可以将你的模块 APP 的 `Application` 继承于 `ModuleApplication` 以实现更多功能。 + +详情请参考 [ModuleApplication](api/document?id=moduleapplication-class)。 + 然后,你就可以开始编写 Hook 代码了。 有关作为 Xposed 模块使用的相关配置详细内容,你可以 [点击这里](config/xposed-using) 继续阅读。 diff --git a/yukihookapi-ksp-xposed/src/api/kotlin/com/highcapable/yukihookapi_ksp_xposed/YukiHookXposedProcessor.kt b/yukihookapi-ksp-xposed/src/api/kotlin/com/highcapable/yukihookapi_ksp_xposed/YukiHookXposedProcessor.kt index a955135c..d5842c61 100644 --- a/yukihookapi-ksp-xposed/src/api/kotlin/com/highcapable/yukihookapi_ksp_xposed/YukiHookXposedProcessor.kt +++ b/yukihookapi-ksp-xposed/src/api/kotlin/com/highcapable/yukihookapi_ksp_xposed/YukiHookXposedProcessor.kt @@ -211,9 +211,49 @@ class YukiHookXposedProcessor : SymbolProcessorProvider { packageName.split(".hook")[0] else error(msg = "Cannot identify your App's package name, please manually configure the package name") } + val injectPackageName = "com.highcapable.yukihookapi.hook.xposed.application.inject" + fun commentContent(name: String) = ("/**\n" + + " * $name Inject Class\n" + + " *\n" + + " * Compiled from YukiHookXposedProcessor\n" + + " *\n" + + " * HookEntryClass: [$className]\n" + + " *\n" + + " * Generate Date: ${SimpleDateFormat.getDateTimeInstance().format(Date())}\n" + + " *\n" + + " * Powered by YukiHookAPI (C) HighCapable 2022\n" + + " *\n" + + " * Project Address: [YukiHookAPI](https://github.com/fankes/YukiHookAPI)\n" + + " */\n") codeGenerator.createNewFile( - Dependencies.ALL_FILES, - packageName, + dependencies = Dependencies.ALL_FILES, + packageName = injectPackageName, + fileName = "ModuleApplication_Injector" + ).apply { + /** 插入 ModuleApplication_Injector 代码 */ + write( + ("@file:Suppress(\"ClassName\")\n" + + "\n" + + "package $injectPackageName\n" + + "\n" + + "import $packageName.$className\n" + + "\n" + + commentContent(name = "ModuleApplication") + + "object ModuleApplication_Injector {\n" + + "\n" + + " @JvmStatic\n" + + " fun callApiInit() = try {\n" + + " $className().onInit()\n" + + " } catch (_: Throwable) {\n" + + " }\n" + + "}").toByteArray() + ) + flush() + close() + } + codeGenerator.createNewFile( + dependencies = Dependencies.ALL_FILES, + packageName = packageName, fileName = "$className$xposedClassShortName" ).apply { /** 插入 xposed_init 代码 */ @@ -232,19 +272,7 @@ class YukiHookXposedProcessor : SymbolProcessorProvider { "import com.highcapable.yukihookapi.annotation.YukiGenerateApi\n" + "import $packageName.$className\n" + "\n" + - "/**\n" + - " * XposedInit Inject Class\n" + - " *\n" + - " * Compiled from YukiHookXposedProcessor\n" + - " *\n" + - " * HookEntryClass: [$className]\n" + - " *\n" + - " * Generate Date: ${SimpleDateFormat.getDateTimeInstance().format(Date())}\n" + - " *\n" + - " * Powered by YukiHookAPI (C) HighCapable 2022\n" + - " *\n" + - " * Project Address: https://github.com/fankes/YukiHookAPI\n" + - " */\n" + + commentContent(name = "XposedInit") + "@Keep\n" + "@YukiGenerateApi\n" + "class $className$xposedClassShortName : IXposedHookLoadPackage {\n" + diff --git a/yukihookapi/build.gradle b/yukihookapi/build.gradle index a3b6dbe1..436618bb 100644 --- a/yukihookapi/build.gradle +++ b/yukihookapi/build.gradle @@ -40,7 +40,8 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { dependencies { // Used 82 API Version compileOnly 'de.robv.android.xposed:api:82' - compileOnly fileTree(include: ['android-stub.jar'], dir: 'libs') + compileOnly fileTree(include: ['android-stub.jar', 'module-injector.jar'], dir: 'libs') + implementation fileTree(include: ['free-reflection.jar'], dir: 'libs') implementation 'androidx.annotation:annotation:1.3.0' } diff --git a/yukihookapi/libs/free-reflection.jar b/yukihookapi/libs/free-reflection.jar new file mode 100644 index 00000000..e21de47a Binary files /dev/null and b/yukihookapi/libs/free-reflection.jar differ diff --git a/yukihookapi/libs/module-injector.jar b/yukihookapi/libs/module-injector.jar new file mode 100644 index 00000000..eafaf3d1 Binary files /dev/null and b/yukihookapi/libs/module-injector.jar differ 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 3484a643..5cd0dcc1 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 @@ -29,6 +29,7 @@ package com.highcapable.yukihookapi.hook.factory +import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.hook.bean.CurrentClass import com.highcapable.yukihookapi.hook.bean.HookClass import com.highcapable.yukihookapi.hook.core.finder.ConstructorFinder @@ -36,6 +37,7 @@ 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 de.robv.android.xposed.XposedHelpers import java.lang.reflect.Constructor import java.lang.reflect.Field import java.lang.reflect.Member @@ -71,8 +73,16 @@ val String.hasClass get() = hasClass(loader = null) 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) } + when { + YukiHookAPI.hasXposedBridge -> + runCatching { XposedHelpers.findClassIfExists(name, loader) }.getOrNull() + ?: when (loader) { + null -> Class.forName(name) + else -> loader.loadClass(name) + } + 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/xposed/application/ModuleApplication.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/application/ModuleApplication.kt new file mode 100644 index 00000000..6859e5b7 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/application/ModuleApplication.kt @@ -0,0 +1,82 @@ +/* + * 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/4/15. + */ +package com.highcapable.yukihookapi.hook.xposed.application + +import android.app.Application +import android.content.Context +import com.highcapable.yukihookapi.YukiHookAPI +import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication.Companion.appContext +import com.highcapable.yukihookapi.hook.xposed.application.inject.ModuleApplication_Injector +import com.highcapable.yukihookapi.hook.xposed.proxy.YukiHookXposedInitProxy +import me.weishu.reflection.Reflection + +/** + * 这是对使用 [YukiHookAPI] Xposed 模块实现中的一个扩展功能 + * + * 在你的 Xposed 模块的 [Application] 中继承此类 + * + * 或在 AndroidManifest.xml 的 application 标签中指定此类 + * + * 目前可实现功能如下 + * + * - 全局共享模块中静态的 [appContext] + * + * - 在模块与宿主中装载 [YukiHookAPI.Configs] 以确保 [YukiHookAPI.Configs.debugTag] 不需要重复定义 + * + * - 在模块中使用系统隐藏 API - 核心技术引用了开源项目 [FreeReflection](https://github.com/tiann/FreeReflection) + * + * 详情请参考 [ModuleApplication](https://fankes.github.io/YukiHookAPI/#/api/document?id=moduleapplication-class) + */ +open class ModuleApplication : Application() { + + companion object { + + /** 全局静态 [Application] 实例 */ + private var currentContext: ModuleApplication? = null + + /** + * 全局静态 [Application] 实例 + * @throws IllegalStateException 如果 [Application] 没有正确装载完成 + */ + val appContext get() = currentContext ?: error("App is dead, You cannot call to appContext") + } + + override fun attachBaseContext(base: Context?) { + super.attachBaseContext(base) + Reflection.unseal(base) + } + + override fun onCreate() { + super.onCreate() + currentContext = this + callApiInit() + } + + /** 调用入口类的 [YukiHookXposedInitProxy.onInit] 方法 */ + private fun callApiInit() = runCatching { ModuleApplication_Injector.callApiInit() } +} \ No newline at end of file