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) 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 > The following example
```kotlin ```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?) { override fun onCreate(savedInstanceState: Bundle?) {
delegate.onCreate(savedInstanceState)
super.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 > The following example
```kotlin ```kotlin
class HostTestActivity : ModuleAppCompatActivity() { class HostTestActivity : BaseActivity() {
// 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
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) 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 > The following example
```kotlin ```kotlin
class HostTestActivity : ModuleAppActivity() { class HostTestActivity : BaseActivity() {
// Specify an additional proxy Activity class name // Specify an additional proxy Activity class name
// Which must also exist in the Host App's AndroidManifest // Which must also exist in the Host App's AndroidManifest
@@ -210,7 +221,7 @@ class HostTestActivity : ModuleAppActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// Module App's Resources have been injected automatically // Module App's Resources have been injected automatically
// You can directly use xml to load the layout // 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. 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 { ... }`. `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) registerModuleAppActivities(TestActivity::class.java)
``` ```
注册完成后,请将你需要使用宿主启动的模块中的 `Activity` 继承于 `ModuleAppActivity` `ModuleAppCompatActivity` 注册完成后,请将你需要使用宿主启动的模块中的 `Activity` 实现 `ModuleActivity` 接口
这些 `Activity` 现在无需注册即可无缝存活于宿主中。 这些 `Activity` 现在无需注册即可无缝存活于宿主中。
我们推荐你创建 `BaseActivity` 作为所有模块 `Activity` 的基类。
> 示例如下 > 示例如下
```kotlin ```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?) { override fun onCreate(savedInstanceState: Bundle?) {
delegate.onCreate(savedInstanceState)
super.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 ```kotlin
class HostTestActivity : ModuleAppCompatActivity() { class HostTestActivity : BaseActivity() {
// 这里的主题名称仅供参考,请填写你模块中已有的主题名称
override val moduleTheme get() = R.style.Theme_AppCompat
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -193,7 +206,7 @@ context.startActivity(context, HostTestActivity::class.java)
> 示例如下 > 示例如下
```kotlin ```kotlin
class HostTestActivity : ModuleAppActivity() { class HostTestActivity : BaseActivity() {
// 指定一个另外的代理 Activity 类名,其也必须存在于宿主的 AndroidManifest 中 // 指定一个另外的代理 Activity 类名,其也必须存在于宿主的 AndroidManifest 中
override val proxyClassName get() = "com.demo.test.activity.OtherActivity" 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``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 version: 1.0.0
hikage-widget-material: hikage-widget-material:
version: 1.0.0 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: androidx.annotation:
annotation: annotation:
version: 1.9.1 version: 1.9.1

View File

@@ -44,6 +44,8 @@ dependencies {
implementation(org.lsposed.hiddenapibypass.hiddenapibypass) implementation(org.lsposed.hiddenapibypass.hiddenapibypass)
implementation(com.highcapable.kavaref.kavaref.core) implementation(com.highcapable.kavaref.kavaref.core)
implementation(com.highcapable.kavaref.kavaref.extension) 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.core.core.ktx)
implementation(androidx.appcompat.appcompat) implementation(androidx.appcompat.appcompat)
implementation(androidx.preference.preference.ktx) implementation(androidx.preference.preference.ktx)

View File

@@ -21,7 +21,7 @@
*/ */
package com.highcapable.yukihookapi.hook.core.api.reflect 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 com.highcapable.kavaref.resolver.processor.MemberProcessor
import org.lsposed.hiddenapibypass.HiddenApiBypass import org.lsposed.hiddenapibypass.HiddenApiBypass
import java.lang.reflect.Constructor import java.lang.reflect.Constructor
@@ -49,21 +49,13 @@ class AndroidHiddenApiBypassResolver private constructor() : MemberProcessor.Res
fun get() = self fun get() = self
} }
override fun <T : Any> getDeclaredConstructors(declaringClass: Class<T>): List<Constructor<T>> { override fun <T : Any> getDeclaredConstructors(declaringClass: Class<T>): List<Constructor<T>> =
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) SystemVersion.require(SystemVersion.P, super.getDeclaredConstructors(declaringClass)) {
return super.getDeclaredConstructors(declaringClass) HiddenApiBypass.getDeclaredMethods(declaringClass).filterIsInstance<Constructor<T>>().toList()
val constructors = HiddenApiBypass.getDeclaredMethods(declaringClass) }
.filterIsInstance<Constructor<T>>()
.toList()
return constructors
}
override fun <T : Any> getDeclaredMethods(declaringClass: Class<T>): List<Method> { override fun <T : Any> getDeclaredMethods(declaringClass: Class<T>): List<Method> =
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) SystemVersion.require(SystemVersion.P, super.getDeclaredMethods(declaringClass)) {
return super.getDeclaredMethods(declaringClass) HiddenApiBypass.getDeclaredMethods(declaringClass).filterIsInstance<Method>().toList()
val methods = HiddenApiBypass.getDeclaredMethods(declaringClass) }
.filterIsInstance<Method>()
.toList()
return methods
}
} }

View File

@@ -28,20 +28,19 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.os.Build
import android.os.Process import android.os.Process
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
import android.widget.ImageView import android.widget.ImageView
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.StyleRes import androidx.annotation.StyleRes
import androidx.core.net.toUri import androidx.core.net.toUri
import com.highcapable.betterandroid.system.extension.tool.SystemVersion
import com.highcapable.yukihookapi.YukiHookAPI 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.channel.YukiHookDataChannel import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics 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.proxy.ModuleActivity
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppCompatActivity
import com.highcapable.yukihookapi.hook.xposed.parasitic.context.wrapper.ModuleContextThemeWrapper import com.highcapable.yukihookapi.hook.xposed.parasitic.context.wrapper.ModuleContextThemeWrapper
import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookPrefsBridge import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookPrefsBridge
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
@@ -163,7 +162,7 @@ fun Resources.injectModuleAppResources() = AppParasitics.injectModuleAppResource
* *
* 使用此方法会在未注册的 [Activity] 在 Hook APP (宿主) 中启动时自动调用 [injectModuleAppResources] 注入当前 Xposed 模块的资源 * 使用此方法会在未注册的 [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) * 详情请参考 [注册模块 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) * - 最低支持 Android 7.0 (API 24)
* @param proxy 代理的 [Activity] - 必须存在于宿主的 AndroidMainifest 清单中 - 不填使用默认 [Activity] * @param proxy 代理的 [Activity] - 必须存在于宿主的 AndroidMainifest 清单中 - 不填使用默认 [Activity]
*/ */
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(SystemVersion.N)
fun Context.registerModuleAppActivities(proxy: Any? = null) = AppParasitics.registerModuleAppActivities(context = this, proxy) fun Context.registerModuleAppActivities(proxy: Any? = null) = AppParasitics.registerModuleAppActivities(context = this, proxy)
/** /**

View File

@@ -23,7 +23,6 @@
package com.highcapable.yukihookapi.hook.xposed.channel package com.highcapable.yukihookapi.hook.xposed.channel
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.ActivityManager import android.app.ActivityManager
import android.app.Application import android.app.Application
@@ -32,11 +31,13 @@ import android.content.Context
import android.content.Context.ACTIVITY_SERVICE import android.content.Context.ACTIVITY_SERVICE
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import android.os.TransactionTooLargeException 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.YukiHookAPI
import com.highcapable.yukihookapi.hook.log.YLog import com.highcapable.yukihookapi.hook.log.YLog
import com.highcapable.yukihookapi.hook.log.data.YLogData import com.highcapable.yukihookapi.hook.log.data.YLogData
@@ -142,26 +143,21 @@ class YukiHookDataChannel private constructor() {
private var isAllowSendTooLargeData = false private var isAllowSendTooLargeData = false
/** 广播接收器 */ /** 广播接收器 */
private val handlerReceiver by lazy { private val handlerReceiver = BroadcastReceiver { _, intent ->
object : BroadcastReceiver() { intent.action?.also { action ->
override fun onReceive(context: Context?, intent: Intent?) { runCatching {
if (intent == null) return receiverCallbacks.takeIf { it.isNotEmpty() }?.apply {
intent.action?.also { action -> mutableListOf<String>().also { destroyedCallbacks ->
runCatching { forEach { (key, it) ->
receiverCallbacks.takeIf { it.isNotEmpty() }?.apply { when {
mutableListOf<String>().also { destroyedCallbacks -> (it.first as? Activity?)?.isDestroyed == true -> destroyedCallbacks.add(key)
forEach { (key, it) -> isCurrentBroadcast(it.first) -> it.second(action, intent)
when {
(it.first as? Activity?)?.isDestroyed == true -> destroyedCallbacks.add(key)
isCurrentBroadcast(it.first) -> it.second(action, intent)
}
}
destroyedCallbacks.takeIf { it.isNotEmpty() }?.forEach { remove(it) }
} }
} }
}.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 ?: "") { internal fun register(context: Context?, packageName: String = context?.packageName ?: "") {
if (YukiHookAPI.Configs.isEnableDataChannel.not() || context == null) return if (YukiHookAPI.Configs.isEnableDataChannel.not() || context == null) return
receiverContext = context receiverContext = context
IntentFilter().apply { val filter = IntentFilter().apply {
addAction(if (isXposedEnvironment) hostActionName(packageName) else moduleActionName(context)) 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 if (isXposedEnvironment.not()) return
nameSpace(context, packageName).with { nameSpace(context, packageName).with {
@@ -696,13 +684,13 @@ class YukiHookDataChannel private constructor() {
*/ */
private fun pushReceiver(wrapper: ChannelDataWrapper<*>) { private fun pushReceiver(wrapper: ChannelDataWrapper<*>) {
/** 发送广播 */ /** 发送广播 */
(context ?: AppParasitics.currentApplication)?.sendBroadcast(Intent().apply { (context ?: AppParasitics.currentApplication)?.sendBroadcast {
action = if (isXposedEnvironment) moduleActionName() else hostActionName(packageName) action = if (isXposedEnvironment) moduleActionName() else hostActionName(packageName)
/** 由于系统框架的包名可能不唯一 - 为防止发生问题不再对系统框架的广播设置接收者包名 */ /** 由于系统框架的包名可能不唯一 - 为防止发生问题不再对系统框架的广播设置接收者包名 */
if (packageName != AppParasitics.SYSTEM_FRAMEWORK_NAME) if (packageName != AppParasitics.SYSTEM_FRAMEWORK_NAME)
setPackage(if (isXposedEnvironment) YukiXposedModule.modulePackageName else packageName) setPackage(if (isXposedEnvironment) YukiXposedModule.modulePackageName else packageName)
putExtra(wrapper.instance.key + keyNonRepeatName, wrapper) 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.AndroidAppHelper
import android.app.Application import android.app.Application
import android.app.Instrumentation import android.app.Instrumentation
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
@@ -39,10 +38,11 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.UserHandle import android.os.UserHandle
import androidx.annotation.RequiresApi 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.KavaRef.Companion.resolve
import com.highcapable.kavaref.extension.classOf import com.highcapable.kavaref.extension.classOf
import com.highcapable.kavaref.extension.lazyClass import com.highcapable.kavaref.extension.lazyClass
@@ -363,20 +363,8 @@ internal object AppParasitics {
* @param result 回调 - ([Context] 当前实例, [Intent] 当前对象) * @param result 回调 - ([Context] 当前实例, [Intent] 当前对象)
*/ */
fun IntentFilter.registerReceiver(result: (Context, Intent) -> Unit) { fun IntentFilter.registerReceiver(result: (Context, Intent) -> Unit) {
object : BroadcastReceiver() { it.registerReceiver(filter = this, exported = true) { context, intent ->
override fun onReceive(context: Context?, intent: Intent?) { result(context, 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)
} }
} }
hostApplication = it hostApplication = it
@@ -431,13 +419,13 @@ internal object AppParasitics {
* @param context 当前 [Context] * @param context 当前 [Context]
* @param proxy 代理的 [Activity] * @param proxy 代理的 [Activity]
*/ */
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(SystemVersion.N)
internal fun registerModuleAppActivities(context: Context, proxy: Any?) { internal fun registerModuleAppActivities(context: Context, proxy: Any?) {
if (isActivityProxyRegistered) return if (isActivityProxyRegistered) return
if (YukiXposedModule.isXposedEnvironment.not()) return YLog.innerW("You can only register Activity Proxy in Xposed Environment") 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") if (context.packageName == YukiXposedModule.modulePackageName) return YLog.innerE("You cannot register Activity Proxy into yourself")
@SuppressLint("ObsoleteSdkInt") @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 { runCatching {
ActivityProxyConfig.apply { ActivityProxyConfig.apply {
proxyIntentName = "${YukiXposedModule.modulePackageName}.ACTIVITY_PROXY_INTENT" proxyIntentName = "${YukiXposedModule.modulePackageName}.ACTIVITY_PROXY_INTENT"

View File

@@ -23,45 +23,38 @@
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import com.highcapable.yukihookapi.hook.factory.injectModuleAppResources import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.proxy.ModuleActivity
import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule
import com.highcapable.yukihookapi.hook.xposed.parasitic.reference.ModuleClassLoader
/** /**
* 代理 [Activity] * 代理 [Activity]
* *
* 继承于此类的 [Activity] 可以同时在宿主与模块中启动 * - 由于超类继承者的不唯一性 - 此类已弃用 - 在之后的版本中将直接被删除
* *
* - 在 (Xposed) 宿主环境需要在宿主启动时调用 [Context.registerModuleAppActivities] 进行注册 * - 请现在参考并迁移到 [ModuleActivity]
*/ */
open class ModuleAppActivity : Activity() { @Deprecated(message = "请使用新方式来实现此功能")
open class ModuleAppActivity : Activity(), ModuleActivity {
/** override fun getClassLoader() = delegate.getClassLoader()
* 设置当前代理的 [Activity] 类名
*
* 留空则使用 [Context.registerModuleAppActivities] 时设置的类名
*
* - 代理的 [Activity] 类名必须存在于宿主的 AndroidMainifest 清单中
* @return [String]
*/
open val proxyClassName get() = ""
override fun getClassLoader(): ClassLoader? = ModuleClassLoader.instance()
@CallSuper @CallSuper
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
if (YukiXposedModule.isXposedEnvironment) injectModuleAppResources() delegate.onConfigurationChanged(newConfig)
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
} }
@CallSuper @CallSuper
override fun onRestoreInstanceState(savedInstanceState: Bundle) { override fun onRestoreInstanceState(savedInstanceState: Bundle) {
savedInstanceState.getBundle("android:viewHierarchyState")?.classLoader = classLoader delegate.onRestoreInstanceState(savedInstanceState)
super.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 package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base
import android.app.Activity
import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.highcapable.yukihookapi.hook.factory.injectModuleAppResources import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.proxy.ModuleActivity
import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule
import com.highcapable.yukihookapi.hook.xposed.parasitic.reference.ModuleClassLoader
/** /**
* 代理 [AppCompatActivity] * 代理 [AppCompatActivity]
* *
* 继承于此类的 [Activity] 可以同时在宿主与模块中启动 * - 由于超类继承者的不唯一性 - 此类已弃用 - 在之后的版本中将直接被删除
* *
* - 在 (Xposed) 宿主环境需要在宿主启动时调用 [Context.registerModuleAppActivities] 进行注册 * - 请现在参考并迁移到 [ModuleActivity]
*
* - 在 (Xposed) 宿主环境需要重写 [moduleTheme] 设置 AppCompat 主题 - 否则会无法启动
*/ */
open class ModuleAppCompatActivity : AppCompatActivity() { @Deprecated(message = "请使用新方式来实现此功能")
open class ModuleAppCompatActivity : AppCompatActivity(), ModuleActivity {
/** override fun getClassLoader() = delegate.getClassLoader()
* 设置当前代理的 [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()
@CallSuper @CallSuper
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
if (YukiXposedModule.isXposedEnvironment) injectModuleAppResources() delegate.onConfigurationChanged(newConfig)
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
} }
@CallSuper @CallSuper
override fun onRestoreInstanceState(savedInstanceState: Bundle) { override fun onRestoreInstanceState(savedInstanceState: Bundle) {
savedInstanceState.getBundle("android:viewHierarchyState")?.classLoader = classLoader delegate.onRestoreInstanceState(savedInstanceState)
super.onRestoreInstanceState(savedInstanceState) super.onRestoreInstanceState(savedInstanceState)
} }
@CallSuper @CallSuper
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
if (YukiXposedModule.isXposedEnvironment && moduleTheme != -1) setTheme(moduleTheme) delegate.onCreate(savedInstanceState)
super.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.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.IBinder import android.os.IBinder
import android.os.Message import android.os.Message
import com.highcapable.betterandroid.system.extension.tool.SystemVersion
import com.highcapable.kavaref.KavaRef.Companion.resolve import com.highcapable.kavaref.KavaRef.Companion.resolve
import com.highcapable.kavaref.extension.lazyClass import com.highcapable.kavaref.extension.lazyClass
import com.highcapable.yukihookapi.hook.core.api.reflect.AndroidHiddenApiBypassResolver import com.highcapable.yukihookapi.hook.core.api.reflect.AndroidHiddenApiBypassResolver
@@ -93,7 +93,7 @@ internal object HandlerDelegateCaller {
if (intent?.hasExtra(ActivityProxyConfig.proxyIntentName) == true) { if (intent?.hasExtra(ActivityProxyConfig.proxyIntentName) == true) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
val subIntent = intent.getParcelableExtra<Intent>(ActivityProxyConfig.proxyIntentName) val subIntent = intent.getParcelableExtra<Intent>(ActivityProxyConfig.proxyIntentName)
if (Build.VERSION.SDK_INT >= 31) { if (SystemVersion.isHighOrEqualsTo(SystemVersion.S)) {
val currentActivityThread = ActivityThreadClass.resolve() val currentActivityThread = ActivityThreadClass.resolve()
.processor(AndroidHiddenApiBypassResolver.get()) .processor(AndroidHiddenApiBypassResolver.get())
.optional(silent = true) .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.isSubclassOf
import com.highcapable.kavaref.extension.toClassOrNull import com.highcapable.kavaref.extension.toClassOrNull
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics 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.config.ActivityProxyConfig
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.proxy.ModuleActivity
import java.lang.reflect.InvocationHandler import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method 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 fun String.verify() = if (AppParasitics.hostApplication?.classLoader?.hasClass(this) == true) this else null
setClassName(component.packageName, component.className.toClassOrNull()?.runCatching { setClassName(component.packageName, component.className.toClassOrNull()?.runCatching {
when { when {
this isSubclassOf ModuleAppActivity::class -> this isSubclassOf ModuleActivity::class ->
createInstanceAsTypeOrNull<ModuleAppActivity>()?.proxyClassName?.verify() createInstanceAsTypeOrNull<ModuleActivity>()?.proxyClassName?.verify()
this isSubclassOf ModuleAppCompatActivity::class ->
createInstanceAsTypeOrNull<ModuleAppCompatActivity>()?.proxyClassName?.verify()
else -> null else -> null
} }
}?.getOrNull() ?: ActivityProxyConfig.proxyClassName) }?.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() = ""
}