简介
Apply Changes 是 Android Studio 中的一项功能,我们在 Android Studio 3.5 中引入了这项功能,以帮助开发者快速迭代您对应用所做的更改。Apply Changes 通过 JVMTI API 来判断是否可以使用此方式进行变更。在 Android 11 上,ART (Android 运行时) 扩展了 JVMTI API,引入了一个名为 Structural Class Redefinition (类的结构性重定义) 的新功能。该功能使 Apply Changes 在 Android 11 设备上增加了一类新的应用场景。现在,可以使用 Apply Changes 将更复杂的修改快速部署到正在运行的应用上,这包括:
- 增加方法 (Android Studio 4.1)
- 增加资源文件 (Android Studio 4.2)
- 增加静态字段 (Android Studio 4.2)
这可以使您减少研发周期,最大化生产效率。本文我们将探讨在 Android Studio 中该功能是如何实现的。
通过 Android Studio 实现更强的功能
Apply Changes 基于 Android Runtime 特性从头设计,所以可以利用其升级更新的功能不断发展。
对于类的结构性重定义而言,将具有新增方法的类发送到 ART,这与之前的 Android 版本没有什么不同。如今新增了一个入口 API,为此您需要将 Android Studio 升级到 4.1 或更高版本,以利用动态在运行中添加新方法的优势,包括静态方法和虚方法。
但是,增加变量需要在 Android Studio 中进行新的分析。当增加一个新的变量时,ART 不会尝试为其分配具体的值。(请持续关注后续关于 ART 实现类的结构性重定义的文章)。取而代之的是,被增加的变量仅会被初始化为默认初始值或 null,并且如何初始化将由 Android Studio 决定。
此过程较为复杂,考虑这样一种情况: 将 long 类型静态变量 y 添加到类中 (y 的初始化发生在类加载期间)。例如:
代码语言:javascript复制public class example {
public final static long x = System.currentTimeMillis();
public final static long y = System.currentTimeMillis();
}
复制代码
如果该类被加载,x 和 y 的值将会非常接近。在通过使用 Apply Code Changes 增加 y 的情况下,很难计算出正确的 y 值。事实上对 y 的赋值,即使采用最接近的模拟类加载和初始化 y 的程序,也是有争议的。因为两个 curentTimeMillis()
在静态初始化 ( 方法) 中调用,Apply Changes 将继续遵守不重新执行 方法任何部分的策略,所以新增的 y 值为 0。
幸运的是,Apply Changes 已经 使用了 D8 分析 DEX 文件,并且作为该过程的一部分,在最新版本的 Android Studio 中,Apply Changes 能够利用 D8 新引入的 Inspector API。这种轻量级的检查 API 能够在 DEX 比较过程中计算出一些额外的信息,而仅需增加少量开销 (仅检查发生修改的 Java 类)。一系列有关新增变量的元信息将被附加在发送到对应设备的 Apply Changes 请求的 ProtoBuf 消息中。
在设备上,Android Studio 将我们的更改传达给 VM 之前,Java Agent 将检查即将被替换的当前加载类。通过比较当前加载类和新编译类的字段,即可计算出新增字段列表及每个字段的初始值。然后,代理程序将暂时挂起所有其他线程,防止未初始化的新增字段在替换前被访问。如果替换请求成功执行,它将使用合适的变量初始化新增字段。
局限与即将推出的新功能
在 Android Studio 4.2 Canary 3 中,此功能仅支持新增静态原语的应用场景。作为衍生功能,这有助于在 R.class 中新增值,使 Apply Changes 支持新增资源。
对于所有使用 Apply Changes 的场景中,需要记住一点: 当您重新编译并重新运行一个程序,任何语义和之前都是不同的。试想这样的一个例子: 构造函数发生了变化,但是所有基于原来的构造函数初始化的对象并没有重新初始化。同样的,该规则也适用于静态变量,因为 不会被重新调用。
希望 Android Studio 中这一新功能可以为开发者带来生产力的提高。我们一如既往地欢迎大家给我们 反馈,并让我们知道您希望看到哪些改进。