自定义XPosed模块
自定义 Xposed
模块
什么是 Xposed
框架?
- Xposed 框架是适用于 Android 的一个工具,它允许开发者在不修改 APK 文件的情况下对 APP 和系统行为进行修改或定制。
- 通过 Hook 技术,Xposed 可以在运行时对应用程序的方法进行拦截、修改,甚至替换,从而实现对系统和应用程序的深度定制。
- 注意: 使用 Xposed 需要获取 Root 权限或修改系统,可能对设备的稳定性和安全性有影响,且某些应用可能检测到 Xposed 的存在而限制功能。
Xposed
模块的结构
一个典型的 Xposed 模块是一个普通的 Android 应用程序(APK),但它包含了一些特定的配置和代码,以便 Xposed 框架能够正确加载和执行。
Xposed
模块组成
- 基本组成:
AndroidManifest.xml
声明应用程序的基本信息。- 模块代码 包含了实际的 Hook 逻辑,通常实现了
Xposed
的接口。 assets
目录下的xposed_init
文件 指示Xposed
框架入口类的位置(实现了IXposedHookLoadPackage
接口的全路径)。
xposed_init
的作用
- 定义:
xposed_init
是存放在模块的assets
目录中的一个纯文本文件,文件名固定为xposed_init
,没有扩展名。
- 作用:
- 告知 Xposed 框架哪些类包含模块的入口代码,即哪些类需要在系统启动时被加载。
- 框架在启动时会读取每个已激活的 Xposed 模块的
xposed_init
文件,获取要加载的类名,然后使用这些类初始化模块。
- 内容:
1
me.hacket.hello_world.HelloWorldXPosed
Xposed 资源梳理
XposedBridge.jar
:XposedBridge.jar 是 Xposed 提供的 jar 文件,负责在 Native 层与 FrameWork 层进行交互。/system/bin/app_process 进程启动过程中会加载该 jar 包,其它的 Modules 的开发与运行都是基于该 jar 包的。 注意:XposedBridge.jar 文件本质上是由 XposedBridge 生成的 APK 文件更名而来。Xposed
:Xposed 的 C++ 部分,主要是用来替换/system/bin/app_process,并为 XposedBridge 提供 JNI 方法。XposedInstaller
:Xposed 的安装包,负责配置 Xposed 工作的环境并且提供对基于 Xposed 框架的 Modules 的管理。在安装 XposedInstaller 之后,app_process 与 XposedBridge.jar 放置在了/data/data/de.robv.android.xposed.installer
。XposedMods
:使用 Xposed 开发的一些 Modules,其中 AppSettings 是一个可以进行权限动态管理的应用
Xposed 模块的初始化流程
Xposed
框架启动:- 当设备开机或系统启动时,
Xposed
框架被加载。
- 当设备开机或系统启动时,
- 加载已激活的模块:
- Xposed 框架遍历所有已激活的模块,读取它们的
xposed_init
文件。
- Xposed 框架遍历所有已激活的模块,读取它们的
- 加载入口类:
- 对于每个模块,
Xposed
会使用模块的ClassLoader
加载xposed_init
文件中指定的类。
- 对于每个模块,
- 调用入口类的方法:
- 加载类后,
Xposed
框架会调用该类的init
方法(通常是实现的接口方法),在适当的时机执行模块的初始化逻辑。
- 加载类后,
自定义 Xposed
步骤
本质
从本质上来讲,Xposed 模块也是一个 Android 程序。但与普通程序不同的是,想要让写出的 Android 程序成为一个 Xposed 模块,要额外多完成以下四个步骤:
- 让手机上的 xposed 框架知道我们安装的这个程序是个 xposed 模块。
- 模块里要包含有 xposed 的 API 的 jar 包,以实现下一步的 hook 操作。
- 这个模块里面要有对目标程序进行 hook 操作的方法。
- 要让手机上的 xposed 框架知道,我们编写的 xposed 模块中,哪一个方法是实现 hook 操作的。
对应上面的四个步骤我们需要做的修改有:
AndroidManifest.xml
XposedBridgeApi-xx.jar
与build.gradle
- 实现 hook 操作的具体代码
src/main/assets/xposed_init
目标
- hook 应用包名为:
me.hacket.demos
- 在
me.hacket.sample.MainActivity
的 onCreate 弹出一个 toast
添加 Gradle
依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
repositories {
jcenter()
}
dependencies {
compileOnly 'de.robv.android.xposed:api:82'
// compileOnly 'de.robv.android.xposed:api:82:sources'
}
// settings.gradle.kts
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven("https://api.xposed.info/")
}
}
更改 AndroidManifest.xml 文件
添加模块的元数据,让 Xposed 框架识别该模块。放到 application
节点下
1
2
3
4
5
6
7
8
9
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="这是一个Xposed模块" />
<meta-data
android:name="xposedminversion"
android:value="30" />
创建入口类
实现接口:IXposedHookLoadPackage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class HelloWorldXPosed : IXposedHookLoadPackage {
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
val packageName = lpparam.packageName
XposedBridge.log("----------Hello World, handleLoadPackage! packageName: $packageName")
if (!packageName.equals("me.hacket.demos")) {
return
}
XposedBridge.log("--------Hello World, XPosed! packageName: $packageName")
XposedHelpers.findAndHookMethod("me.hacket.sample.MainActivity",
lpparam.classLoader,
"onCreate",
Bundle::class.java,
object : XC_MethodHook() {
@Throws(Throwable::class)
override fun afterHookedMethod(param: MethodHookParam) {
val activity = param.thisObject as? Activity
// 在 onCreate 方法中添加你的方法
myMethod(activity)
}
})
}
private fun myMethod(activity: Activity?) {
// 你的方法实现
Log.e("hacket.hook", "hooked method called")
Handler(Looper.getMainLooper()).postDelayed({
Toast.makeText(activity, "hooked method called", Toast.LENGTH_SHORT).show()
}, 4000)
}
}
配置 xposed_init
告诉 Xposed 框架,你的 hook 代码的入口点在哪里,以便框架能够在合适的时机调用你的 hook 代码。在 app/src/main
目录下创建一个名为 assets
的文件夹,并在其中创建一个名为 xposed_init
的文件(没有扩展名)。在这个文件中,写入你的 IXposedHookLoadPackage 实现类的全限定名:
1
me.hacket.hello_world.HelloWorldXPosed
进行编译签名打包
不用用 AS→Run,这种方式可能找不到模块的入口
激活模块
- 打开设备上的 Xposed 框架管理应用(Xposed Installer) 或 LSPosed。
- 在模块列表中找到您的模块,勾选启用。
- 重启设备,使模块生效。
测试
安装好之后在 LSPosed 中启用该 XPosed 模块,勾选应用的 app,然后重启手机应用/重启系统更改
其他
LSPosed 作用于应用 自动勾选
LSPosed 模块自动勾选应用模块作用域 LSPosed 推荐的应用。
Edxposed 的模块作用域和 LSPosed 需要用户手动勾选应用,但是有些模块可以自动勾选应用。
- 添加 array:在
res -> values
目录下添加array.xml
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="xposed_scope">
<!--这里是填入需要自动勾选的应用的包名,多个就是多个item-->
<item>com.eg.android.AlipayGphone</item>
</string-array>
</resources>
- 添加 Scope: 在清单文件
Androidmanifest.xml
的 application 标签下添加xposedscope
的 meta-data,跟xposedmodule
是同级的,如下
1
2
3
<meta-data
android:name="xposedscope"
android:resource="@array/xposed_scope" />
- 在
LSposed
的 Modules 对应的模块会自动勾选该包名对应的 APP
示例
Hook JobSchedulerService 修改 15 min 限制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class ModifyJobScheduler implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) {
String packageName = lpparam.packageName;
if (!packageName.equals("android") && !packageName.equals("system")
) {
XposedBridge.log("Init error, packageName:" + packageName + ",not android or system, ignore.");
return;
} else {
XposedBridge.log("Init start , hook packageName:" + packageName);
}
try {
Class<?> jobSchedulerServiceClass = XposedHelpers.findClass(
"com.android.server.job.JobSchedulerService",
// lpparam.classLoader // failed to load class com.android.server.job.JobSchedulerService
ClassLoader.getSystemClassLoader() // 修改为使用 ClassLoader.getSystemClassLoader()
);
// Hook onStart() 确保mConstants已初始化
XposedHelpers.findAndHookMethod(jobSchedulerServiceClass, "onStart",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object service = param.thisObject;
Object constants = XposedHelpers.getObjectField(service, "mConstants");
try {
Class<?> constantsClass = constants.getClass();
// 修改MIN_PERIOD_MS(移除final修饰符)
Field minPeriodField = constantsClass.getDeclaredField("MIN_PERIOD_MS");
minPeriodField.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(minPeriodField, minPeriodField.getModifiers() & ~Modifier.FINAL);
minPeriodField.setLong(constants, 60_000L);
// 类似修改MIN_FLEX_MS...
XposedBridge.log("MIN_PERIOD_MS updated to 1 minute");
} catch (Exception e) {
XposedBridge.log("Error: " + e);
}
}
});
} catch (Throwable t) {
XposedBridge.log("Init error: " + t);
}
}
}
IXposedHookLoadPackage
:一个接口,允许开发者实现应用加载时的 hook 逻辑。通过实现这个接口,模块可以在特定应用被加载时执行自定义代码。XposedBridge
:Xposed 框架的核心类,提供了多种方法来进行 hook 和日志记录等操作。XC_LoadPackage.LoadPackageParam
:一个类,包含了关于加载的应用程序的信息,例如包名、类加载器等。handleLoadPackage
是接口IXposedHookLoadPackage
中定义的方法。当一个应用被加载时,Xposed 框架会调用这个方法。LoadPackageParam lpparam
参数包含了被加载应用的相关信息。XposedBridge.log(…)
方法用于记录日志,这里记录了加载的应用的包名。通过查看 Xposed 的日志,开发者可以看到哪些应用被加载了。
测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private fun scheduleJob() {
val serviceName = ComponentName(packageName, TestJobService::class.java.name)
val builder = JobInfo.Builder(JOB_ID, serviceName)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) // 可选:设置网络类型
.setPeriodic(1 * 30 * 1000) // 设置周期为 1 分 30 秒 (90秒) - 您可以调整这个时间
.setPersisted(true) // 设备重启后任务仍然存在
val scheduler = getSystemService(JOB_SCHEDULER_SERVICE) as JobScheduler
val result = scheduler.schedule(builder.build())
if (result == JobScheduler.RESULT_SUCCESS) {
Log.d(TAG, "Job scheduled successfully")
Toast.makeText(this, "Job scheduled successfully", Toast.LENGTH_SHORT).show()
} else {
Log.e(TAG, "Job scheduling failed")
Toast.makeText(this, "Job scheduling failed", Toast.LENGTH_SHORT).show()
}
}
class TestJobService : JobService() {
private val TAG = "hacket.JobSchedulerTest"
override fun onStartJob(params: JobParameters?): Boolean {
Log.d(TAG, "TestJobService onStartJob() executed at: " + System.currentTimeMillis())
// 这里可以添加您要执行的周期性任务的代码
jobFinished(params, false) // false 表示不需要重新调度,因为是周期性任务
return true // true 表示任务是异步的,onStopJob 需要被调用
}
override fun onStopJob(params: JobParameters?): Boolean {
Log.d(TAG, "TestJobService onStopJob()")
return true // true 表示如果任务被取消,需要稍后重新调度
}
}
问题: failed to load class com.android.server.job.JobSchedulerService
问题
Hook 不到类报错以及 Xposed 的 Hook 方法
failed to load class me.hacket.sample.MainActivity
解决 AndroidStudio 修改代码运行没变化没生效的问题 -Xposed 插件重载
是因为 AndroidStudio 对 Android11 之后部署优化的问题,在 Run 处的位置 EditConfigruations, 勾选 Always install with package manager
即可。
Ref
开源的 XPosed 模块
QAuxiliary
QAuxiliary
是一个基于 QNotified
的开源 Xposed
模块