整体架构
这一篇从高层俯瞰 VirtualXposed 是怎么组织的,建立全局认知后再往下钻各子系统。
两层结构
VirtualXposed 在工程上分两层:
settings.gradle 里 include ':lib', ':app', ':launcher',三者构成完整 APK。
三个进程
VirtualXposed 安装后,运行时会分化出多种进程,每种职责不同。核心是这三类(详见进程模型):
| 进程 | 职责 | 关键类 |
|---|---|---|
| 主进程 (Main) | 宿主 UI、桌面、设置 | XApp、NewHomeActivity |
| Server 进程 | 虚拟系统服务端:AMS/PMS/账户/定位/通知/Job | BinderProvider、VActivityManagerService、VPackageManagerService |
| 虚拟 App 进程 (VAppClient) | 跑被虚拟的 App + Xposed 模块 | VClientImpl、ExposedBridge |
一个简化的运行时视图:
关键设计:用 ContentProvider 当 IPC 桥
一个普通 App 怎么凭空搞出一个“系统服务端”?VirtualXposed 的巧思是:用一个 ContentProvider 作为 server 进程的引导入口。
BinderProvider(一个 ContentProvider)注册在 Manifest 里,被系统拉起时onCreate里初始化所有虚拟服务并塞进ServiceCache。- 客户端通过
ProviderCall调它的call("@")方法,拿到IServiceFetcher的 IBinder。 - 之后所有虚拟服务的获取都走这个 fetcher:
ServiceManagerNative.getService("activity")→ server 返回VActivityManagerService的 IBinder。
这样不依赖任何系统权限,纯靠 ContentProvider + Binder 就搭起了跨进程的服务总线。详见跨进程 IPC 桥。
关键设计:劫持 ServiceManager 缓存
客户端怎么让目标 App 以为自己在真系统里?答案是把系统 ServiceManager 缓存里的 IBinder 换成自己的代理。
BinderInvocationProxy.inject()调ServiceManager.sCache.get().put(name, this),用假 IBinder 替换真 IBinder。- 假 IBinder 的
queryLocalInterface返回一个 Java 动态代理,代理上挂着一堆MethodProxy。 - 目标 App 调
getSystemService(LOCATION_SERVICE)→ 拿到代理 → 每个方法调用都被对应MethodProxy拦截 → 返回虚拟数据(假定位、假 IMEI 等)。
这是整个 Hook 体系的核心,详见系统服务 Hook。
关键设计:Stub Activity 欺骗 AMS
Android 不允许一个 App 直接启动另一个 App 的 Activity(受 manifest 限制)。VirtualXposed 怎么启动虚拟 App 的 Activity?
- 客户端 Hook
startActivity,把真实 Intent 改写成指向预注册的占位 Activity(StubActivity$C0~C99,共 100 个,对应不同 launchMode/taskAffinity)。 - 真实 AMS 看到的是“宿主自己的 Activity”,正常启动。
- Activity 实例化前,Hook
ActivityThread.mH的mCallback(HCallbackStub),拦截LAUNCH_ACTIVITY消息,把占位 Intent 还原成真实 Intent,并绑定目标 App 的 ClassLoader。 - 于是真正实例化、显示的是虚拟 App 的 Activity,但 AMS 全程以为在跑宿主的 Activity。
详见 Stub Activity 机制。
关键设计:native 层 IO 重定向
数据隔离(每个虚拟 App 独立数据目录)不能只靠 Java 层,因为很多 App 直接走 native 读写 /data/data/<pkg>。VirtualXposed 在 native 层 hook libc 的文件接口,把路径透明重定向:
/data/data/<目标pkg>→ VirtualXposed 私有目录下的虚拟数据目录/sdcard→ 虚拟存储目录- 配合白名单(系统公共目录不重定向)
这部分由 libva++.so 完成,入口是 NativeEngine,详见虚拟存储与 IO 重定向和Native 层。
关键设计:在虚拟进程里加载 Xposed
这是 VirtualXposed 相对原版 VirtualApp 的核心增量。VClientImpl.bindApplicationNoCheck() 在绑定目标 App 的 Application 时:
ExposedBridge.initOnce(context, appInfo, classLoader)—— 初始化 epic 的 ART Hook 引擎。- 遍历所有已安装的 App,把其中是 Xposed 模块的
ExposedBridge.loadModule(...)加载进来。 - 模块用标准 Xposed API(
XposedBridge.findAndHookMethod等)注册钩子,epic 在 ART 层把方法入口改写。
于是模块的 Hook 就挂在了同进程的目标 App 方法上。详见 Xposed 集成。
一张图串联:启动虚拟 App 的完整时序
下面这张时序图把上述所有关键设计串起来——从用户点图标到目标 App 跑起来、Xposed 模块生效的全过程:
阅读路线
建议按这个顺序读,每一篇都建立在前一篇之上: