记一次将 xposed 模块迁移到新版 api

前言

记一次将 xposed 模块迁移到新版 API 。

最近在想着将 xposed 模块迁移到新版的 API 。LSP 团队在 wiki 上也写上了关于 API 变更的帖子。不过迁移过程还是遇到了一些问题,故记录一下。

正文

依赖

在旧版 API 下,模块需要安装 xposed 的依赖,并将它设置为 compileOnly

libs.versions.toml 文件如下:

1
2
3
4
5
[versions]
xposed = "82"

[libraries]
xposed = { group = "de.robv.android.xposed", name = "api", version.ref = "xposed" }

build.gradle.kts 文件如下:

1
2
3
dependencies {
compileOnly(libs.xposed)
}

而在新版 API 下,我们需要将这个换成新版的 API 包。

libs.versions.toml 文件如下:

1
2
3
4
5
[versions]
libxposed = "102.0.0"

[libraries]
libxposedapi = { group = "io.github.libxposed", name = "api", version.ref = "libxposed" }

同样这个包也是 compileOnly 的, build.gradle.kts 文件如下:

1
2
3
dependencies {
compileOnly(libs.libxposedapi)
}

入口配置

接着去除 src/main/assets/xposed_init 文件,该文件在旧版上的作用为标记入口类,如下:

1
com.par9uet.fkmojiread.MainHook

而在新版上,内容不变,但是要将文件移动到 src/main/resources/META-INF/xposed 下并重命名为 java_init.list

作用域

在旧版 API 上,我们是在 AndroidManifest.xml 内在 application 标签内定义元数据来给模块绑定作用域:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true">
<!-- 其他元数据 -->
<!-- 模块作用域 -->
<meta-data
android:name="xposedscope"
android:resource="@array/xposedscope"/>
</application>
</manifest>

这里的 @array/xposedscope 对应的文件位于 src/main/res/values/array.xml ,内容如下:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="xposedscope" >
<!-- 这里填写模块的作用域应用的包名,可以填多个。 -->
<item>com.mojidict.read</item>
</string-array>
</resources>

而在新版 API 下,不是在此处编写作用域了,我们需要在和入口配置同级的目录下,也就是 src/main/resources/META-INF/xposed ,新加一个 scope.list 文件来定义:

1
com.mojidict.read

注意这里每行对应一个应用。

API 版本标识

在旧版 API 下,版本标识和作用域一样,在 AndroidManifest.xml 内在 application 标签内定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true">
<!-- 版本标识 -->
<meta-data
android:name="xposedminversion"
android:value="93"/>
</application>
</manifest>

而在新版 API 下,不是在此处编写作用域了,我们需要在和入口配置同级的目录下,也就是 src/main/resources/META-INF/xposed ,新加一个 module.prop 文件来定义:

1
2
minApiVersion=101
targetApiVersion=102

其中 minApiVersion 表明模块所需的最低版本,新版 API 的第一个版本就是 101 ,所以一般这里最低就是 101 了, targetApiVersion 则表明该模块引入的版本,目前最新的是 102 。所以这个根据实际情况编写。

这个文件是一定要存在的,不然无法被 Lsposed 识别成模块,当时迁移的时候没写这个文件,百思不得其解为啥没识别成模块,最后还是上 github 看了一些基于新版 API 的模块,才知道缺了这个文件…

模块描述

在旧版 API 下,模块描述和作用域一样,在 AndroidManifest.xml 内在 application 标签内定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true">
<meta-data
android:name="xposeddescription"
android:value="FKMOJi阅读" />
</application>
</manifest>

而在新版 API 下,需要改为在 application 标签上使用 android:description 来定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:description="@string/app_description"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true">
</application>
</manifest>

src/main/res/values/strings.xml 添加一行 app_description

1
2
3
4
<resources>
<string name="app_name">fkmojiread</string>
<string name="app_description">解锁永久会员部分功能</string>
</resources>

hook

在旧版 API 下,入口类需要实现 IXposedHookLoadPackage 接口,然后实现 handleLoadPackage 方法,在里面写 hook 逻辑。

1
2
3
4
5
6
7
8
9
10
public class MainHook implements IXposedHookLoadPackage {

@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam param) {
if (!param.packageName.equals("{包名}")) {
return;
}
// 执行Hook 逻辑
}
}

新版 API 下,需要继承 XposedModule ,然后实现 onPackageLoaded 来注入 hook 逻辑:

1
2
3
4
5
6
7
8
9
10
public class MainHook extends XposedModule {

@Override
public void onPackageLoaded(PackageLoadedParam param) {
if (!param.getPackageName().equals("{包名}")) {
return;
}
// 执行Hook
}
}

关于 XposedHelpers

旧版 API 提供了 XposedHelpers 工具类,能少写很多样板代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MainHook implements IXposedHookLoadPackage {

void hook(XC_LoadPackage.LoadPackageParam param) {
// 第一种 填ClassName
XposedHelpers.findAndHookMethod(
"V5.a", // 类完整路径
param.classLoader,
"a", // 方法名
String.class, // 方法参数
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {}

@Override
protected void afterHookedMethod(MethodHookParam param) {}
}
);
}
}

虽然在 wiki 里说有提供新版的 helper :

但是很遗憾,目前这个包并没有提供 maven 包供使用,而且源码似乎还有一些问题,所以能做的就是把这里面的源码拷贝到自己的模块里面构建。

这里有几个要注意的问题,首先是 org.apache.commons:commons-lang3 提供的 MemberUtilsX 并没有暴露,但源码里面导入了这个,有点没搞懂,是我配置的问题么,我的解决方法就是把这个类直接复制出来。

在把 MemberUtilsX 复制出来后,会提示缺少 AccessibleObjects ,这个类也是没有暴露出来的,同样也是复制出来即可,实现逻辑很简单。

在把 package 定义都修改之后,基本上就能打包了,但是如果使用的话,会发现空指针错误,来源是 XposedBridge 内的静态 api 属性为空:

这里我的解决方法就是手动调用它的 init 方法,调用时机的话继承 XposedModuleonModuleLoaded 上,将自身传入 init

1
2
3
4
5
6
7
8
public class MainHook extends XposedModule {

@Override
public void onModuleLoaded(@NonNull ModuleLoadedParam param) {
super.onModuleLoaded(param);
XposedBridge.init(this);
}
}

之所以能这么写,原因是 XposedModule 继承了 XposedInterfaceWrapper ,而 XposedInterfaceWrapper 实现了 XposedInterface ,所以模块自身其实也就是一个 XposedInterface 。这样子过后,基本上就可以无痛将以前的逻辑迁移过来了。

后记

省流步骤总结:

  • 安装新版依赖。
  • 调整 xposed_init 位置,并重命名为 java_init.list
  • 去除 AndroidManifest.xml 里的元数据。
  • 新增 scope.listmodule.prop 文件。
  • 实现 IXposedHookLoadPackage 改为继承 XposedModule ,并实现对应方法。
  • 迁移 helper 库(如果需要的话)。

如果还有疑问,也也可以查看我的仓库:Dedicatus546/fkmojiread 。目前也是成功迁移到了新版 API 。