diff --git a/docs-source/src/en/api/special-features/host-inject.md b/docs-source/src/en/api/special-features/host-inject.md index 8421d1ba..14568684 100644 --- a/docs-source/src/en/api/special-features/host-inject.md +++ b/docs-source/src/en/api/special-features/host-inject.md @@ -146,34 +146,45 @@ Alternatively, if you write a `stub` for the Host App's class, you can register registerModuleAppActivities(TestActivity::class.java) ``` -After the registration is complete, extends the `Activity` in the Module App you need to use the Host App to start by `ModuleAppActivity` or `ModuleAppCompatActivity`. +After registration is completed, please implement the `ModuleActivity` interface using the `Activity` module in the host-started module. -These `Activity` now live seamlessly in the Host App without registration. +These `Activity` (ies) now live seamlessly in the host without registration. + +We recommend that you create `BaseActivity` as the base class for all modules `Activity`. > The following example ```kotlin -class HostTestActivity : ModuleAppActivity() { +abstract class BaseActivity : AppCompatActivity(), ModuleActivity { + + // Set up AppCompat Theme (if currently is [AppCompatActivity]) + override val moduleTheme get() = R.style.YourAppTheme + + override fun getClassLoader() = delegate.getClassLoader() override fun onCreate(savedInstanceState: Bundle?) { + delegate.onCreate(savedInstanceState) super.onCreate(savedInstanceState) - // Module App's Resources have been injected automatically - // You can directly use xml to load the layout - setContentView(R.layout.activity_main) + } + + override fun onConfigurationChanged(newConfig: Configuration) { + delegate.onConfigurationChanged(newConfig) + super.onConfigurationChanged(newConfig) + } + + override fun onRestoreInstanceState(savedInstanceState: Bundle) { + delegate.onRestoreInstanceState(savedInstanceState) + super.onRestoreInstanceState(savedInstanceState) } } ``` -If you need to extends `ModuleAppCompatActivity`, you need to set the AppCompat theme manually. +Then inherit the `Activity` you want to implement in `BaseActivity`. > The following example ```kotlin -class HostTestActivity : ModuleAppCompatActivity() { - - // The theme name here is for reference only - // Please fill in the theme name already in your Module App - override val moduleTheme get() = R.style.Theme_AppCompat +class HostTestActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -200,7 +211,7 @@ If you need to specify a delegated `Activity` to use another Host App's `Activit > The following example ```kotlin -class HostTestActivity : ModuleAppActivity() { +class HostTestActivity : BaseActivity() { // Specify an additional proxy Activity class name // Which must also exist in the Host App's AndroidManifest @@ -210,7 +221,7 @@ class HostTestActivity : ModuleAppActivity() { super.onCreate(savedInstanceState) // Module App's Resources have been injected automatically // You can directly use xml to load the layout - setContentView(R. layout. activity_main) + setContentView(R.layout.activity_main) } } ``` diff --git a/docs-source/src/en/config/move-to-api-1-3-x.md b/docs-source/src/en/config/move-to-api-1-3-x.md index b97aaea7..8ce16f41 100644 --- a/docs-source/src/en/config/move-to-api-1-3-x.md +++ b/docs-source/src/en/config/move-to-api-1-3-x.md @@ -85,4 +85,10 @@ val instance: Any Now, `YukiHookAPI` no longer limits duplicate Hooks to the same method, you can hook multiple times on the same method. `YukiHookAPI` also deprecated the `onAlreadyHooked` method of `hook { ... }`. -Now this method will be useless and will not be called back. If necessary, please manually handle the relevant logic of duplicate Hooks. \ No newline at end of file +Now this method will be useless and will not be called back. If necessary, please manually handle the relevant logic of duplicate Hooks. + +## Register Module App's Activity Behavior Change + +`YukiHookAPI` Starting with `1.3.0`, the way in which the module `Activity` behavior has changed. + +Please read [Register Module App's Activity](../api/special-features/host-inject#register-module-app-s-activity) for more information. \ No newline at end of file diff --git a/docs-source/src/zh-cn/api/special-features/host-inject.md b/docs-source/src/zh-cn/api/special-features/host-inject.md index 4c316810..a5874e8d 100644 --- a/docs-source/src/zh-cn/api/special-features/host-inject.md +++ b/docs-source/src/zh-cn/api/special-features/host-inject.md @@ -142,32 +142,45 @@ registerModuleAppActivities(proxy = "com.demo.test.activity.TestActivity") registerModuleAppActivities(TestActivity::class.java) ``` -注册完成后,请将你需要使用宿主启动的模块中的 `Activity` 继承于 `ModuleAppActivity` 或 `ModuleAppCompatActivity`。 +注册完成后,请将你需要使用宿主启动的模块中的 `Activity` 实现 `ModuleActivity` 接口。 这些 `Activity` 现在无需注册即可无缝存活于宿主中。 +我们推荐你创建 `BaseActivity` 作为所有模块 `Activity` 的基类。 + > 示例如下 ```kotlin -class HostTestActivity : ModuleAppActivity() { +abstract class BaseActivity : AppCompatActivity(), ModuleActivity { + + // 设置 AppCompat 主题 (如果当前是 [AppCompatActivity]) + override val moduleTheme get() = R.style.YourAppTheme + + override fun getClassLoader() = delegate.getClassLoader() override fun onCreate(savedInstanceState: Bundle?) { + delegate.onCreate(savedInstanceState) super.onCreate(savedInstanceState) - // 模块资源已被自动注入,可以直接使用 xml 装载布局 - setContentView(R.layout.activity_main) + } + + override fun onConfigurationChanged(newConfig: Configuration) { + delegate.onConfigurationChanged(newConfig) + super.onConfigurationChanged(newConfig) + } + + override fun onRestoreInstanceState(savedInstanceState: Bundle) { + delegate.onRestoreInstanceState(savedInstanceState) + super.onRestoreInstanceState(savedInstanceState) } } ``` -若你需要继承于 `ModuleAppCompatActivity`,你需要手动设置 AppCompat 主题。 +然后将需要实现的 `Activity` 继承于 `BaseActivity`。 > 示例如下 ```kotlin -class HostTestActivity : ModuleAppCompatActivity() { - - // 这里的主题名称仅供参考,请填写你模块中已有的主题名称 - override val moduleTheme get() = R.style.Theme_AppCompat +class HostTestActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -193,7 +206,7 @@ context.startActivity(context, HostTestActivity::class.java) > 示例如下 ```kotlin -class HostTestActivity : ModuleAppActivity() { +class HostTestActivity : BaseActivity() { // 指定一个另外的代理 Activity 类名,其也必须存在于宿主的 AndroidManifest 中 override val proxyClassName get() = "com.demo.test.activity.OtherActivity" diff --git a/docs-source/src/zh-cn/config/move-to-api-1-3-x.md b/docs-source/src/zh-cn/config/move-to-api-1-3-x.md index 9df39372..d4f5dea7 100644 --- a/docs-source/src/zh-cn/config/move-to-api-1-3-x.md +++ b/docs-source/src/zh-cn/config/move-to-api-1-3-x.md @@ -79,4 +79,10 @@ val instance: Any `YukiHookAPI` 从 `1.3.0` 版本开始弃用了重复 Hook 的限制,现在,`YukiHookAPI` 不再限制重复 Hook 同一个方法,你可以在同一个方法上多次 Hook。 -`YukiHookAPI` 同时弃用了 `hook { ... }` 的 `onAlreadyHooked` 方法,现在此方法将无作用且不会被回调,如有需要,请手动处理重复 Hook 的相关逻辑。 \ No newline at end of file +`YukiHookAPI` 同时弃用了 `hook { ... }` 的 `onAlreadyHooked` 方法,现在此方法将无作用且不会被回调,如有需要,请手动处理重复 Hook 的相关逻辑。 + +## 注册模块 Activity 行为变更 + +`YukiHookAPI` 从 `1.3.0` 版本开始,注册模块 `Activity` 行为的方式发生了变更。 + +请阅读 [注册模块 Activity](../api/special-features/host-inject#注册模块-activity) 以了解更多信息。 \ No newline at end of file diff --git a/gradle/sweet-dependency/sweet-dependency-config.yaml b/gradle/sweet-dependency/sweet-dependency-config.yaml index bd138187..37df912a 100644 --- a/gradle/sweet-dependency/sweet-dependency-config.yaml +++ b/gradle/sweet-dependency/sweet-dependency-config.yaml @@ -70,6 +70,13 @@ libraries: version: 1.0.0 hikage-widget-material: version: 1.0.0 + com.highcapable.betterandroid: + ui-component: + version: 1.0.7 + ui-extension: + version: 1.0.6 + system-extension: + version: 1.0.2 androidx.annotation: annotation: version: 1.9.1 diff --git a/yukihookapi-core/build.gradle.kts b/yukihookapi-core/build.gradle.kts index 781119e6..6a333b7f 100644 --- a/yukihookapi-core/build.gradle.kts +++ b/yukihookapi-core/build.gradle.kts @@ -44,6 +44,8 @@ dependencies { implementation(org.lsposed.hiddenapibypass.hiddenapibypass) implementation(com.highcapable.kavaref.kavaref.core) implementation(com.highcapable.kavaref.kavaref.extension) + implementation(com.highcapable.betterandroid.ui.extension) + implementation(com.highcapable.betterandroid.system.extension) implementation(androidx.core.core.ktx) implementation(androidx.appcompat.appcompat) implementation(androidx.preference.preference.ktx) diff --git a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/core/api/reflect/AndroidHiddenApiBypassResolver.kt b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/core/api/reflect/AndroidHiddenApiBypassResolver.kt index f4c87acb..53ae0134 100644 --- a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/core/api/reflect/AndroidHiddenApiBypassResolver.kt +++ b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/core/api/reflect/AndroidHiddenApiBypassResolver.kt @@ -21,7 +21,7 @@ */ package com.highcapable.yukihookapi.hook.core.api.reflect -import android.os.Build +import com.highcapable.betterandroid.system.extension.tool.SystemVersion import com.highcapable.kavaref.resolver.processor.MemberProcessor import org.lsposed.hiddenapibypass.HiddenApiBypass import java.lang.reflect.Constructor @@ -49,21 +49,13 @@ class AndroidHiddenApiBypassResolver private constructor() : MemberProcessor.Res fun get() = self } - override fun getDeclaredConstructors(declaringClass: Class): List> { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) - return super.getDeclaredConstructors(declaringClass) - val constructors = HiddenApiBypass.getDeclaredMethods(declaringClass) - .filterIsInstance>() - .toList() - return constructors - } + override fun getDeclaredConstructors(declaringClass: Class): List> = + SystemVersion.require(SystemVersion.P, super.getDeclaredConstructors(declaringClass)) { + HiddenApiBypass.getDeclaredMethods(declaringClass).filterIsInstance>().toList() + } - override fun getDeclaredMethods(declaringClass: Class): List { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) - return super.getDeclaredMethods(declaringClass) - val methods = HiddenApiBypass.getDeclaredMethods(declaringClass) - .filterIsInstance() - .toList() - return methods - } + override fun getDeclaredMethods(declaringClass: Class): List = + SystemVersion.require(SystemVersion.P, super.getDeclaredMethods(declaringClass)) { + HiddenApiBypass.getDeclaredMethods(declaringClass).filterIsInstance().toList() + } } \ No newline at end of file diff --git a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt index 07f2d239..a2ba8cd1 100644 --- a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt +++ b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt @@ -28,20 +28,19 @@ import android.content.Context import android.content.Intent import android.content.res.Configuration import android.content.res.Resources -import android.os.Build import android.os.Process import android.view.ContextThemeWrapper import android.widget.ImageView import androidx.annotation.RequiresApi import androidx.annotation.StyleRes import androidx.core.net.toUri +import com.highcapable.betterandroid.system.extension.tool.SystemVersion import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker import com.highcapable.yukihookapi.hook.param.PackageParam import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics -import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppActivity -import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppCompatActivity +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.proxy.ModuleActivity import com.highcapable.yukihookapi.hook.xposed.parasitic.context.wrapper.ModuleContextThemeWrapper import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookPrefsBridge import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit @@ -163,7 +162,7 @@ fun Resources.injectModuleAppResources() = AppParasitics.injectModuleAppResource * * 使用此方法会在未注册的 [Activity] 在 Hook APP (宿主) 中启动时自动调用 [injectModuleAppResources] 注入当前 Xposed 模块的资源 * - * - 你要将需要在宿主启动的 [Activity] 继承于 [ModuleAppActivity] or [ModuleAppCompatActivity] + * - 你要将需要在宿主启动的 [Activity] 实现 [ModuleActivity] 接口 * * 详情请参考 [注册模块 Activity](https://highcapable.github.io/YukiHookAPI/zh-cn/api/special-features/host-inject#%E6%B3%A8%E5%86%8C%E6%A8%A1%E5%9D%97-activity) * @@ -174,7 +173,7 @@ fun Resources.injectModuleAppResources() = AppParasitics.injectModuleAppResource * - 最低支持 Android 7.0 (API 24) * @param proxy 代理的 [Activity] - 必须存在于宿主的 AndroidMainifest 清单中 - 不填使用默认 [Activity] */ -@RequiresApi(Build.VERSION_CODES.N) +@RequiresApi(SystemVersion.N) fun Context.registerModuleAppActivities(proxy: Any? = null) = AppParasitics.registerModuleAppActivities(context = this, proxy) /** diff --git a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.kt b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.kt index 41394cba..49179bb2 100644 --- a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.kt +++ b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel.kt @@ -23,7 +23,6 @@ package com.highcapable.yukihookapi.hook.xposed.channel -import android.annotation.SuppressLint import android.app.Activity import android.app.ActivityManager import android.app.Application @@ -32,11 +31,13 @@ import android.content.Context import android.content.Context.ACTIVITY_SERVICE import android.content.Intent import android.content.IntentFilter -import android.os.Build import android.os.Bundle import android.os.Parcel import android.os.Parcelable import android.os.TransactionTooLargeException +import com.highcapable.betterandroid.system.extension.component.BroadcastReceiver +import com.highcapable.betterandroid.system.extension.component.registerReceiver +import com.highcapable.betterandroid.system.extension.component.sendBroadcast import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.hook.log.YLog import com.highcapable.yukihookapi.hook.log.data.YLogData @@ -142,26 +143,21 @@ class YukiHookDataChannel private constructor() { private var isAllowSendTooLargeData = false /** 广播接收器 */ - private val handlerReceiver by lazy { - object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - if (intent == null) return - intent.action?.also { action -> - runCatching { - receiverCallbacks.takeIf { it.isNotEmpty() }?.apply { - mutableListOf().also { destroyedCallbacks -> - forEach { (key, it) -> - when { - (it.first as? Activity?)?.isDestroyed == true -> destroyedCallbacks.add(key) - isCurrentBroadcast(it.first) -> it.second(action, intent) - } - } - destroyedCallbacks.takeIf { it.isNotEmpty() }?.forEach { remove(it) } + private val handlerReceiver = BroadcastReceiver { _, intent -> + intent.action?.also { action -> + runCatching { + receiverCallbacks.takeIf { it.isNotEmpty() }?.apply { + mutableListOf().also { destroyedCallbacks -> + forEach { (key, it) -> + when { + (it.first as? Activity?)?.isDestroyed == true -> destroyedCallbacks.add(key) + isCurrentBroadcast(it.first) -> it.second(action, intent) } } - }.onFailure { YLog.innerE("Received action \"$action\" failed", it) } + destroyedCallbacks.takeIf { it.isNotEmpty() }?.forEach { remove(it) } + } } - } + }.onFailure { YLog.innerE("Received action \"$action\" failed", it) } } } @@ -208,18 +204,10 @@ class YukiHookDataChannel private constructor() { internal fun register(context: Context?, packageName: String = context?.packageName ?: "") { if (YukiHookAPI.Configs.isEnableDataChannel.not() || context == null) return receiverContext = context - IntentFilter().apply { + val filter = IntentFilter().apply { addAction(if (isXposedEnvironment) hostActionName(packageName) else moduleActionName(context)) - }.also { filter -> - /** - * 从 Android 14 (及预览版) 开始 - * 外部广播必须声明 [Context.RECEIVER_EXPORTED] - */ - @SuppressLint("UnspecifiedRegisterReceiverFlag") - if (Build.VERSION.SDK_INT >= 33) - context.registerReceiver(handlerReceiver, filter, Context.RECEIVER_EXPORTED) - else context.registerReceiver(handlerReceiver, filter) } + context.registerReceiver(filter, exported = true, body = handlerReceiver) /** 排除模块环境下模块注册自身广播 */ if (isXposedEnvironment.not()) return nameSpace(context, packageName).with { @@ -696,13 +684,13 @@ class YukiHookDataChannel private constructor() { */ private fun pushReceiver(wrapper: ChannelDataWrapper<*>) { /** 发送广播 */ - (context ?: AppParasitics.currentApplication)?.sendBroadcast(Intent().apply { + (context ?: AppParasitics.currentApplication)?.sendBroadcast { action = if (isXposedEnvironment) moduleActionName() else hostActionName(packageName) /** 由于系统框架的包名可能不唯一 - 为防止发生问题不再对系统框架的广播设置接收者包名 */ if (packageName != AppParasitics.SYSTEM_FRAMEWORK_NAME) setPackage(if (isXposedEnvironment) YukiXposedModule.modulePackageName else packageName) putExtra(wrapper.instance.key + keyNonRepeatName, wrapper) - }) ?: YLog.innerE("Failed to sendBroadcast like \"${wrapper.instance.key}\", because got null context in \"$packageName\"") + } ?: YLog.innerE("Failed to sendBroadcast like \"${wrapper.instance.key}\", because got null context in \"$packageName\"") } } } \ No newline at end of file diff --git a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/AppParasitics.kt b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/AppParasitics.kt index d37b05e2..6ef945bf 100644 --- a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/AppParasitics.kt +++ b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/AppParasitics.kt @@ -30,7 +30,6 @@ import android.app.ActivityManager import android.app.AndroidAppHelper import android.app.Application import android.app.Instrumentation -import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter @@ -39,10 +38,11 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.res.Configuration import android.content.res.Resources -import android.os.Build import android.os.Handler import android.os.UserHandle import androidx.annotation.RequiresApi +import com.highcapable.betterandroid.system.extension.component.registerReceiver +import com.highcapable.betterandroid.system.extension.tool.SystemVersion import com.highcapable.kavaref.KavaRef.Companion.resolve import com.highcapable.kavaref.extension.classOf import com.highcapable.kavaref.extension.lazyClass @@ -363,20 +363,8 @@ internal object AppParasitics { * @param result 回调 - ([Context] 当前实例, [Intent] 当前对象) */ fun IntentFilter.registerReceiver(result: (Context, Intent) -> Unit) { - object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - if (context == null || intent == null) return - result(context, intent) - } - }.also { receiver -> - /** - * 从 Android 14 (及预览版) 开始 - * 外部广播必须声明 [Context.RECEIVER_EXPORTED] - */ - @SuppressLint("UnspecifiedRegisterReceiverFlag") - if (Build.VERSION.SDK_INT >= 33) - it.registerReceiver(receiver, this, Context.RECEIVER_EXPORTED) - else it.registerReceiver(receiver, this) + it.registerReceiver(filter = this, exported = true) { context, intent -> + result(context, intent) } } hostApplication = it @@ -431,13 +419,13 @@ internal object AppParasitics { * @param context 当前 [Context] * @param proxy 代理的 [Activity] */ - @RequiresApi(Build.VERSION_CODES.N) + @RequiresApi(SystemVersion.N) internal fun registerModuleAppActivities(context: Context, proxy: Any?) { if (isActivityProxyRegistered) return if (YukiXposedModule.isXposedEnvironment.not()) return YLog.innerW("You can only register Activity Proxy in Xposed Environment") if (context.packageName == YukiXposedModule.modulePackageName) return YLog.innerE("You cannot register Activity Proxy into yourself") @SuppressLint("ObsoleteSdkInt") - if (Build.VERSION.SDK_INT < 24) return YLog.innerE("Activity Proxy only support for Android 7.0 (API 24) or higher") + if (SystemVersion.isLowTo(SystemVersion.N)) return YLog.innerE("Activity Proxy only support for Android 7.0 (API 24) or higher") runCatching { ActivityProxyConfig.apply { proxyIntentName = "${YukiXposedModule.modulePackageName}.ACTIVITY_PROXY_INTENT" diff --git a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/base/ModuleAppActivity.kt b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/base/ModuleAppActivity.kt index 61b65aaa..47af68ec 100644 --- a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/base/ModuleAppActivity.kt +++ b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/base/ModuleAppActivity.kt @@ -23,45 +23,38 @@ package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base import android.app.Activity -import android.content.Context import android.content.res.Configuration import android.os.Bundle import androidx.annotation.CallSuper -import com.highcapable.yukihookapi.hook.factory.injectModuleAppResources -import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities -import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule -import com.highcapable.yukihookapi.hook.xposed.parasitic.reference.ModuleClassLoader +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.proxy.ModuleActivity /** * 代理 [Activity] * - * 继承于此类的 [Activity] 可以同时在宿主与模块中启动 + * - 由于超类继承者的不唯一性 - 此类已弃用 - 在之后的版本中将直接被删除 * - * - 在 (Xposed) 宿主环境需要在宿主启动时调用 [Context.registerModuleAppActivities] 进行注册 + * - 请现在参考并迁移到 [ModuleActivity] */ -open class ModuleAppActivity : Activity() { +@Deprecated(message = "请使用新方式来实现此功能") +open class ModuleAppActivity : Activity(), ModuleActivity { - /** - * 设置当前代理的 [Activity] 类名 - * - * 留空则使用 [Context.registerModuleAppActivities] 时设置的类名 - * - * - 代理的 [Activity] 类名必须存在于宿主的 AndroidMainifest 清单中 - * @return [String] - */ - open val proxyClassName get() = "" - - override fun getClassLoader(): ClassLoader? = ModuleClassLoader.instance() + override fun getClassLoader() = delegate.getClassLoader() @CallSuper override fun onConfigurationChanged(newConfig: Configuration) { - if (YukiXposedModule.isXposedEnvironment) injectModuleAppResources() + delegate.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig) } @CallSuper override fun onRestoreInstanceState(savedInstanceState: Bundle) { - savedInstanceState.getBundle("android:viewHierarchyState")?.classLoader = classLoader + delegate.onRestoreInstanceState(savedInstanceState) super.onRestoreInstanceState(savedInstanceState) } + + @CallSuper + override fun onCreate(savedInstanceState: Bundle?) { + delegate.onCreate(savedInstanceState) + super.onCreate(savedInstanceState) + } } \ No newline at end of file diff --git a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/base/ModuleAppCompatActivity.kt b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/base/ModuleAppCompatActivity.kt index 6c8b0c47..94c73e31 100644 --- a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/base/ModuleAppCompatActivity.kt +++ b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/base/ModuleAppCompatActivity.kt @@ -22,61 +22,39 @@ */ package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base -import android.app.Activity -import android.content.Context import android.content.res.Configuration import android.os.Bundle import androidx.annotation.CallSuper import androidx.appcompat.app.AppCompatActivity -import com.highcapable.yukihookapi.hook.factory.injectModuleAppResources -import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities -import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule -import com.highcapable.yukihookapi.hook.xposed.parasitic.reference.ModuleClassLoader +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.proxy.ModuleActivity /** * 代理 [AppCompatActivity] * - * 继承于此类的 [Activity] 可以同时在宿主与模块中启动 + * - 由于超类继承者的不唯一性 - 此类已弃用 - 在之后的版本中将直接被删除 * - * - 在 (Xposed) 宿主环境需要在宿主启动时调用 [Context.registerModuleAppActivities] 进行注册 - * - * - 在 (Xposed) 宿主环境需要重写 [moduleTheme] 设置 AppCompat 主题 - 否则会无法启动 + * - 请现在参考并迁移到 [ModuleActivity] */ -open class ModuleAppCompatActivity : AppCompatActivity() { +@Deprecated(message = "请使用新方式来实现此功能") +open class ModuleAppCompatActivity : AppCompatActivity(), ModuleActivity { - /** - * 设置当前代理的 [Activity] 类名 - * - * 留空则使用 [Context.registerModuleAppActivities] 时设置的类名 - * - * - 代理的 [Activity] 类名必须存在于宿主的 AndroidMainifest 清单中 - * @return [String] - */ - open val proxyClassName get() = "" - - /** - * 设置当前代理的 [Activity] 主题 - * @return [Int] - */ - open val moduleTheme get() = -1 - - override fun getClassLoader(): ClassLoader? = ModuleClassLoader.instance() + override fun getClassLoader() = delegate.getClassLoader() @CallSuper override fun onConfigurationChanged(newConfig: Configuration) { - if (YukiXposedModule.isXposedEnvironment) injectModuleAppResources() + delegate.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig) } @CallSuper override fun onRestoreInstanceState(savedInstanceState: Bundle) { - savedInstanceState.getBundle("android:viewHierarchyState")?.classLoader = classLoader + delegate.onRestoreInstanceState(savedInstanceState) super.onRestoreInstanceState(savedInstanceState) } @CallSuper override fun onCreate(savedInstanceState: Bundle?) { - if (YukiXposedModule.isXposedEnvironment && moduleTheme != -1) setTheme(moduleTheme) + delegate.onCreate(savedInstanceState) super.onCreate(savedInstanceState) } } \ No newline at end of file diff --git a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/caller/HandlerDelegateCaller.kt b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/caller/HandlerDelegateCaller.kt index d2f74d44..96879131 100644 --- a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/caller/HandlerDelegateCaller.kt +++ b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/caller/HandlerDelegateCaller.kt @@ -26,11 +26,11 @@ package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.call import android.app.Activity import android.content.Intent -import android.os.Build import android.os.Bundle import android.os.Handler import android.os.IBinder import android.os.Message +import com.highcapable.betterandroid.system.extension.tool.SystemVersion import com.highcapable.kavaref.KavaRef.Companion.resolve import com.highcapable.kavaref.extension.lazyClass import com.highcapable.yukihookapi.hook.core.api.reflect.AndroidHiddenApiBypassResolver @@ -93,7 +93,7 @@ internal object HandlerDelegateCaller { if (intent?.hasExtra(ActivityProxyConfig.proxyIntentName) == true) { @Suppress("DEPRECATION") val subIntent = intent.getParcelableExtra(ActivityProxyConfig.proxyIntentName) - if (Build.VERSION.SDK_INT >= 31) { + if (SystemVersion.isHighOrEqualsTo(SystemVersion.S)) { val currentActivityThread = ActivityThreadClass.resolve() .processor(AndroidHiddenApiBypassResolver.get()) .optional(silent = true) diff --git a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/caller/IActivityManagerProxyCaller.kt b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/caller/IActivityManagerProxyCaller.kt index 76a124df..02bc556d 100644 --- a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/caller/IActivityManagerProxyCaller.kt +++ b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/caller/IActivityManagerProxyCaller.kt @@ -32,9 +32,8 @@ import com.highcapable.kavaref.extension.hasClass import com.highcapable.kavaref.extension.isSubclassOf import com.highcapable.kavaref.extension.toClassOrNull import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics -import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppActivity -import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppCompatActivity import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.config.ActivityProxyConfig +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.proxy.ModuleActivity import java.lang.reflect.InvocationHandler import java.lang.reflect.Method @@ -75,10 +74,8 @@ internal object IActivityManagerProxyCaller { fun String.verify() = if (AppParasitics.hostApplication?.classLoader?.hasClass(this) == true) this else null setClassName(component.packageName, component.className.toClassOrNull()?.runCatching { when { - this isSubclassOf ModuleAppActivity::class -> - createInstanceAsTypeOrNull()?.proxyClassName?.verify() - this isSubclassOf ModuleAppCompatActivity::class -> - createInstanceAsTypeOrNull()?.proxyClassName?.verify() + this isSubclassOf ModuleActivity::class -> + createInstanceAsTypeOrNull()?.proxyClassName?.verify() else -> null } }?.getOrNull() ?: ActivityProxyConfig.proxyClassName) diff --git a/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/proxy/ModuleActivity.kt b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/proxy/ModuleActivity.kt new file mode 100644 index 00000000..36f09600 --- /dev/null +++ b/yukihookapi-core/src/main/java/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/proxy/ModuleActivity.kt @@ -0,0 +1,133 @@ +/* + * YukiHookAPI - An efficient Hook API and Xposed Module solution built in Kotlin. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/YukiHookAPI + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/6/18. + */ +@file:Suppress("UNUSED_PARAMETER") + +package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.proxy + +import android.app.Activity +import android.content.Context +import android.content.res.Configuration +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.highcapable.yukihookapi.hook.factory.injectModuleAppResources +import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities +import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.proxy.ModuleActivity.Delegate +import com.highcapable.yukihookapi.hook.xposed.parasitic.reference.ModuleClassLoader + +/** + * 模块 [Activity] 代理接口 + * + * 实现了此接口的 [Activity] 可以同时在宿主与模块中启动 + * + * - 在 (Xposed) 宿主环境需要在宿主启动时调用 [Context.registerModuleAppActivities] 进行注册 + * + * - 在 (Xposed) 宿主环境需要重写 [moduleTheme] 设置 AppCompat 主题 (如果当前是 [AppCompatActivity]) - 否则会无法启动 + * + * 请参考下方示例手动调用 [delegate] 对 [Activity] 完成必要方法的注册 - 建议在自己的 `BaseActivity` 中实现此接口并重写相关方法 - + * 然后继承自此 `BaseActivity` 来实现模块 [Activity] 的代理 + * + * ```kotlin + * abstract class BaseActivity : AppCompatActivity(), ModuleActivity { + * + * // 设置 AppCompat 主题 (如果当前是 [AppCompatActivity]) + * override val moduleTheme get() = R.style.YourAppTheme + * + * override fun getClassLoader() = delegate.getClassLoader() + * + * override fun onCreate(savedInstanceState: Bundle?) { + * delegate.onCreate(savedInstanceState) + * super.onCreate(savedInstanceState) + * } + * + * override fun onConfigurationChanged(newConfig: Configuration) { + * delegate.onConfigurationChanged(newConfig) + * super.onConfigurationChanged(newConfig) + * } + * + * override fun onRestoreInstanceState(savedInstanceState: Bundle) { + * delegate.onRestoreInstanceState(savedInstanceState) + * super.onRestoreInstanceState(savedInstanceState) + * } + * } + * ``` + * @see Delegate + */ +interface ModuleActivity { + + /** + * 模块 [Activity] 代理提供者 + */ + class Delegate internal constructor(private val self: ModuleActivity) { + + private val selfActivity get() = self as? Activity ?: error("ModuleActivity must be implemented an Activity") + + /** + * @see Activity.getClassLoader + */ + fun getClassLoader() = ModuleClassLoader.instance() + + /** + * @see Activity.onCreate + */ + fun onCreate(savedInstanceState: Bundle?) { + if (YukiXposedModule.isXposedEnvironment && self.moduleTheme != -1) + selfActivity.setTheme(self.moduleTheme) + } + + /** + * @see Activity.onConfigurationChanged + */ + fun onConfigurationChanged(newConfig: Configuration) { + if (YukiXposedModule.isXposedEnvironment) selfActivity.injectModuleAppResources() + } + + /** + * @see Activity.onRestoreInstanceState + */ + fun onRestoreInstanceState(savedInstanceState: Bundle) { + savedInstanceState.getBundle("android:viewHierarchyState")?.classLoader = selfActivity.classLoader + } + } + + /** + * 获取当前 [ModuleActivity] 的 [Delegate] 实例 + * @return [Delegate] + */ + val delegate get() = Delegate(self = this) + + /** + * 设置当前代理的 [Activity] 主题 + * @return [Int] + */ + val moduleTheme get() = -1 + + /** + * 设置当前代理的 [Activity] 类名 + * + * 留空则使用 [Context.registerModuleAppActivities] 时设置的类名 + * + * - 代理的 [Activity] 类名必须存在于宿主的 AndroidMainifest 清单中 + * @return [String] + */ + val proxyClassName get() = "" +} \ No newline at end of file