refactor: merge ModuleAppActivity, ModuleAppCompatActivity to ModuleActivity and some tweaks

This commit is contained in:
2025-06-18 20:39:26 +08:00
parent 701cbe0fc0
commit 14399538ea
15 changed files with 270 additions and 157 deletions

View File

@@ -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)
}
}
```

View File

@@ -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.
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.

View File

@@ -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"

View File

@@ -79,4 +79,10 @@ val instance: Any
`YukiHookAPI``1.3.0` 版本开始弃用了重复 Hook 的限制,现在,`YukiHookAPI` 不再限制重复 Hook 同一个方法,你可以在同一个方法上多次 Hook。
`YukiHookAPI` 同时弃用了 `hook { ... }``onAlreadyHooked` 方法,现在此方法将无作用且不会被回调,如有需要,请手动处理重复 Hook 的相关逻辑。
`YukiHookAPI` 同时弃用了 `hook { ... }``onAlreadyHooked` 方法,现在此方法将无作用且不会被回调,如有需要,请手动处理重复 Hook 的相关逻辑。
## 注册模块 Activity 行为变更
`YukiHookAPI``1.3.0` 版本开始,注册模块 `Activity` 行为的方式发生了变更。
请阅读 [注册模块 Activity](../api/special-features/host-inject#注册模块-activity) 以了解更多信息。

View File

@@ -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

View File

@@ -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)

View File

@@ -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 <T : Any> getDeclaredConstructors(declaringClass: Class<T>): List<Constructor<T>> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
return super.getDeclaredConstructors(declaringClass)
val constructors = HiddenApiBypass.getDeclaredMethods(declaringClass)
.filterIsInstance<Constructor<T>>()
.toList()
return constructors
}
override fun <T : Any> getDeclaredConstructors(declaringClass: Class<T>): List<Constructor<T>> =
SystemVersion.require(SystemVersion.P, super.getDeclaredConstructors(declaringClass)) {
HiddenApiBypass.getDeclaredMethods(declaringClass).filterIsInstance<Constructor<T>>().toList()
}
override fun <T : Any> getDeclaredMethods(declaringClass: Class<T>): List<Method> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
return super.getDeclaredMethods(declaringClass)
val methods = HiddenApiBypass.getDeclaredMethods(declaringClass)
.filterIsInstance<Method>()
.toList()
return methods
}
override fun <T : Any> getDeclaredMethods(declaringClass: Class<T>): List<Method> =
SystemVersion.require(SystemVersion.P, super.getDeclaredMethods(declaringClass)) {
HiddenApiBypass.getDeclaredMethods(declaringClass).filterIsInstance<Method>().toList()
}
}

View File

@@ -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)
/**

View File

@@ -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<String>().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<String>().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\"")
}
}
}

View File

@@ -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"

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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<Intent>(ActivityProxyConfig.proxyIntentName)
if (Build.VERSION.SDK_INT >= 31) {
if (SystemVersion.isHighOrEqualsTo(SystemVersion.S)) {
val currentActivityThread = ActivityThreadClass.resolve()
.processor(AndroidHiddenApiBypassResolver.get())
.optional(silent = true)

View File

@@ -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<ModuleAppActivity>()?.proxyClassName?.verify()
this isSubclassOf ModuleAppCompatActivity::class ->
createInstanceAsTypeOrNull<ModuleAppCompatActivity>()?.proxyClassName?.verify()
this isSubclassOf ModuleActivity::class ->
createInstanceAsTypeOrNull<ModuleActivity>()?.proxyClassName?.verify()
else -> null
}
}?.getOrNull() ?: ActivityProxyConfig.proxyClassName)

View File

@@ -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() = ""
}