libxposed API 101 找不到 getModuleContext(),如何读取模块资源?
结论
libxposed API 101 的公开接口中没有 getModuleContext(),继承 XposedModule 也不会获得这个方法。读取模块资源时,可以先调用 getModuleApplicationInfo() 获取模块自身的 ApplicationInfo。如果已经拿到目标进程中的有效 Context,可以通过 PackageManager.getResourcesForApplication(ApplicationInfo) 加载模块资源。
getModuleApplicationInfo() 返回的是包信息,不能直接调用 getModuleApplicationInfo().getResources()。
为什么继承后仍然报错?
API 101 的 XposedModule 继承自 XposedInterfaceWrapper。后者提供了 getModuleApplicationInfo(),但没有 getModuleContext(),因此编译器无法解析该调用。可以核对 API 101 的 XposedModule 源码和 XposedInterfaceWrapper 源码。
“接口不存在”和“接口已弃用”是两种情况。单凭 cannot find symbol,不能认定某个方法曾经存在并被移除,也不能据此推断 API 的设计动机。
如何通过公开 API 获取模块资源?
需要先满足两个条件:模块已经进入框架允许使用接口的生命周期阶段,并且当前代码能够取得有效的 Android Context。API 101 的入口类说明要求不要在 onModuleLoaded() 之前进行初始化。
获得 Context 后,可以在现有 XposedModule 子类中加入下面的方法:
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.util.Log;
private Resources loadModuleResources(Context targetContext) {
ApplicationInfo moduleInfo = getModuleApplicationInfo();
try {
return targetContext.getPackageManager()
.getResourcesForApplication(moduleInfo);
} catch (PackageManager.NameNotFoundException e) {
log(Log.ERROR, "Back2Kill", "Failed to load module resources", e);
return null;
}
}
targetContext 由调用方传入,必须是有效的 Context。例如,可以在合适的 Hook 回调中从目标 View 获取。代码不依赖 onPackageReady() 直接提供 Context。
Android 的 PackageManager 文档说明,getResourcesForApplication(ApplicationInfo) 用于获取指定应用的资源,无法加载时会抛出 NameNotFoundException。这个方案的前提是目标进程能够访问模块资源。
将返回值赋给 mRes 后,先判断是否为 null,再调用 mRes.getString()。传入的资源 ID 必须属于模块自己的 R.string,不要使用目标应用同名资源的 ID。若使用 getIdentifier(),应传入模块包名,并处理返回 0 的情况。
参考代码中的反射方案是否可靠?
通过 AssetManager 和 addAssetPath() 加载 APK 可以作为特定环境下的候选方案,但不能保证适用于所有 Android 版本和加载器。
需要留意以下几点:
addAssetPath()属于非 SDK 接口。Android 对非 SDK 接口的访问有限制,反射也受影响;具体行为取决于系统版本及运行环境,参见 Android 非 SDK 接口限制。Resources(AssetManager, DisplayMetrics, Configuration)构造方法已被弃用,参见 Resources 文档。- 仅加入
moduleInfo.sourceDir,没有显式处理可能存在的 split APK 资源。 - 使用
Resources.getSystem()的配置创建资源,未必符合目标界面的语言和显示配置;长期缓存时还要考虑配置变化。
如果需要保留反射方案,应检查资源路径是否成功加入,并记录完整异常堆栈。原代码只记录 t.getMessage(),容易丢失排查线索。加载失败后也应停止使用未初始化的 mRes。
修改后仍然报错时检查什么?
先检查入口类是否导入 io.github.libxposed.api.XposedModule,再检查构建实际解析到的 libxposed 依赖版本。如果连 getModuleApplicationInfo() 都无法编译,应优先检查依赖冲突、导入和调用位置。
如果已经能够编译,但运行时加载资源失败,则继续检查模块 APK 是否可访问、资源 ID 是否属于模块,以及异常堆栈中的具体失败位置。编译期的方法解析错误和运行期的资源加载错误需要分别处理。
备注:内容仅供参考。
