VirtualApp 开发实战指南:源码架构、SDK 接入与 Hook 能力全解析
2026/9/15 22:27:48 网站建设 项目流程

VirtualApp 开发实战指南:源码架构、SDK 接入与 Hook 能力全解析

【免费下载链接】VirtualAppVirtual Engine for Android(Support 14.0 in business version)项目地址: https://gitcode.com/GitHub_Trending/vi/VirtualApp

VirtualApp(VA)是一个面向 Android 的虚拟化引擎框架,本文以仓库内 doc/VADev.md 为基础开发文档为主线,先拆解 VA 的源码目录结构与核心包职责,再完整讲解从工程接入、安装/启动/卸载 App,到 Java Hook(Xposed 风格)与 Native Hook(Cydia Substrate 风格)的实战用法。读完本文,你将掌握 VA 框架的进程模型、四大源码目录的作用、VirtualCoreVActivityManager的核心 API 调用方式,并能独立完成一个基于 VA 的多开/虚拟化应用接入。


1. VA 源码结构介绍

1.1 源码目录一览

VA 工程源码根目录下共有 4 个源码目录,各自职责如下:

目录名称作用
appVA Demo 主包源码所在目录
app-extVA Demo 插件包源码所在目录
libVA 库源码所在目录
lib-extVA 插件库源码所在目录

在本仓库中,对应结构即VirtualApp/app(Demo 主包)与VirtualApp/lib(核心库),其中lib内含src/main/java/com/lody/virtual/...的全部框架代码、src/main/aidl的 AIDL 接口以及src/main/jni的 Native 层实现。app-extlib-ext属于商业版 VA 的插件包体系,当前开源仓库未包含对应目录。

1.2 编译配置文件 VAConfig.gradle

VA 的编译配置集中在VAConfig.gradle中,开发者接入时需要根据自身业务修改以下配置项:

配置名称作用
PACKAGE_NAME配置 VA 主包的包名
EXT_PACKAGE_NAME配置 VA 插件包的包名
VA_MAIN_PACKAGE_32BIT配置 VA 主包是 32 位还是 64 位,true为 32 位,false为 64 位
VA_ACCESS_PERMISSION_NAME配置 VA 中四大组件(Activity/Service/Receiver/Provider)的权限名称
VA_AUTHORITY_PREFIX配置 VA 主包中 ContentProvider 的authorities
VA_EXT_AUTHORITY_PREFIX配置 VA 插件包中 ContentProvider 的authorities
VA_VERSION配置 VA 库版本,开发者一般不需要关心
VA_VERSION_CODE配置 VA 库版本代码,开发者一般不需要关心

注意:VA_AUTHORITY_PREFIXVA_EXT_AUTHORITY_PREFIX在设备上全局唯一,不能与其他已安装 App 的 authority 重复,否则会导致 ContentProvider 冲突。

1.3 核心代码包解析

VA 框架的核心逻辑分布在几个关键包中,理解这些包的进程归属与职责,是读懂 VA 架构的第一步:

(1)com.lody.virtual.client包 —— VAPP Client 进程运行在 VAPP(Virtual App)Client 进程中,主要用于 VA Framework 中的 App Hook 部分,完成对系统各个 Service 的 Hook 处理。从源码结构看,该包下包含hook/proxies(对ActivityManagerPackageManagerClipboardMountService等系统服务的 Hook 代理,例如VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/ActivityManagerStub.java)、ipcVActivityManager等客户端 IPC 封装)以及NativeEngine.java等入口。

(2)com.lody.virtual.server包 —— VA Server 进程运行在 VA Server 进程中,主要用于 VA Framework 中的 App Server 部分,实现 App 安装以及其他不交给 Android 系统处理的 App 请求。该包内含interfaces(AIDL 接口定义,如IActivityManager)、pm(包管理)、am(Activity 管理)、accountsnotification等子模块,对应VirtualApp/lib/src/main/java/com/lody/virtual/server/目录。

(3)mirror包 —— 系统隐藏类引用mirror包下的代码主要用于对系统隐藏类的引用,属于工具类,用于减少大量反射代码的编写。仓库中VirtualApp/lib/src/main/java/mirror/目录下对android.app.ActivityThreadandroid.content.pm.PackageParserandroid.os.ServiceManager等系统内部类做了镜像封装,配合RefClass/RefMethod/RefStaticObject等注解式反射基类使用。

(4)cpp包 —— VA Native 部分运行在 VAPP Client 进程中,主要用于 VA Native 部分,实现 IO 重定向和 JNI 函数 Hook。其中:

  • substrate中实现了针对 arm32 和 arm64 的 Hook;
  • vfs.cpp中实现了 VA 的虚拟文件系统,用于控制 App 文件访问限制;
  • syscall_hook.cpp中实现了对 IO 的 Hook。

对应到本仓库,Native 层位于VirtualApp/lib/src/main/jni/,其中Substrate/目录(含CydiaSubstrate.hSubstrateHook.cpp等)实现跨架构 inline hook,Foundation/目录(含IOUniformer.cppSandboxFs.cppVMPatch.cpp等)实现 IO 重定向与符号查找,HookZz/目录提供另一套跨平台 Hook 引擎。

(5)DelegateApplicationExt.java—— VA Host Plugin 进程运行在 VA Host Plugin 进程中,用于 VA 插件包,实现主包代码的加载执行。它属于商业版插件体系的宿主代理 Application(开源版中与之对应的是com.lody.virtual.DelegateApplication64Bit,见VirtualApp/lib/src/main/java/com/lody/virtual/DelegateApplication64Bit.java)。


2. VA SDK 使用介绍

2.1 VA 工程接入

用 Android Studio 打开项目

将 VirtualApp-Priv 项目用 Android Studio 打开后,可见多个模块:

  • app
  • app-ext
  • lib
  • lib-ext

其中liblib-ext属于 VirtualApp核心库以及扩展库appapp-ext则属于示例 App。对本仓库而言,VirtualApp/app是可直接运行的 Demo,VirtualApp/lib是要依赖的核心库。

创建自己的 App

新建一个application类型的 module,并添加lib模块为依赖:

implementation project(':lib')
根据需求修改 VAConfig.gradle
ext { VA_MAIN_PACKAGE_32BIT = true // 主包为32位 VA_ACCESS_PERMISSION_NAME = "io.busniess.va.permission.SAFE_ACCESS" // VirtualApp组件用到的权限名称 VA_AUTHORITY_PREFIX = "io.busniess.va" // VirtualApp中ContentProvider用到的authority,不能与其他app重复 VA_EXT_AUTHORITY_PREFIX = "io.busniess.va.ext" // VirtualApp扩展包中ContentProvider用到的authority,不能与其他app重复 // ... }
在 AndroidManifest.xml 添加所需权限
<uses-permission android:name="${VA_ACCESS_PERMISSION_NAME}" />

权限名称必须与VAConfig.gradle中所声明的保持一致。可以在build.gradle中添加Placeholder来防止出错:

android { // ... manifestPlaceholders = [ VA_ACCESS_PERMISSION_NAME: rootProject.ext.VA_ACCESS_PERMISSION_NAME, ] }

2.2 创建 Application 并初始化 VirtualApp

复写attachBaseContext,添加引导 VirtualApp 的代码

VA 需要在attachBaseContext阶段完成早期初始化(此时onCreate尚未执行):

@Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); try { VirtualCore.get().startup(base, mConfig); } catch (Throwable e) { e.printStackTrace(); } }

这里传入了一个SettingConfig配置对象mConfig

private SettingConfig mConfig = new SettingConfig() { @Override public String getMainPackageName() { // 主包的包名 return BuildConfig.APPLICATION_ID; } @Override public String getExtPackageName() { // 扩展包包名 return BuildConfig.EXT_PACKAGE_NAME; } @Override public boolean isEnableIORedirect() { // 是否启用IO重定向,建议开启 return true; } @Override public Intent onHandleLauncherIntent(Intent originIntent) { // 回到桌面的 Intent 拦截操作,这里把回到桌面的动作改成回到主包的BackHomeActivity页面 Intent intent = new Intent(); ComponentName component = new ComponentName(getMainPackageName(), BackHomeActivity.class.getName()); intent.setComponent(component); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return intent; } @Override public boolean isUseRealDataDir(String packageName) { // data路径模拟真实路径格式,需要启用IO重定向。部分加固会校该验路径格式 return false; } @Override public boolean isOutsidePackage(String packageName) { // 是否是外部app。 设置外部 app 对内部app看见 return false; } @Override public boolean isAllowCreateShortcut() { // 是否允许创建桌面快捷图标。建议关闭(false),自己实现桌面快捷方式 return false; } @Override public boolean isHostIntent(Intent intent) { // 是否由VirtualApp处理的Intent return intent.getData() != null && "market".equals(intent.getData().getScheme()); } @Override public boolean isUseRealApkPath(String packageName) { // 安装apk路径模拟真实路径,需要启用IO重定向。部分加固会校验该路径格式 return false; } @Override public boolean isEnableVirtualSdcardAndroidData() { // 启用外置存储下的 `Android/data` 目录的重定向 // 需要重定向支持 // Android 11 之后必须启用!! return BuildCompat.isR(); } @Override public String getVirtualSdcardAndroidDataName() { // 设置外置存储下的 `Android/data` 目录的重定向路径 // /sdcard/Android/data/com.example.test/ ==>> /sdcard/{VirtualSdcardAndroidDataName}/{user_id}/Android/data/com.example.test/ return "Android_va"; } @Override public FakeWifiStatus getFakeWifiStatus() { // 修改wifi信息。 null 则不修改 return null; } @Override public boolean isHideForegroundNotification() { // 隐藏前台消息,不建议隐藏 return false; } @Override public boolean isOutsideAction(String action) { // 外部 Intent 的 action 事件响应 return MediaStore.ACTION_IMAGE_CAPTURE.equals(action) || MediaStore.ACTION_VIDEO_CAPTURE.equals(action) || Intent.ACTION_PICK.equals(action); } @Override public boolean isDisableDrawOverlays(String packageName) { // 禁用 VAPP 的顶层覆盖(浮窗)。 return false; } };

关键配置项速查:

配置项默认建议说明
isEnableIORedirecttrue是否启用 IO 重定向,建议开启
isUseRealDataDirfalsedata 路径模拟真实路径格式,需配合 IO 重定向;部分加固会校验路径格式
isUseRealApkPathfalse安装 apk 路径模拟真实路径,需配合 IO 重定向
isAllowCreateShortcutfalse建议关闭,自行实现桌面快捷方式
isEnableVirtualSdcardAndroidDataBuildCompat.isR()外置存储Android/data重定向,Android 11 之后必须启用
getVirtualSdcardAndroidDataName"Android_va"重定向目录名,规则:/sdcard/{VirtualSdcardAndroidDataName}/{user_id}/Android/data/<pkg>/
getFakeWifiStatusnull修改 WiFi 信息,null表示不修改
isHideForegroundNotificationfalse隐藏前台通知,不建议隐藏
isOutsideAction拍照/录像/PICK外部 Intent 的 action 事件响应

其中BuildCompat.isR()对应仓库 BuildCompat.java 中的系统版本判断逻辑,用于在 Android 11(API 30)及以上自动开启Android/data重定向。

复写onCreate,添加初始化 VirtualApp 的代码
@Override public void onCreate() { super.onCreate(); VirtualCore virtualCore = VirtualCore.get(); virtualCore.initialize(new VirtualCore.VirtualInitializer() { @Override public void onMainProcess() { // 主进程回调 } @Override public void onVirtualProcess() { // 虚拟App进程回调 } @Override public void onServerProcess() { // 服务端进程回调 } @Override public void onChildProcess() { // 其他子进程回调 } }); }

由于 VirtualApp 会启动多个进程,Application 会进入 N 次,不同的进程会走到VirtualInitializer不同的回调,可以在这里根据进程类型添加额外的初始化代码。四个回调对应四类进程:主进程(宿主自身)、虚拟 App 进程(VAPP,被 Hook 的插件运行进程)、服务端进程(VA Server)以及其余子进程。


3. 安装 APP

3.1 安装 API 与 Uri 来源

安装 App 的核心 API 定义在VirtualCore中:

VirtualCore.java public VAppInstallerResult installPackage(Uri uri, VAppInstallerParams params);

(开源版VirtualCore中对应的底层方法为installPackage(String apkPath, int flags),见 VirtualCore.java,商业版在VAppInstallerParams上扩展了更丰富的安装参数。)

Uri决定了需要安装的 apk 的来源,目前支持packagefile两种协议。

Package Uri 示例
Uri packageUri = Uri.parse("package:com.hello.world");
File Uri 示例
File apkFile = new File("/sdcard/test.apk"); Uri packageUri = Uri.fromFile(apkFile);

3.2 两种 Uri 安装 App 有何区别

package协议安装 app,只需要传入包名,不需要具体的 APK 路径。以这种协议安装的 app,相当于双开:app 会随外部版本的升级而自动升级,随外部版本的卸载而自动卸载。PackageSetting中的dynamictrue

file协议则是内部安装,apk 会被复制到容器内部,与外部版本完全独立。PackageSetting中的dynamicfalse

3.3 安装参数 VAppInstallerParams

安装标志 installFlags
FLAG说明
FLAG_INSTALL_OVERRIDE_NO_CHECK允许覆盖安装
FLAG_INSTALL_OVERRIDE_FORBIDDEN禁止覆盖安装
FLAG_INSTALL_OVERRIDE_DONT_KILL_APP覆盖安装不 kill 已经启动的 APP
安装模式 mode
FLAG说明
MODE_FULL_INSTALL完整安装
MODE_INHERIT_EXISTING已安装的安装模式。预留

mode 为预留参数,暂时未使用,目前不管设置哪种都一样。

cpuAbiOverride

指定 app 的 abi。特殊需求下,可以强制指定 app 在指定 abi 下运行;不指定时默认根据系统规则来决定运行的 abi。可选参数:

  • armeabi
  • armeabi-v7a
  • arm64-v8a
双开 app 实例代码
VAppInstallerParams params = new VAppInstallerParams(VAppInstallerParams.FLAG_INSTALL_OVERRIDE_NO_CHECK); VAppInstallerResult result = VirtualCore.get().installPackage(Uri.parse("package:com.tencent.mobileqq"), params); if (result.status == VAppInstallerResult.STATUS_SUCCESS) { Log.e("test", "install apk success."); }
从 sd 卡安装 apk 实例代码
VAppInstallerParams params = new VAppInstallerParams(VAppInstallerParams.FLAG_INSTALL_OVERRIDE_NO_CHECK); VAppInstallerResult result = VirtualCore.get().installPackage(Uri.fromFile(new File("/sdcard/test.apk")), params); if (result.status == VAppInstallerResult.STATUS_SUCCESS) { Log.e("test", "install apk success."); }
安装 Split apk

先安装 base 包,然后再安装所有 split 包即可:

File dir = new File("/sdcard/YouTube_XAPK_Unzip/"); VAppInstallerParams params = new VAppInstallerParams(VAppInstallerParams.FLAG_INSTALL_OVERRIDE_NO_CHECK); VAppInstallerResult result = VirtualCore.get().installPackage( Uri.fromFile(new File(dir,"com.google.android.youtube.apk")), params); for (File file : dir.listFiles()) { String name = file.getName(); if (name.startsWith("config.") && name.endsWith(".apk")) { result = VirtualCore.get().installPackage( Uri.fromFile(file), params); } }

4. 启动及管理 Application

4.1 启动 App

// class VActivityManager public boolean launchApp(final int userId, String packageName)

实例代码:

VActivityManager.get().launchApp(0, "com.tencent.mobileqq");

在开源版中,VActivityManager位于 VActivityManager.java,通过getService()向 VA Server 侧的IActivityManager发起 IPC 调用完成启动。

4.2 杀死 App

// class VActivityManager public void killAppByPkg(String pkg, int userId) public void killAllApps()

实例代码:

// 杀死userid为0的QQ程序进程 VActivityManager.get().killAppByPkg("com.tencent.mobileqq", 0);
// 杀死所有App进程 VActivityManager.get().killAllApps();

开源版中对应实现见 VActivityManager.java(killAllApps)与 VActivityManager.java(killAppByPkg)。

4.3 卸载 App

// class VirtualCore public boolean uninstallPackageAsUser(String pkgName, int userId) public boolean uninstallPackage(String pkgName)

实例代码:

// 卸载userid为0的QQ程序 VirtualCore.get().uninstallPackageAsUser("com.tencent.mobileqq", 0); // 卸载所有user下安装的QQ程序 VirtualCore.get().uninstallPackage("com.tencent.mobileqq");

开源版对应实现见 VirtualCore.java。

4.4 查询已安装的 App

// class VirtualCore public List<InstalledAppInfo> getInstalledApps(int flags)

开源版还提供了按用户查询的变体getInstalledAppsAsUser(int userId, int flags),见 VirtualCore.java。InstalledAppInfo定义于VirtualApp/lib/src/main/java/com/lody/virtual/remote/InstalledAppInfo.java,包含包名、安装路径、用户信息等字段。


5. Java Hook 使用

VirtualApp 中实现了一套 Xposed 风格的接口,用户只要会使用 Xposed 就可以做到原本需要系统内置 Xposed 才能做到的事情。但需要明确:VA 中 Xposed 的作用域是 VA 这个 App 中的,不能越权控制系统或其他外部 App。

5.1 AppCallback 接口

VA 中提供了一个 App 创建启动的回调接口com.lody.virtual.client.core.AppCallback

public interface AppCallback { void beforeStartApplication(String packageName, String processName, Context context); void beforeApplicationCreate(String packageName, String processName, Application application); void afterApplicationCreate(String packageName, String processName, Application application); }

接口说明:

名称说明
beforeStartApplicationAPP 启动之前,创建之后
beforeApplicationCreateAPP 被创建之前,Application 已经准备完毕,Application.onCreate 未执行
afterApplicationCreateAPP 被创建之后,Application.onCreate 已被执行

参数说明:

名称说明
packageNameVAPP 的包名
processNameVAPP 的进程名
contextVAPP 的 Application context
applicationVAPP 的 Application

注:APP 的创建指的是Application被创建。

5.2 注册 AppCallback

结合前面的进程回调知识:只需要在VAPP 进程回调onVirtualProcess)里设置 App 回调AppCallback即可:

@Override public void onCreate() { super.onCreate(); VirtualCore virtualCore = VirtualCore.get(); virtualCore.initialize(new VirtualCore.VirtualInitializer() { @Override public void onVirtualProcess() { // 设置VAPP启动回调 virtualCore.setAppCallback(new MyComponentDelegate()); } }); }

5.3 使用 Xposed API 进行 Hook

MyComponentDelegate实现AppCallback,并在beforeApplicationCreate阶段使用XposedHelpers进行方法 Hook:

public class MyComponentDelegate implements AppCallback { @Override public void beforeStartApplication(String packageName, String processName, Context context) { } @Override public void beforeApplicationCreate(String packageName, String processName, Application application) { XposedHelpers.findAndHookMethod("android.app.ContextImpl", ClassLoader.getSystemClassLoader(), "getOpPackageName", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) { VLog.printStackTrace("getOpPackageName"); param.setResult(VirtualCore.get().getHostPkg()); } }); } @Override public void afterApplicationCreate(String packageName, String processName, Application application) { } }

上面示例中已经展示了一个 Xposed 使用案例:通过XposedHelpers.findAndHookMethod定位android.app.ContextImpl#getOpPackageName,并在beforeHookedMethod中把返回值替换为宿主包名。

关于入口:Xposed 的入口是一个IXposedHookLoadPackage的实例,它提供了一个void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)接口,参数为XC_LoadPackage.LoadPackageParam。在 VA 中虽然不能与系统 Xposed 完全一一对应,但已完全够用。loadPackageParam.classLoader可以用context.getClassLoader()application.getClassLoader()代替。后续XposedHelpersXposedBridge原本怎么用,在 VA 里也一样使用。

结合仓库源码:VA 的 Xposed 兼容层位于VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/目录下,包含XposedHelpers等工具与 Hook 代理实现,可直接在 VAPP 进程内生效。


6. Native Hook 使用

对于 ARM 32 和 ARM 64 的 Hook,只需要引入头文件CydiaSubstrate.h即可:

MSHookFunction(Type_ *symbol, Type_ *replace, Type_ **result)

参数说明:

名称说明
symbol要 Hook 的地址
replace你自定义的 hook 函数
result被 hook 函数的备份

参考syscall_hook.cpp代码:

auto is_accessible_str = "__dl__ZN19android_namespace_t13is_accessibleERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE"; void *is_accessible_addr = getSym(linker_path, is_accessible_str); if (is_accessible_addr) { MSHookFunction(is_accessible_addr, (void *) new_is_accessible,(void **) &orig_is_accessible); }

MSHookFunction内部会自动判断当前是 ARM32 还是 ARM64:

_extern void MSHookFunction(void *symbol, void *replace, void **result) { if (*result != nullptr) { return; } // ALOGE("[MSHookFunction] symbol(%p) replace(%p) result(%p)", symbol, replace, *result); #ifdef __aarch64__ A64HookFunction(symbol, replace, result); #else SubstrateHookFunction(NULL, symbol, replace, result); #endif }

结合仓库源码:以上 Native Hook 能力对应VirtualApp/lib/src/main/jni/Substrate/目录中的实现,其中 CydiaSubstrate.h 声明了跨架构 Hook API,SubstrateHook.cpp 提供SubstrateHookFunction底层实现;IO 重定向与虚拟文件系统对应VirtualApp/lib/src/main/jni/Foundation/目录(IOUniformer.cppSandboxFs.cpp)。整个 JNI 层的注册入口在VirtualApp/lib/src/main/jni/Jni/VAJni.cpp


7. 总结

VA 的开发可以概括为一条主线:先用VAConfig.gradle完成包名、authority、权限等编译期配置,再在宿主ApplicationattachBaseContext中调用VirtualCore.get().startup()引导框架、在onCreate中通过VirtualInitializer按进程类型初始化,随后用VirtualCore.installPackage安装(package协议双开 /file协议独立安装)、用VActivityManager启动与管控进程,最后按需在 VAPP 进程内通过AppCallback+ Xposed 兼容 API 做 Java Hook,或通过MSHookFunction做 Native Hook。

四个源码目录(app/app-ext/lib/lib-ext)、两个进程角色(VAPP Client 与 VA Server)、两类 Hook(Java Xposed 风格与 Native Substrate 风格),构成了 VA 框架最核心的开发知识体系。本文所有 API 与配置均可在本仓库VirtualApp/libVirtualApp/app中找到对应源码作为参考。

【免费下载链接】VirtualAppVirtual Engine for Android(Support 14.0 in business version)项目地址: https://gitcode.com/GitHub_Trending/vi/VirtualApp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询