ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

VirtualApp 虚拟引擎:APK 是如何“不安装就运行“的——3 个关键数据结构与源码走读指南

VirtualApp 虚拟引擎:APK 是如何“不安装就运行“的——3 个关键数据结构与源码走读指南 VirtualApp 虚拟引擎APK 是如何不安装就运行的——3 个关键数据结构与源码走读指南【免费下载链接】VirtualAppVirtual Engine for Android(Support 14.0 in business version)项目地址: https://gitcode.com/GitHub_Trending/vi/VirtualAppVirtualApp简称 VA是一个 Android 虚拟引擎它把 APK 装进一个自建的沙盒迷你 Android里运行解决同一部手机多开 App、隔离数据、免 Root Hook这类真实痛点。这篇文章带你沿着一个 APK 从进入沙盒到跑起来的数据流把 VPackage、VEnvironment、VLocConfig 这 3 个关键数据结构和核心实现逻辑讲透看完你基本能读懂 VA 的主干源码。没有 VA 时会卡在哪一步先说问题。Android 的规则是APK 必须安装进系统、拿到 UID 和/data/data/包名目录才能运行。这就卡死了几个常见需求同一个 App比如微信想登多个账号——系统只允许装一份不想让某个 App 读到你的真实数据或想审计它的行为——系统层没有这个开关想在不 Root 的情况下 Hook 系统调用——没有入口。VA 的思路可以理解为既然不能改系统那就自己伪造一套系统环境把 App 的请求全部拦下来改好参数再转发给真系统返回时再改回来。换句话说App 以为自己安装在系统里其实活在宿主 App 的files目录中。这张图来自项目文档展示了 VA 运行时的进程分工宿主主进程32 位、宿主插件进程64 位各自承载若干 VApp 客户端进程所有不交给系统处理的请求比如安装 App都汇聚到 VA Server 进程。这个分工会反复出现在后面的源码里。弄清了目标接下来我们跟着一份 APK 文件走一遍完整数据流。数据流主线APK 从文件到已安装的三次变身第一站VEnvironment——沙盒里的迷你文件系统一个虚拟 App 的家在哪答案是宿主的files/virtual/目录。VEnvironment是这个迷你 Android 的路径总管它的所有静态方法都在回答同一个问题真实 Android 的某个路径在沙盒里对应哪里。private static File ROOT; private static File DATA_DIRECTORY; private static File USER_DIRECTORY; private static File DALVIK_CACHE_DIRECTORY; static { File host new File(getContext().getApplicationInfo().dataDir); ROOT ensureCreated(new File(host, virtual)); // 对应真实系统的 / DATA_DIRECTORY ensureCreated(new File(ROOT, data)); // 对应 /data/ USER_DIRECTORY ensureCreated(new File(DATA_DIRECTORY, user)); // 对应 /data/user/ DALVIK_CACHE_DIRECTORY ensureCreated(new File(ROOT, opt)); // dex 优化缓存 }它的妙处在于结构复刻不是随便找个目录塞文件而是把真实 Android 的目录层级/data/user/包名、/data/app/包名/base.apk、odex 缓存一比一镜像到沙盒内。这样后面 Native 层的 IO 重定向只需要做前缀替换App 里写死的绝对路径也就能被透明重定向。每个虚拟 App 的 APK 会被拷贝成virtual/data/app/包名/base.apk私有数据放在virtual/data/user/用户ID/包名/下——多开之所以互不干扰根源就在这里每个用户 包名组合有独立的数据目录。路径体系搭好之后接下来是第二站把 APK 变成可查询的结构。第二站VPackage——虚拟世界的安装包身份证系统安装 APK 时会把它解析成PackageInfo。VA 不能碰系统的 PackageManager于是自己写了一个解析器PackageParserEx.java产物就是VPackage——整个引擎里最重要的一张大结构体public class VPackage implements Parcelable { public ArrayListActivityComponent activities; // 所有 Activity public ArrayListServiceComponent services; // 所有 Service public ArrayListProviderComponent providers; // 所有 ContentProvider public ArrayListString requestedPermissions; // 申请的权限 public ApplicationInfo applicationInfo; // 应用元信息 public String packageName; public int mVersionCode; public int mSharedUserLabel; // ... }注意每个组件都是XxxComponent而不是直接用系统的ActivityInfo——VA 给组件额外挂上了自己的元数据比如mExtras上挂着PackageSetting记录是否已安装、是否隐藏、依赖系统包还是沙盒包。VPackage实现了Parcelable这一点至关重要安装动作发生在 VA Server 进程而启动 App 的指令从宿主 UI 进程发起VPackage必须能整体跨进程搬运。解析完成后它还会通过PackageCacheManager缓存、由PackagePersistenceLayer序列化进packages.ini落盘——重启后无需重新解析 APK。结构有了、路径有了那安装到底做了什么看第三站。第三站VAppManagerService.installPackage——把数据落到沙盒里public synchronized InstallResult installPackage(String path, int flags, boolean notify) { File packageFile new File(path); if (!packageFile.exists() || !packageFile.isFile()) { return InstallResult.makeFailure(Package File is not exist.); } VPackage pkg PackageParserEx.parsePackage(packageFile); // 1. 解析 APK → VPackage File appDir VEnvironment.getDataAppPackageDirectory(pkg.packageName); // 2. 沙盒路径 File libDir new File(appDir, lib); if (res.isUpdate) { FileUtils.deleteDir(libDir); VEnvironment.getOdexFile(pkg.packageName).delete(); // 更新时清理旧产物 VActivityManagerService.get().killAppByPkg(pkg.packageName, VUserHandle.USER_ALL); } NativeLibraryHelperCompat.copyNativeBinaries(new File(path), libDir); // 3. 拷 .so // 4. 拷贝 APK 为 base.apk }数据流到这里完成闭环APK 文件 → 解析为 VPackage → 按 VEnvironment 规划的路径落盘APK 原生库 dex 缓存→ 状态写入持久化层。注意它从头到尾没有调用系统的PackageManager.install——系统始终不知道这个 App存在这正是 VA 能任意多开的前提。文件与结构只是静态的真正运转起来靠进程间通信下面走读几处最能说明问题的源码。关键源码走读桥接、代理与持久化的三处设计走读一VEnvironment 的按需建目录前面贴过静态块这里看它的辅助方法private static File ensureCreated(File folder) { if (!folder.exists() !folder.mkdirs()) { VLog.w(TAG, Unable to create the directory: %s., folder.getPath()); } return folder; }设计意图是访问即创建每个getXxxDirectory()调用都走ensureCreated沙盒目录树不是初始化时一次性建好的而是随用随长。这让冷启动足够轻不碰磁盘就没有 I/O也让沙盒结构永远与真实 Android 保持同构——哪怕某个 App 从没写过数据它的数据目录也随时能出现。走读二VirtualCore——宿主进程里的总开关虚拟 App 启动后它运行的其实是宿主进程见开头的进程图。VirtualCore是这个进程内所有 VA 能力的单一入口VirtualCore.javapublic final class VirtualCore { SuppressLint(StaticFieldLeak) private static VirtualCore gCore new VirtualCore(); private String hostPkgName; // 宿主包名 private Object mainThread; // 反射拿到的 ActivityThread private IPCSingletonIAppManager singleton; // 客户端→VA Server 的代理 ... }几个字段各管一段hostPkgName是换装的原料——拦截到 App 请求自己的包名时就替换成宿主包名骗过系统mainThread通过反射拿到系统的ActivityThread私有实例是后续 Hook Activity/Service 生命周期的抓手IPCSingletonIAppManager则是跨进程代理客户端调的每个安装/启动方法最终都变成对 VA Server 的 Binder 调用。客户端与服务器之间这种接口名相同、实现分两侧的命名法VXxxManager在客户端VXxxService在服务端贯穿整个项目是你读码时辨认数据流向的坐标系。走读三VLocConfig——配置类数据的完整生命周期虚拟定位VirtualLocationService.java是 VA 最典型的数据驱动功能看它如何管理每个 App 的定位配置private final SparseArrayMapString, VLocConfig mLocConfigs new SparseArray(); private final VLocConfig mGlobalConfig new VLocConfig(); private static final int MODE_CLOSE 0; private static final int MODE_USE_GLOBAL 1; // 跟随全局 private static final int MODE_USE_SELF 2; // 用 App 自己的VLocConfig内部只有mode VLocation 基站列表但它是Parcelable的且外层 Service 实现了IVirtualLocationManager接口UI 进程设置坐标 → Parcel 序列化 → VA Server 更新mLocConfigs→ Hook 住定位 API 时返回伪造值 → 通过PersistenceLayer写入virtual-loc.ini落盘。一个小小的配置结构走完了UI → IPC → 内存 → 磁盘全链路这也是理解 VA 里所有XxxService数据流路的样板。数据流讲完了最后客观聊聊这套实现好在哪、代价又是什么。权衡与取舍能力换兼容性先说优点。这套设计最聪明的一点是用数据同构换控制透明沙盒目录镜像真实 Android、VPackage 镜像 PackageInfo、VUserHandle 镜像 UserHandle于是 IO 重定向和请求改写都可以做成机械的查表替换新增功能基本是复制一份系统结构 写一个 Service。多开、隔离、审计、虚拟定位全部由此衍生扩展性很好。代价同样明显兼容性是持续的军备竞赛。它靠反射访问ActivityThread、ContextImpl等私有 APIAndroid 每大版本改动都可能让 Hook 点失效——mirror包里成片的ContextImplICS/Kitkat/Oreo版本分支就是证据。开源版代码 2017 年后停止更新只覆盖到当时的系统版本直接跑在新机型上会大量翻车商业版支持更高版本。反射与 Hook 有性能损耗且VPackage整包 Parcel 化搬运大应用首次跨进程查询的开销不小用PackageCacheManager缓存缓解。安全边界由沙盒自己承担虚拟 App 共享宿主的网络与部分资源隔离并非操作系统级的硬隔离。知道边界之后按下面这条路径读源码两天内能建立完整的心智模型。上手路径五天读码计划先读总纲通读 README.md 的VA技术架构与VA进程架构两节配合上面两张架构图确认自己理解宿主/插件/Server 三类进程各自管什么。跟目录走打开 VEnvironment.java从头到尾看一遍所有getXxx方法在纸上画出沙盒目录树——这是全项目的地图。追一条安装流从 VAppManagerService.java 的installPackage出发顺藤摸到 PackageParserEx.java 和PackagePersistenceLayer确认解析 → 落盘 → 持久化三步。看跨进程通信对比 VirtualCore.java客户端与server/interfaces/下的 AIDL 定义理解IPCSingleton如何把本地方法调用变成 Binder 调用。读一个完整功能以虚拟定位为例从app/模块的home/location/VirtualLocationSettings.java一路读到 VirtualLocationService.java验证UI → IPC → 内存 → 磁盘数据流再回头看 hook 目录 里定位相关的 Hook闭环就完整了。把这条数据流刻进脑子后VA 里新增任何一个虚拟 XX 服务你都能预判它的代码会出现在哪几个目录——这大概就是读架构源码最实用的回报。【免费下载链接】VirtualAppVirtual Engine for Android(Support 14.0 in business version)项目地址: https://gitcode.com/GitHub_Trending/vi/VirtualApp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表