mirror of
https://github.com/HighCapable/YukiHookAPI.git
synced 2025-09-06 02:35:40 +08:00
Added YukiHookDataChannel function and PackageParam.onAppLifecycle function and refactor some code
This commit is contained in:
@@ -44,7 +44,10 @@ import com.highcapable.yukihookapi.hook.param.PackageParam
|
|||||||
import com.highcapable.yukihookapi.hook.param.type.HookEntryType
|
import com.highcapable.yukihookapi.hook.param.type.HookEntryType
|
||||||
import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper
|
import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper
|
||||||
import com.highcapable.yukihookapi.hook.store.MemberCacheStore
|
import com.highcapable.yukihookapi.hook.store.MemberCacheStore
|
||||||
|
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
|
||||||
|
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
|
||||||
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge
|
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.prefs.YukiHookModulePrefs
|
||||||
import de.robv.android.xposed.XposedBridge
|
import de.robv.android.xposed.XposedBridge
|
||||||
import java.lang.reflect.Constructor
|
import java.lang.reflect.Constructor
|
||||||
@@ -151,6 +154,24 @@ object YukiHookAPI {
|
|||||||
*/
|
*/
|
||||||
var isEnableModuleAppResourcesCache = true
|
var isEnableModuleAppResourcesCache = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用 Hook Xposed 模块激活等状态功能
|
||||||
|
*
|
||||||
|
* - 为原生支持 Xposed 模块激活状态检测 - 此功能默认启用
|
||||||
|
*
|
||||||
|
* ❗关闭后你将不能再使用 [YukiHookModuleStatus] 中的功能
|
||||||
|
*/
|
||||||
|
var isEnableHookModuleStatus = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用当前 Xposed 模块与宿主交互的 [YukiHookDataChannel] 功能
|
||||||
|
*
|
||||||
|
* 请确保 Xposed 模块的 [Application] 继承于 [ModuleApplication] 才能有效
|
||||||
|
*
|
||||||
|
* - 此功能默认启用 - 关闭后将不会在功能初始化的时候装载 [YukiHookDataChannel]
|
||||||
|
*/
|
||||||
|
var isEnableDataChannel = true
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否启用 [Member] 缓存功能
|
* 是否启用 [Member] 缓存功能
|
||||||
*
|
*
|
||||||
|
@@ -38,9 +38,9 @@ import com.highcapable.yukihookapi.YukiHookAPI
|
|||||||
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
|
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
|
||||||
import com.highcapable.yukihookapi.hook.param.PackageParam
|
import com.highcapable.yukihookapi.hook.param.PackageParam
|
||||||
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
|
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
|
||||||
|
import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
|
||||||
import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookModulePrefs
|
import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookModulePrefs
|
||||||
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
|
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
|
||||||
import com.highcapable.yukihookapi.hook.xposed.proxy.YukiHookXposedInitProxy
|
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileReader
|
import java.io.FileReader
|
||||||
@@ -64,15 +64,6 @@ fun IYukiHookXposedInit.encase(initiate: PackageParam.() -> Unit) = YukiHookAPI.
|
|||||||
*/
|
*/
|
||||||
fun IYukiHookXposedInit.encase(vararg hooker: YukiBaseHooker) = YukiHookAPI.encase(hooker = hooker)
|
fun IYukiHookXposedInit.encase(vararg hooker: YukiBaseHooker) = YukiHookAPI.encase(hooker = hooker)
|
||||||
|
|
||||||
@Deprecated("请将接口转移到 IYukiHookXposedInit")
|
|
||||||
inline fun YukiHookXposedInitProxy.configs(initiate: YukiHookAPI.Configs.() -> Unit) = YukiHookAPI.configs(initiate)
|
|
||||||
|
|
||||||
@Deprecated("请将接口转移到 IYukiHookXposedInit")
|
|
||||||
fun YukiHookXposedInitProxy.encase(initiate: PackageParam.() -> Unit) = YukiHookAPI.encase(initiate)
|
|
||||||
|
|
||||||
@Deprecated("请将接口转移到 IYukiHookXposedInit")
|
|
||||||
fun YukiHookXposedInitProxy.encase(vararg hooker: YukiBaseHooker) = YukiHookAPI.encase(hooker = hooker)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取模块的存取对象
|
* 获取模块的存取对象
|
||||||
* @return [YukiHookModulePrefs]
|
* @return [YukiHookModulePrefs]
|
||||||
@@ -84,23 +75,28 @@ val Context.modulePrefs get() = YukiHookModulePrefs.instance(context = this)
|
|||||||
* @param name 自定义 Sp 存储名称
|
* @param name 自定义 Sp 存储名称
|
||||||
* @return [YukiHookModulePrefs]
|
* @return [YukiHookModulePrefs]
|
||||||
*/
|
*/
|
||||||
fun Context.modulePrefs(name: String) = YukiHookModulePrefs.instance(context = this).name(name)
|
fun Context.modulePrefs(name: String) = modulePrefs.name(name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取模块的数据通讯桥命名空间对象
|
||||||
|
* @param packageName 目标 Hook APP (宿主) 包名
|
||||||
|
* @return [YukiHookDataChannel.NameSpace]
|
||||||
|
*/
|
||||||
|
fun Context.dataChannel(packageName: String) = YukiHookDataChannel.instance().nameSpace(context = this, packageName)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前进程名称
|
* 获取当前进程名称
|
||||||
* @return [String]
|
* @return [String]
|
||||||
*/
|
*/
|
||||||
val Context.processName
|
val Context.processName
|
||||||
get() = try {
|
get() = runCatching {
|
||||||
BufferedReader(FileReader(File("/proc/${Process.myPid()}/cmdline"))).let { buff ->
|
BufferedReader(FileReader(File("/proc/${Process.myPid()}/cmdline"))).let { buff ->
|
||||||
buff.readLine().trim { it <= ' ' }.let {
|
buff.readLine().trim { it <= ' ' }.let {
|
||||||
buff.close()
|
buff.close()
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_: Throwable) {
|
}.getOrNull() ?: packageName ?: ""
|
||||||
packageName ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断当前 Hook Framework 是否支持资源钩子(Resources Hook)
|
* 判断当前 Hook Framework 是否支持资源钩子(Resources Hook)
|
||||||
|
@@ -30,7 +30,9 @@
|
|||||||
package com.highcapable.yukihookapi.hook.param
|
package com.highcapable.yukihookapi.hook.param
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import com.highcapable.yukihookapi.YukiHookAPI
|
import com.highcapable.yukihookapi.YukiHookAPI
|
||||||
import com.highcapable.yukihookapi.hook.bean.HookClass
|
import com.highcapable.yukihookapi.hook.bean.HookClass
|
||||||
@@ -47,6 +49,7 @@ import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper
|
|||||||
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge
|
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge
|
||||||
import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiModuleResources
|
import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiModuleResources
|
||||||
import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiResources
|
import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiResources
|
||||||
|
import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
|
||||||
import com.highcapable.yukihookapi.hook.xposed.helper.YukiHookAppHelper
|
import com.highcapable.yukihookapi.hook.xposed.helper.YukiHookAppHelper
|
||||||
import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookModulePrefs
|
import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookModulePrefs
|
||||||
|
|
||||||
@@ -96,11 +99,11 @@ open class PackageParam internal constructor(@PublishedApi internal var wrapper:
|
|||||||
/**
|
/**
|
||||||
* 获取当前 Hook APP 的 [Application] 实例
|
* 获取当前 Hook APP 的 [Application] 实例
|
||||||
*
|
*
|
||||||
* - ❗首次装载可能是空的 - 请延迟一段时间再获取
|
* - ❗首次装载可能是空的 - 请延迟一段时间再获取或通过设置 [onAppLifecycle] 监听来完成
|
||||||
* @return [Application]
|
* @return [Application]
|
||||||
* @throws IllegalStateException 如果 [Application] 是空的
|
* @throws IllegalStateException 如果 [Application] 是空的
|
||||||
*/
|
*/
|
||||||
val appContext get() = YukiHookAppHelper.currentApplication() ?: error("PackageParam got null appContext")
|
val appContext get() = YukiHookBridge.hostApplication ?: YukiHookAppHelper.currentApplication() ?: error("PackageParam got null appContext")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前 Hook APP 的 Resources
|
* 获取当前 Hook APP 的 Resources
|
||||||
@@ -162,12 +165,16 @@ open class PackageParam internal constructor(@PublishedApi internal var wrapper:
|
|||||||
fun prefs(name: String) = prefs.name(name)
|
fun prefs(name: String) = prefs.name(name)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得当前 Hook APP 的 [YukiResources] 对象
|
* 获得当前使用的数据通讯桥命名空间对象
|
||||||
*
|
*
|
||||||
* 请调用 [HookResources.hook] 方法开始 Hook
|
* - ❗作为 Hook API 装载时无法使用 - 会抛出异常
|
||||||
* @return [HookResources]
|
* @return [YukiHookDataChannel.NameSpace]
|
||||||
|
* @throws IllegalStateException 如果在 [HookEntryType.ZYGOTE] 装载
|
||||||
*/
|
*/
|
||||||
fun resources() = HookResources(wrapper?.appResources)
|
val dataChannel
|
||||||
|
get() = if (wrapper?.type != HookEntryType.ZYGOTE)
|
||||||
|
YukiHookDataChannel.instance().nameSpace(packageName = packageName)
|
||||||
|
else error("YukiHookDataChannel cannot used in zygote")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 赋值并克隆另一个 [PackageParam]
|
* 赋值并克隆另一个 [PackageParam]
|
||||||
@@ -177,9 +184,25 @@ open class PackageParam internal constructor(@PublishedApi internal var wrapper:
|
|||||||
this.wrapper = anotherParam.wrapper
|
this.wrapper = anotherParam.wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得当前 Hook APP 的 [YukiResources] 对象
|
||||||
|
*
|
||||||
|
* 请调用 [HookResources.hook] 方法开始 Hook
|
||||||
|
* @return [HookResources]
|
||||||
|
*/
|
||||||
|
fun resources() = HookResources(wrapper?.appResources)
|
||||||
|
|
||||||
/** 刷新当前 Xposed 模块自身 [Resources] */
|
/** 刷新当前 Xposed 模块自身 [Resources] */
|
||||||
fun refreshModuleAppResources() = YukiHookBridge.refreshModuleAppResources()
|
fun refreshModuleAppResources() = YukiHookBridge.refreshModuleAppResources()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听当前 Hook APP 生命周期装载事件
|
||||||
|
*
|
||||||
|
* - ❗在 [loadZygote] 中不会被装载 - 仅会在 [loadSystem]、[loadApp] 中装载
|
||||||
|
* @param initiate 方法体
|
||||||
|
*/
|
||||||
|
inline fun onAppLifecycle(initiate: AppLifecycle.() -> Unit) = AppLifecycle().apply(initiate).build()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 装载并 Hook 指定、全部包名的 APP
|
* 装载并 Hook 指定、全部包名的 APP
|
||||||
*
|
*
|
||||||
@@ -378,5 +401,67 @@ open class PackageParam internal constructor(@PublishedApi internal var wrapper:
|
|||||||
HookClass(name = name, throwable = throwable ?: e)
|
HookClass(name = name, throwable = throwable ?: e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前 Hook APP 的生命周期实例处理类
|
||||||
|
*
|
||||||
|
* - ❗请使用 [onAppLifecycle] 方法来获取 [AppLifecycle]
|
||||||
|
*/
|
||||||
|
inner class AppLifecycle @PublishedApi internal constructor() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听当前 Hook APP 装载 [Application.attachBaseContext]
|
||||||
|
* @param initiate 回调 - ([Context] baseContext,[Boolean] 是否已执行 super)
|
||||||
|
*/
|
||||||
|
fun attachBaseContext(initiate: (baseContext: Context, hasCalledSuper: Boolean) -> Unit) {
|
||||||
|
YukiHookBridge.AppLifecycleCallback.attachBaseContextCallback = initiate
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听当前 Hook APP 装载 [Application.onCreate]
|
||||||
|
* @param initiate 方法体
|
||||||
|
*/
|
||||||
|
fun onCreate(initiate: Application.() -> Unit) {
|
||||||
|
YukiHookBridge.AppLifecycleCallback.onCreateCallback = initiate
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听当前 Hook APP 装载 [Application.onTerminate]
|
||||||
|
* @param initiate 方法体
|
||||||
|
*/
|
||||||
|
fun onTerminate(initiate: Application.() -> Unit) {
|
||||||
|
YukiHookBridge.AppLifecycleCallback.onTerminateCallback = initiate
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听当前 Hook APP 装载 [Application.onLowMemory]
|
||||||
|
* @param initiate 方法体
|
||||||
|
*/
|
||||||
|
fun onLowMemory(initiate: Application.() -> Unit) {
|
||||||
|
YukiHookBridge.AppLifecycleCallback.onLowMemoryCallback = initiate
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听当前 Hook APP 装载 [Application.onTrimMemory]
|
||||||
|
* @param initiate 回调 - ([Application] 当前实例,[Int] 类型)
|
||||||
|
*/
|
||||||
|
fun onTrimMemory(initiate: (self: Application, level: Int) -> Unit) {
|
||||||
|
YukiHookBridge.AppLifecycleCallback.onTrimMemoryCallback = initiate
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听当前 Hook APP 装载 [Application.onConfigurationChanged]
|
||||||
|
* @param initiate 回调 - ([Application] 当前实例,[Configuration] 配置实例)
|
||||||
|
*/
|
||||||
|
fun onConfigurationChanged(initiate: (self: Application, config: Configuration) -> Unit) {
|
||||||
|
YukiHookBridge.AppLifecycleCallback.onConfigurationChangedCallback = initiate
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 设置创建生命周期监听回调 */
|
||||||
|
@PublishedApi
|
||||||
|
internal fun build() {
|
||||||
|
YukiHookBridge.AppLifecycleCallback.isCallbackSetUp = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString() = "PackageParam by $wrapper"
|
override fun toString() = "PackageParam by $wrapper"
|
||||||
}
|
}
|
@@ -32,6 +32,7 @@ import android.content.Context
|
|||||||
import com.highcapable.yukihookapi.YukiHookAPI
|
import com.highcapable.yukihookapi.YukiHookAPI
|
||||||
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication.Companion.appContext
|
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.application.inject.ModuleApplication_Injector
|
||||||
|
import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
|
||||||
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
|
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
|
||||||
import me.weishu.reflection.Reflection
|
import me.weishu.reflection.Reflection
|
||||||
|
|
||||||
@@ -48,6 +49,8 @@ import me.weishu.reflection.Reflection
|
|||||||
*
|
*
|
||||||
* - 在模块与宿主中装载 [YukiHookAPI.Configs] 以确保 [YukiHookAPI.Configs.debugTag] 不需要重复定义
|
* - 在模块与宿主中装载 [YukiHookAPI.Configs] 以确保 [YukiHookAPI.Configs.debugTag] 不需要重复定义
|
||||||
*
|
*
|
||||||
|
* - 在模块与宿主中使用 [YukiHookDataChannel] 进行通讯
|
||||||
|
*
|
||||||
* - 在模块中使用系统隐藏 API - 核心技术引用了开源项目 [FreeReflection](https://github.com/tiann/FreeReflection)
|
* - 在模块中使用系统隐藏 API - 核心技术引用了开源项目 [FreeReflection](https://github.com/tiann/FreeReflection)
|
||||||
*
|
*
|
||||||
* 详情请参考 [ModuleApplication](https://fankes.github.io/YukiHookAPI/#/api/document?id=moduleapplication-class)
|
* 详情请参考 [ModuleApplication](https://fankes.github.io/YukiHookAPI/#/api/document?id=moduleapplication-class)
|
||||||
@@ -76,6 +79,7 @@ open class ModuleApplication : Application() {
|
|||||||
super.onCreate()
|
super.onCreate()
|
||||||
currentContext = this
|
currentContext = this
|
||||||
callApiInit()
|
callApiInit()
|
||||||
|
YukiHookDataChannel.instance().register(context = this)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 调用入口类的 [IYukiHookXposedInit.onInit] 方法 */
|
/** 调用入口类的 [IYukiHookXposedInit.onInit] 方法 */
|
||||||
|
@@ -29,7 +29,10 @@
|
|||||||
|
|
||||||
package com.highcapable.yukihookapi.hook.xposed.bridge
|
package com.highcapable.yukihookapi.hook.xposed.bridge
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import com.highcapable.yukihookapi.YukiHookAPI
|
import com.highcapable.yukihookapi.YukiHookAPI
|
||||||
import com.highcapable.yukihookapi.annotation.YukiGenerateApi
|
import com.highcapable.yukihookapi.annotation.YukiGenerateApi
|
||||||
@@ -38,10 +41,16 @@ import com.highcapable.yukihookapi.hook.param.PackageParam
|
|||||||
import com.highcapable.yukihookapi.hook.param.type.HookEntryType
|
import com.highcapable.yukihookapi.hook.param.type.HookEntryType
|
||||||
import com.highcapable.yukihookapi.hook.param.wrapper.HookParamWrapper
|
import com.highcapable.yukihookapi.hook.param.wrapper.HookParamWrapper
|
||||||
import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper
|
import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper
|
||||||
|
import com.highcapable.yukihookapi.hook.type.android.ApplicationClass
|
||||||
|
import com.highcapable.yukihookapi.hook.type.android.ConfigurationClass
|
||||||
|
import com.highcapable.yukihookapi.hook.type.android.ContextClass
|
||||||
|
import com.highcapable.yukihookapi.hook.type.android.InstrumentationClass
|
||||||
|
import com.highcapable.yukihookapi.hook.type.java.IntType
|
||||||
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
|
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
|
||||||
import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiModuleResources
|
import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiModuleResources
|
||||||
import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiResources
|
import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiResources
|
||||||
import com.highcapable.yukihookapi.hook.xposed.bridge.inject.YukiHookBridge_Injector
|
import com.highcapable.yukihookapi.hook.xposed.bridge.inject.YukiHookBridge_Injector
|
||||||
|
import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
|
||||||
import de.robv.android.xposed.*
|
import de.robv.android.xposed.*
|
||||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources
|
import de.robv.android.xposed.callbacks.XC_InitPackageResources
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
@@ -66,6 +75,9 @@ object YukiHookBridge {
|
|||||||
/** Xposed 是否装载完成 */
|
/** Xposed 是否装载完成 */
|
||||||
private var isXposedInitialized = false
|
private var isXposedInitialized = false
|
||||||
|
|
||||||
|
/** [YukiHookDataChannel] 是否已经注册 */
|
||||||
|
private var isDataChannelRegister = false
|
||||||
|
|
||||||
/** 已在 [PackageParam] 中被装载的 APP 包名 */
|
/** 已在 [PackageParam] 中被装载的 APP 包名 */
|
||||||
private val loadedPackageNames = HashSet<String>()
|
private val loadedPackageNames = HashSet<String>()
|
||||||
|
|
||||||
@@ -75,6 +87,13 @@ object YukiHookBridge {
|
|||||||
/** 当前 [PackageParam] 方法体回调 */
|
/** 当前 [PackageParam] 方法体回调 */
|
||||||
internal var packageParamCallback: (PackageParam.() -> Unit)? = null
|
internal var packageParamCallback: (PackageParam.() -> Unit)? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前 Hook APP (宿主) 的全局生命周期 [Application]
|
||||||
|
*
|
||||||
|
* 需要 [YukiHookAPI.Configs.isEnableDataChannel] 或 [AppLifecycleCallback.isCallbackSetUp] 才会生效
|
||||||
|
*/
|
||||||
|
internal var hostApplication: Application? = null
|
||||||
|
|
||||||
/** 当前 Xposed 模块自身 APK 路径 */
|
/** 当前 Xposed 模块自身 APK 路径 */
|
||||||
internal var moduleAppFilePath = ""
|
internal var moduleAppFilePath = ""
|
||||||
|
|
||||||
@@ -194,6 +213,67 @@ object YukiHookBridge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注入当前 Hook APP (宿主) 全局生命周期
|
||||||
|
* @param packageName 包名
|
||||||
|
*/
|
||||||
|
private fun registerToAppLifecycle(packageName: String) {
|
||||||
|
/** Hook [Application] 装载方法 */
|
||||||
|
runCatching {
|
||||||
|
if (AppLifecycleCallback.isCallbackSetUp) {
|
||||||
|
Hooker.hookMethod(Hooker.findMethod(ApplicationClass, name = "attach", ContextClass), object : Hooker.YukiMemberHook() {
|
||||||
|
override fun beforeHookedMember(wrapper: HookParamWrapper) {
|
||||||
|
(wrapper.args?.get(0) as? Context?)?.also { AppLifecycleCallback.attachBaseContextCallback?.invoke(it, false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun afterHookedMember(wrapper: HookParamWrapper) {
|
||||||
|
(wrapper.args?.get(0) as? Context?)?.also { AppLifecycleCallback.attachBaseContextCallback?.invoke(it, true) }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Hooker.hookMethod(Hooker.findMethod(ApplicationClass, name = "onTerminate"), object : Hooker.YukiMemberHook() {
|
||||||
|
override fun afterHookedMember(wrapper: HookParamWrapper) {
|
||||||
|
(wrapper.instance as? Application?)?.also { AppLifecycleCallback.onTerminateCallback?.invoke(it) }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Hooker.hookMethod(Hooker.findMethod(ApplicationClass, name = "onLowMemory"), object : Hooker.YukiMemberHook() {
|
||||||
|
override fun afterHookedMember(wrapper: HookParamWrapper) {
|
||||||
|
(wrapper.instance as? Application?)?.also { AppLifecycleCallback.onLowMemoryCallback?.invoke(it) }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Hooker.hookMethod(Hooker.findMethod(ApplicationClass, name = "onTrimMemory", IntType), object : Hooker.YukiMemberHook() {
|
||||||
|
override fun afterHookedMember(wrapper: HookParamWrapper) {
|
||||||
|
val self = wrapper.instance as? Application? ?: return
|
||||||
|
val type = wrapper.args?.get(0) as? Int ?: return
|
||||||
|
AppLifecycleCallback.onTrimMemoryCallback?.invoke(self, type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Hooker.hookMethod(
|
||||||
|
Hooker.findMethod(ApplicationClass, name = "onConfigurationChanged", ConfigurationClass),
|
||||||
|
object : Hooker.YukiMemberHook() {
|
||||||
|
override fun afterHookedMember(wrapper: HookParamWrapper) {
|
||||||
|
val self = wrapper.instance as? Application? ?: return
|
||||||
|
val config = wrapper.args?.get(0) as? Configuration? ?: return
|
||||||
|
AppLifecycleCallback.onConfigurationChangedCallback?.invoke(self, config)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (YukiHookAPI.Configs.isEnableDataChannel || AppLifecycleCallback.isCallbackSetUp)
|
||||||
|
Hooker.hookMethod(
|
||||||
|
Hooker.findMethod(InstrumentationClass, name = "callApplicationOnCreate", ApplicationClass),
|
||||||
|
object : Hooker.YukiMemberHook() {
|
||||||
|
override fun afterHookedMember(wrapper: HookParamWrapper) {
|
||||||
|
(wrapper.args?.get(0) as? Application)?.also {
|
||||||
|
hostApplication = it
|
||||||
|
AppLifecycleCallback.onCreateCallback?.invoke(it)
|
||||||
|
if (isDataChannelRegister) return
|
||||||
|
isDataChannelRegister = true
|
||||||
|
runCatching { YukiHookDataChannel.instance().register(it, packageName) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 刷新当前 Xposed 模块自身 [Resources] */
|
/** 刷新当前 Xposed 模块自身 [Resources] */
|
||||||
internal fun refreshModuleAppResources() {
|
internal fun refreshModuleAppResources() {
|
||||||
dynamicModuleAppResources?.let { moduleAppResources = it }
|
dynamicModuleAppResources?.let { moduleAppResources = it }
|
||||||
@@ -208,26 +288,27 @@ object YukiHookBridge {
|
|||||||
*/
|
*/
|
||||||
@YukiGenerateApi
|
@YukiGenerateApi
|
||||||
fun hookModuleAppStatus(classLoader: ClassLoader?, isHookResourcesStatus: Boolean = false) {
|
fun hookModuleAppStatus(classLoader: ClassLoader?, isHookResourcesStatus: Boolean = false) {
|
||||||
Hooker.findClass(classLoader, YukiHookModuleStatus::class.java).also { statusClass ->
|
if (YukiHookAPI.Configs.isEnableHookModuleStatus)
|
||||||
if (isHookResourcesStatus.not()) {
|
Hooker.findClass(classLoader, YukiHookModuleStatus::class.java).also { statusClass ->
|
||||||
Hooker.hookMethod(Hooker.findMethod(statusClass, YukiHookModuleStatus.IS_ACTIVE_METHOD_NAME),
|
if (isHookResourcesStatus.not()) {
|
||||||
object : Hooker.YukiMemberReplacement() {
|
Hooker.hookMethod(Hooker.findMethod(statusClass, YukiHookModuleStatus.IS_ACTIVE_METHOD_NAME),
|
||||||
override fun replaceHookedMember(wrapper: HookParamWrapper) = true
|
object : Hooker.YukiMemberReplacement() {
|
||||||
})
|
override fun replaceHookedMember(wrapper: HookParamWrapper) = true
|
||||||
Hooker.hookMethod(Hooker.findMethod(statusClass, YukiHookModuleStatus.GET_XPOSED_TAG_METHOD_NAME),
|
})
|
||||||
object : Hooker.YukiMemberReplacement() {
|
Hooker.hookMethod(Hooker.findMethod(statusClass, YukiHookModuleStatus.GET_XPOSED_TAG_METHOD_NAME),
|
||||||
override fun replaceHookedMember(wrapper: HookParamWrapper) = executorName
|
object : Hooker.YukiMemberReplacement() {
|
||||||
})
|
override fun replaceHookedMember(wrapper: HookParamWrapper) = executorName
|
||||||
Hooker.hookMethod(Hooker.findMethod(statusClass, YukiHookModuleStatus.GET_XPOSED_VERSION_METHOD_NAME),
|
})
|
||||||
object : Hooker.YukiMemberReplacement() {
|
Hooker.hookMethod(Hooker.findMethod(statusClass, YukiHookModuleStatus.GET_XPOSED_VERSION_METHOD_NAME),
|
||||||
override fun replaceHookedMember(wrapper: HookParamWrapper) = executorVersion
|
object : Hooker.YukiMemberReplacement() {
|
||||||
})
|
override fun replaceHookedMember(wrapper: HookParamWrapper) = executorVersion
|
||||||
} else
|
})
|
||||||
Hooker.hookMethod(Hooker.findMethod(statusClass, YukiHookModuleStatus.HAS_RESOURCES_HOOK_METHOD_NAME),
|
} else
|
||||||
object : Hooker.YukiMemberReplacement() {
|
Hooker.hookMethod(Hooker.findMethod(statusClass, YukiHookModuleStatus.HAS_RESOURCES_HOOK_METHOD_NAME),
|
||||||
override fun replaceHookedMember(wrapper: HookParamWrapper) = true
|
object : Hooker.YukiMemberReplacement() {
|
||||||
})
|
override fun replaceHookedMember(wrapper: HookParamWrapper) = true
|
||||||
}
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -285,7 +366,37 @@ object YukiHookBridge {
|
|||||||
assignWrapper(HookEntryType.RESOURCES, resparam.packageName, appResources = YukiResources.createFromXResources(resparam.res))
|
assignWrapper(HookEntryType.RESOURCES, resparam.packageName, appResources = YukiResources.createFromXResources(resparam.res))
|
||||||
else null
|
else null
|
||||||
else -> null
|
else -> null
|
||||||
}?.also { YukiHookAPI.onXposedLoaded(it) }
|
}?.also {
|
||||||
|
YukiHookAPI.onXposedLoaded(it)
|
||||||
|
if (it.type == HookEntryType.PACKAGE) registerToAppLifecycle(it.packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前 Hook APP (宿主) 的生命周期回调处理类
|
||||||
|
*/
|
||||||
|
internal object AppLifecycleCallback {
|
||||||
|
|
||||||
|
/** 是否已设置回调 */
|
||||||
|
internal var isCallbackSetUp = false
|
||||||
|
|
||||||
|
/** [Application.attachBaseContext] 回调 */
|
||||||
|
internal var attachBaseContextCallback: ((Context, Boolean) -> Unit)? = null
|
||||||
|
|
||||||
|
/** [Application.onCreate] 回调 */
|
||||||
|
internal var onCreateCallback: (Application.() -> Unit)? = null
|
||||||
|
|
||||||
|
/** [Application.onTerminate] 回调 */
|
||||||
|
internal var onTerminateCallback: (Application.() -> Unit)? = null
|
||||||
|
|
||||||
|
/** [Application.onLowMemory] 回调 */
|
||||||
|
internal var onLowMemoryCallback: (Application.() -> Unit)? = null
|
||||||
|
|
||||||
|
/** [Application.onTrimMemory] 回调 */
|
||||||
|
internal var onTrimMemoryCallback: ((Application, Int) -> Unit)? = null
|
||||||
|
|
||||||
|
/** [Application.onConfigurationChanged] 回调 */
|
||||||
|
internal var onConfigurationChangedCallback: ((Application, Configuration) -> Unit)? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,285 @@
|
|||||||
|
/*
|
||||||
|
* 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/5/16.
|
||||||
|
*/
|
||||||
|
@file:Suppress("StaticFieldLeak", "UNCHECKED_CAST", "unused", "MemberVisibilityCanBePrivate")
|
||||||
|
|
||||||
|
package com.highcapable.yukihookapi.hook.xposed.channel
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.highcapable.yukihookapi.YukiHookAPI
|
||||||
|
import com.highcapable.yukihookapi.hook.utils.putIfAbsentCompat
|
||||||
|
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
|
||||||
|
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge
|
||||||
|
import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实现 Xposed 模块的数据通讯桥
|
||||||
|
*
|
||||||
|
* 通过模块与宿主相互注册 [BroadcastReceiver] 来实现数据的交互
|
||||||
|
*
|
||||||
|
* 模块需要将 [Application] 继承于 [ModuleApplication] 来实现此功能
|
||||||
|
*
|
||||||
|
* - ❗模块与宿主需要保持存活状态 - 否则无法建立通讯
|
||||||
|
*
|
||||||
|
* - 详情请参考 [API 文档 - YukiHookDataChannel](https://fankes.github.io/YukiHookAPI/#/api/document?id=yukihookdatachannel-class)
|
||||||
|
*/
|
||||||
|
class YukiHookDataChannel private constructor() {
|
||||||
|
|
||||||
|
internal companion object {
|
||||||
|
|
||||||
|
/** 是否为 Xposed 环境 */
|
||||||
|
private val isXposedEnvironment = YukiHookBridge.hasXposedBridge
|
||||||
|
|
||||||
|
/** 模块构建版本号获取标签 */
|
||||||
|
private const val GET_MODULE_GENERATED_VERSION = "module_generated_version_get"
|
||||||
|
|
||||||
|
/** 模块构建版本号结果标签 */
|
||||||
|
private const val RESULT_MODULE_GENERATED_VERSION = "module_generated_version_result"
|
||||||
|
|
||||||
|
/** 仅监听结果键值 */
|
||||||
|
private const val VALUE_WAIT_FOR_LISTENER = "wait_for_listener_value"
|
||||||
|
|
||||||
|
/** 当前 [YukiHookDataChannel] 单例 */
|
||||||
|
private var instance: YukiHookDataChannel? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 [YukiHookDataChannel] 单例
|
||||||
|
* @return [YukiHookDataChannel]
|
||||||
|
*/
|
||||||
|
internal fun instance() = instance ?: YukiHookDataChannel().apply { instance = this }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 注册广播回调数组 */
|
||||||
|
private var receiverCallbacks = HashMap<String, ((String, Intent) -> Unit)>()
|
||||||
|
|
||||||
|
/** 当前注册广播的 [Context] */
|
||||||
|
private var receiverContext: Context? = null
|
||||||
|
|
||||||
|
/** 广播接收器 */
|
||||||
|
private val handlerReceiver by lazy {
|
||||||
|
object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
if (intent == null) return
|
||||||
|
intent.action?.also { action -> receiverCallbacks.takeIf { it.isNotEmpty() }?.forEach { (_, it) -> it(action, intent) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 检查 API 装载状态 */
|
||||||
|
private fun checkApi() {
|
||||||
|
if (YukiHookAPI.isLoadedFromBaseContext) error("YukiHookDataChannel not allowed in Custom Hook API")
|
||||||
|
if (YukiHookBridge.hasXposedBridge && YukiHookBridge.modulePackageName.isBlank())
|
||||||
|
error("Xposed modulePackageName load failed, please reset and rebuild it")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取宿主广播 Action 名称
|
||||||
|
* @param packageName 包名
|
||||||
|
* @return [String]
|
||||||
|
*/
|
||||||
|
private fun hostActionName(packageName: String) = "yukihookapi.intent.action.HOST_DATA_CHANNEL_${packageName.trim().hashCode()}"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取模块广播 Action 名称
|
||||||
|
* @param context 实例 - 默认空
|
||||||
|
* @return [String]
|
||||||
|
*/
|
||||||
|
private fun moduleActionName(context: Context? = null) = "yukihookapi.intent.action.MODULE_DATA_CHANNEL_${
|
||||||
|
YukiHookBridge.modulePackageName.ifBlank { context?.packageName ?: "" }.trim().hashCode()
|
||||||
|
}"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册广播
|
||||||
|
* @param context 目标 Hook APP (宿主) 或模块全局上下文实例 - 为空停止注册
|
||||||
|
* @param packageName 包名 - 为空获取 [context] 的 [Context.getPackageName]
|
||||||
|
*/
|
||||||
|
internal fun register(context: Context?, packageName: String = context?.packageName ?: "") {
|
||||||
|
if (context == null) return
|
||||||
|
if (YukiHookAPI.Configs.isEnableDataChannel.not() || receiverContext != null) return
|
||||||
|
receiverContext = context
|
||||||
|
context.registerReceiver(
|
||||||
|
handlerReceiver, IntentFilter().apply {
|
||||||
|
addAction(if (isXposedEnvironment) hostActionName(packageName) else moduleActionName(context))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
/** 注册监听模块与宿主的版本是否匹配 */
|
||||||
|
nameSpace(context, packageName).wait<String>(GET_MODULE_GENERATED_VERSION) { fromPackageName ->
|
||||||
|
nameSpace(context, fromPackageName).put(RESULT_MODULE_GENERATED_VERSION, YukiHookBridge.moduleGeneratedVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取命名空间
|
||||||
|
* @param context 上下文实例
|
||||||
|
* @param packageName 目标 Hook APP (宿主) 的包名
|
||||||
|
* @return [NameSpace]
|
||||||
|
*/
|
||||||
|
internal fun nameSpace(context: Context? = null, packageName: String): NameSpace {
|
||||||
|
checkApi()
|
||||||
|
return NameSpace(context = context ?: receiverContext, packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [YukiHookDataChannel] 命名空间
|
||||||
|
*
|
||||||
|
* - ❗请使用 [nameSpace] 方法来获取 [NameSpace]
|
||||||
|
* @param context 上下文实例
|
||||||
|
* @param packageName 目标 Hook APP (宿主) 的包名
|
||||||
|
*/
|
||||||
|
inner class NameSpace internal constructor(private val context: Context?, private val packageName: String) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建一个调用空间
|
||||||
|
* @param initiate 方法体
|
||||||
|
* @return [NameSpace] 可继续向下监听
|
||||||
|
*/
|
||||||
|
inline fun with(initiate: NameSpace.() -> Unit) = apply(initiate)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送键值数据
|
||||||
|
* @param key 键值名称
|
||||||
|
* @param value 键值数据
|
||||||
|
*/
|
||||||
|
fun <T> put(key: String, value: T) = pushReceiver(ChannelData(key, value))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送键值数据
|
||||||
|
* @param data 键值实例
|
||||||
|
* @param value 键值数据 - 未指定为 [ChannelData.value]
|
||||||
|
*/
|
||||||
|
fun <T> put(data: ChannelData<T>, value: T? = data.value) = pushReceiver(ChannelData(data.key, value))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送键值数据
|
||||||
|
* @param data 键值实例
|
||||||
|
*/
|
||||||
|
fun put(vararg data: ChannelData<*>) = data.takeIf { it.isNotEmpty() }?.let { pushReceiver(*it) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅发送键值监听 - 使用默认值 [VALUE_WAIT_FOR_LISTENER] 发送键值数据
|
||||||
|
* @param key 键值名称
|
||||||
|
*/
|
||||||
|
fun put(key: String) = pushReceiver(ChannelData(key, VALUE_WAIT_FOR_LISTENER))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取键值数据
|
||||||
|
* @param key 键值名称
|
||||||
|
* @param value 默认值 - 不填则在值为空的时候不回调 [result]
|
||||||
|
* @param result 回调结果数据
|
||||||
|
*/
|
||||||
|
fun <T> wait(key: String, value: T? = null, result: (value: T) -> Unit) {
|
||||||
|
receiverCallbacks.putIfAbsentCompat(key) { action, intent ->
|
||||||
|
if (action == if (isXposedEnvironment) hostActionName(packageName) else moduleActionName(context))
|
||||||
|
(intent.extras?.get(key) as? T?).also { if (it != null || value != null) (it ?: value)?.let { e -> result(e) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取键值数据
|
||||||
|
* @param data 键值实例
|
||||||
|
* @param value 默认值 - 未指定为 [ChannelData.value]
|
||||||
|
* @param result 回调结果数据
|
||||||
|
*/
|
||||||
|
fun <T> wait(data: ChannelData<T>, value: T? = data.value, result: (value: T) -> Unit) {
|
||||||
|
receiverCallbacks.putIfAbsentCompat(data.key) { action, intent ->
|
||||||
|
if (action == if (isXposedEnvironment) hostActionName(packageName) else moduleActionName(context))
|
||||||
|
(intent.extras?.get(data.key) as? T?).also { if (it != null || value != null) (it ?: value)?.let { e -> result(e) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅获取监听结果 - 不获取键值数据
|
||||||
|
*
|
||||||
|
* - ❗仅限使用 [VALUE_WAIT_FOR_LISTENER] 发送的监听才能被接收
|
||||||
|
* @param key 键值名称
|
||||||
|
* @param result 回调结果
|
||||||
|
*/
|
||||||
|
fun wait(key: String, result: () -> Unit) {
|
||||||
|
receiverCallbacks.putIfAbsentCompat(key) { action, intent ->
|
||||||
|
if (action == if (isXposedEnvironment) hostActionName(packageName) else moduleActionName(context))
|
||||||
|
if (intent.getStringExtra(key) == VALUE_WAIT_FOR_LISTENER) result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取模块与宿主的版本是否匹配
|
||||||
|
*
|
||||||
|
* 通过此方法可原生判断 Xposed 模块更新后宿主并未重新装载造成两者不匹配的情况
|
||||||
|
* @param result 回调是否匹配
|
||||||
|
*/
|
||||||
|
fun checkingVersionEquals(result: (Boolean) -> Unit) {
|
||||||
|
wait<String>(RESULT_MODULE_GENERATED_VERSION) { result(it == YukiHookBridge.moduleGeneratedVersion) }
|
||||||
|
put(GET_MODULE_GENERATED_VERSION, packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送广播
|
||||||
|
* @param data 键值数据
|
||||||
|
*/
|
||||||
|
private fun pushReceiver(vararg data: ChannelData<*>) {
|
||||||
|
if (YukiHookAPI.Configs.isEnableDataChannel.not()) return
|
||||||
|
context?.sendBroadcast(Intent().apply {
|
||||||
|
action = if (isXposedEnvironment) moduleActionName() else hostActionName(packageName)
|
||||||
|
data.takeIf { it.isNotEmpty() }?.forEach {
|
||||||
|
when (it.value) {
|
||||||
|
null -> Unit
|
||||||
|
is Bundle -> putExtra(it.key, it.value as Bundle)
|
||||||
|
is Parcelable -> putExtra(it.key, it.value as Parcelable)
|
||||||
|
is Serializable -> putExtra(it.key, it.value as Serializable)
|
||||||
|
is Array<*> -> putExtra(it.key, it.value as Array<*>)
|
||||||
|
is Boolean -> putExtra(it.key, it.value as Boolean)
|
||||||
|
is BooleanArray -> putExtra(it.key, it.value as BooleanArray)
|
||||||
|
is Byte -> putExtra(it.key, it.value as Byte)
|
||||||
|
is ByteArray -> putExtra(it.key, it.value as ByteArray)
|
||||||
|
is Char -> putExtra(it.key, it.value as Char)
|
||||||
|
is CharArray -> putExtra(it.key, it.value as CharArray)
|
||||||
|
is CharSequence -> putExtra(it.key, it.value as CharSequence)
|
||||||
|
is Double -> putExtra(it.key, it.value as Double)
|
||||||
|
is DoubleArray -> putExtra(it.key, it.value as DoubleArray)
|
||||||
|
is Float -> putExtra(it.key, it.value as Float)
|
||||||
|
is FloatArray -> putExtra(it.key, it.value as FloatArray)
|
||||||
|
is Int -> putExtra(it.key, it.value as Int)
|
||||||
|
is IntArray -> putExtra(it.key, it.value as IntArray)
|
||||||
|
is Long -> putExtra(it.key, it.value as Long)
|
||||||
|
is LongArray -> putExtra(it.key, it.value as LongArray)
|
||||||
|
is Short -> putExtra(it.key, it.value as Short)
|
||||||
|
is ShortArray -> putExtra(it.key, it.value as ShortArray)
|
||||||
|
is String -> putExtra(it.key, it.value as String)
|
||||||
|
else -> error("Key-Value type ${it.value?.javaClass?.name} is not allowed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* 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/5/16.
|
||||||
|
*/
|
||||||
|
package com.highcapable.yukihookapi.hook.xposed.channel.data
|
||||||
|
|
||||||
|
import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据通讯桥键值构造类
|
||||||
|
*
|
||||||
|
* 这个类是对 [YukiHookDataChannel] 的一个扩展用法
|
||||||
|
*
|
||||||
|
* - 详情请参考 [API 文档 - ChannelData](https://fankes.github.io/YukiHookAPI/#/api/document?id=channeldata-class)
|
||||||
|
* @param key 键值
|
||||||
|
* @param value 默认值 - 可空
|
||||||
|
*/
|
||||||
|
data class ChannelData<T>(var key: String, var value: T? = null)
|
Reference in New Issue
Block a user