import{_ as a,r as l,o,c as p,b as s,d as t,a as i,e}from"./app.fb8271cf.js";const c={},r=i(`
This is an extension that injects Module App's Resources,
Activity
components, andContext
topics into the Host App.
Before using the following functions, in order to prevent Resource Id from conflicting with each other, you need to modify the Resource Id in the build.gradle
of the current Xposed Module project.
android {
androidResources.additionalParameters("--allow-reserved-package-id", "--package-id", "0x64")
}
android {
aaptOptions.additionalParameters '--allow-reserved-package-id', '--package-id', '0x64'
}
Notice
The sample Resource Id value provided is for reference only, 0x7f cannot be used, the default is 0x64.
In order to prevent the existence of multiple Xposed Modules in the current Host App, it is recommended to customize your own Resource Id.
After the Host App is hooked, we can directly inject the Context
obtained in the Hooker into the current Module App's Resources.
The following example
injectMember {
method {
name = "onCreate"
param(BundleClass)
}
afterHook {
instance<Activity>().also {
// <Scenario 1> Inject Module App's Resources through Context
it.injectModuleAppResources()
// <Scenario 2> Get the Host App's Resources directly and inject the Module App's Resources
it.resources.injectModuleAppResources()
// Use the Module App's Resource Id directly
it.getString(R.id.app_name)
}
}
}
You can also inject current Module App's Resources directly in AppLifecycle
.
The following example
onAppLifecycle {
onCreate {
// Globally inject Module App's Resources, but only in the global lifecycle
// Methods like ImageView.setImageResource need to be injected separately in Activity
// <Scenario 1> Inject Module App's Resources through Context
injectModuleAppResources()
// <Scenario 2> Get the Host App's Resources directly and inject the Module App's Resources
resources.injectModuleAppResources()
// Use the Module App's Resource Id directly
getString(R.id.app_name)
}
}
Tips
For more functions, please refer to the Context+Resources.injectModuleAppResources method.
When the Activity
of all applications in the Android system starts, it needs to be registered in AndroidManifest.xml
.
During the Hook process, if we want to directly start the unregistered Activity
in the Module App through the Host App, what should we do?
After the Host App is hooked, we can directly register the Activity
proxy of the current Module App in the Context
obtained in the Hooker.
The following example
injectMember {
method {
name = "onCreate"
param(BundleClass)
}
afterHook {
instance<Activity>().registerModuleAppActivities()
}
}
You can also register the current Module App's Activity
proxy directly in AppLifecycle
.
The following example
onAppLifecycle {
onCreate {
registerModuleAppActivities()
}
}
If the proxy
parameter is not filled in, the API will automatically obtain the current Host App's launching entry Activity
for proxying according to the current Context
.
Usually, it works, but the above situation will fail in some apps, for example, some Activity
will add launching parameters to the registration list, so we need to use another solution.
If the unregistered Activity
cannot be launched correctly, we can manually get the Host App's AndroidManifest.xml
for analysis to get a registered Activity
tag and get the name
.
You need to choose an unneeded Activity
that may not be used by the current Host App as a "puppet" to proxy it, which usually works.
For example, we have found a suitable Activity
that can be proxied.
The following example
<activity
android:name="com.demo.test.activity.TestActivity"
...>
According to the name
, we only need to add this parameter to the method for registration.
The following example
registerModuleAppActivities(proxy = "com.demo.test.activity.TestActivity")
Alternatively, if you write a stub
for the Host App's class, you can register it directly through the Class
object.
The following example
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
.
These Activity
now live seamlessly in the Host App without registration.
The following example
class HostTestActivity : ModuleAppActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
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)
}
}
If you need to extends ModuleAppCompatActivity
, you need to set the AppCompat theme manually.
The following example
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
override fun onCreate(savedInstanceState: Bundle?) {
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)
}
}
After all the above steps are completed, you can happily call startActivity
anywhere in the (Xposed) Host environment where a Context
exists.
The following example
val context: Context = ... // Assume this is your Context
context.startActivity(context, HostTestActivity::class.java)
Tips
For more functions, please refer to the Context.registerModuleAppActivities method.
Sometimes, we need to use MaterialAlertDialogBuilder
to beautify our own dialogs in the Host App, but we can't create them without the AppCompat theme.
The style on this component requires your app theme to be Theme.AppCompat (or a descendant).
At this time, we want to use MaterialAlertDialogBuilder
to create a dialog in the current Activity
of the Host App being hooked, you can have the following methods.
The following example
injectMember {
method {
name = "onCreate"
param(BundleClass)
}
afterHook {
// Use applyModuleTheme to create a theme resource in the current Module App
val appCompatContext = instance<Activity>().applyModuleTheme(R.style.Theme_AppCompat)
// Directly use this Context that wraps the Module App's theme to create a dialog
MaterialAlertDialogBuilder(appCompatContext)
.setTitle("AppCompat Theme Dialog")
.setMessage("I am an AppCompat theme dialog displayed in the Host App.")
.setPositiveButton("OK", null)
.show()
}
}
You can also set the system (native) night mode and day mode on the current Context
through uiMode
.
Which requires at least Android 10 and above system version support and the current theme contains night mode related elements.
The following example
injectMember {
method {
name = "onCreate"
param(BundleClass)
}
afterHook {
// Define the theme resource in the current Module App
var appCompatContext: ModuleContextThemeWrapper
// <Scenario 1> Get the Configuration object directly to set
appCompatContext = instance<Activity>()
.applyModuleTheme(R.style.Theme_AppCompat)
.applyConfiguration { uiMode = Configuration.UI_MODE_NIGHT_YES }
// <Scenario 2> Create a new Configuration object
// This solution will destroy the original font scaling and other settings in the current Host App
// You need to manually re-pass parameters such as densityDpi
appCompatContext = instance<Activity>().applyModuleTheme(
theme = R.style.Theme_AppCompat,
configuration = Configuration().apply { uiMode = Configuration.UI_MODE_NIGHT_YES }
)
// Directly use this Context that wraps the Module App's theme to create a dialog
MaterialAlertDialogBuilder(appCompatContext)
.setTitle("AppCompat Theme Dialog")
.setMessage("I am an AppCompat theme dialog displayed in the Host App.")
.setPositiveButton("OK", null)
.show()
}
}
This way, we can create dialogs in the Host App very simply using MaterialAlertDialogBuilder
.