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