Jugg JVMTI Agent
Android Studio Apply Changes 依赖 JVMTI Agent,把结构未变化的 class 实现替换进正在运行的 App。run-as 可完整执行、UID 位于 10000..19999,且新建文件与 App 既有缓存目录使用相同 SELinux label 的 App 继续使用这条通道。前提不成立、但 Jugg 能完整访问 sandbox 时,会复用自己的 JVMTI Agent 完成在线替换;同一个 Agent 也继续负责启动时加载 overlay、检测 JVMTI 和修复已知运行时问题。
Apply Changes 为什么还需要 Jugg Agent
把这些功能直接合入 Apply Changes Agent 也是可行的,但这意味着 Jugg 需要同时接管 Agent 分发、class 在线替换、部署通信以及不同 Android Studio 和设备版本的适配,也就是完整管理整条部署链路。
Jugg Agent 有 startup 和 dynamic attach 两个入口。startup 入口检测当前 App 进程能否取得 JVMTI、加载持久化 overlay,并为已知运行时问题安装修正 hook。dynamic attach 入口处理本次 Direct app sandbox 的 class redefine、资源刷新和 Activity 重建请求。满足 Apply Changes 前提的 App 仍继续使用 Android Studio 的部署实现。
run-as 不兼容 App 如何在线替换
Jugg 先把本次 Dex 写入 code_cache/.overlay,再把请求和请求级 Agent so 放入 code_cache/jugg_hot_reload,按主进程 PID 执行 dynamic attach。Agent 找到已加载且可修改的目标 class 后,一次调用 JVMTI RedefineClasses。
通过 root 或 su 写入时,普通 App 数据文件需要恢复为既有 code_cache 的动态 SELinux label;Agent .so 还必须使用 App 进程可以执行的 apk_data_file 类型。仅恢复 owner,或把 .so 标为普通 app_data_file,都可能在 startup 或 dynamic attach 时得到 Permission denied。
在线替换成功时不重建 Activity;失败、超时、class 未加载或不可修改时,Jugg 重启整个 App。重启后 startup 入口加载前面已经提交的 overlay,因此不需要重新编译或生成另一份 Hot Fix payload。
Direct sandbox 部署也支持资源和 assets。Android 11+ 会在运行中的主进程更新宿主资源,并重建该进程全部存活 Activity,包括其它任务栈和多窗口实例;刷新失败时再重启 App,由 startup 入口加载已经提交的 overlay。WebView 等非宿主资源环境保持原状。Android 8~10 需要重启进程,兼容部署继续使用已有资源 APK 机制。
Direct dynamic attach 当前只处理一个主进程,并重建该进程全部存活 Activity。独立进程不会在同一轮获得在线 class 或资源刷新,需要在对应进程重启后加载 overlay。Direct 部署还会整批提交本轮变化,不提供官方 Apply Changes 的切片进度和分片重试。
检测 JVMTI 并修复已知问题
系统加载 Jugg startup agent 后,它先尝试取得 JVMTI 和 JNI,并将结果记为可用或不可用;App 尚未启动或结果尚未生成时,状态保持未确定。
取得 JVMTI 后,Agent 会对 Application、Resources、ClassLoader 等 Framework 入口安装 hook,只在命中已知问题时改变行为:
- 部分系统提前初始化 ClassLoader,导致已下发的增量 DEX 没有进入加载路径时,App runtime 会在启动阶段补入对应 DEX。
- Apply Changes 把宿主 App 的资源 overlay 带入 WebView provider 等非宿主资源环境时,provider 初始化可能触发
IllegalStateException: Already registered a list of actions in this process。Agent hook 会从这些资源环境中移除宿主 overlay,同时保留宿主 App 自己的资源更新。 - Android 15 与较旧 Android Studio 组合没有完整刷新资源和 Activity 时,Agent hook 会补齐资源更新通知和需要的 Activity 重建。
这些修正相互独立。某个系统版本不存在对应 Framework 类或单个 hook 安装失败时,其它可用修正仍会继续执行。DEX、资源和 Application 的完整处理方式见App 进程内 Jugg runtime。
为什么必须部署后推送、重启后检测
Apply Changes 首次准备自己的 startup agent 时可能清理 App 中已有的 startup agent。如果 Jugg 提前推送,后续部署动作可能把它删除。因此 Jugg 先完成 Apply Changes 或其它增量部署,再检查并补齐自己的 Agent。
JVMTI 检测必须等到 App 重启。startup agent 由系统在进程启动时加载,Activity 重建或在旧进程中等待都不会触发这次初始化。本轮没有重启时,检测会留到下一次 App 进程启动后完成。
检测结果如何改变部署路径
App 重启并产生检测结果后,Jugg 部署链路按实际状态继续处理:
App 启动并加载 Jugg startup agent
-> 检测 JVMTI
-> 可用:继续使用普通在线替换路径
-> 不可用:记录当前 app/device 组合
-> 使用本轮增量产物重新尝试兼容部署自动探测记录按 app/device 组合生效。同一台设备上的另一个 App 不会因为这条记录自动进入兼容部署;用户手动开启的设备兼容模式则按设备生效。两者的选择范围不同。