Android 运行时 (ART) 是运行 Android 5.0(API 级别 21)及更高版本的设备的默认运行时。此运行时提供了一些功能,可提高 Android 平台和应用的性能和流畅度。您可以在 Introducing ART 中找到有关 ART 的新功能的更多信息。
但是,一些在 Dalvik 上可行的技术在 ART 上不可行。本文档让您了解在迁移现有应用以使其与 ART 兼容时需要注意的事项。大多数应用在使用 ART 运行时应该可以正常工作。
解决垃圾回收 (GC) 问题
在 Dalvik 下,应用经常发现显式调用 System.gc()
来提示垃圾回收 (GC) 很有用。在 ART 中,这应该没有那么必要,尤其是如果您调用垃圾回收来防止 GC_FOR_ALLOC
类型的事件发生或减少碎片。您可以通过调用 System.getProperty("java.vm.version")
来验证正在使用的运行时。如果使用的是 ART,则该属性的值为 "2.0.0"
或更高。
ART 使用并发复制 (CC) 收集器,该收集器并发地压缩 Java 堆。因此,您应该避免使用与压缩 GC 不兼容的技术(例如,保存指向对象实例数据的指针)。这对于使用 Java 本地接口 (JNI) 的应用尤其重要。有关更多信息,请参阅 防止 JNI 问题。
防止 JNI 问题
ART 的 JNI 比 Dalvik 的更严格一些。使用 CheckJNI 模式来捕获常见问题是一个特别好的主意。如果您的应用程序使用 C/C++ 代码,您应该查看以下文章
检查 JNI 代码是否存在垃圾回收问题
并发复制 (CC) 收集器可能会在内存中移动对象以进行压缩。如果您使用 C/C++ 代码,请不要执行与压缩 GC 不兼容的操作。我们已经增强了 CheckJNI 以识别一些潜在问题(如 ICS 中的 JNI 本地引用更改 中所述)。
需要特别注意的一个方面是使用 Get...ArrayElements()
和 Release...ArrayElements()
函数。在具有非压缩 GC 的运行时中,Get...ArrayElements()
函数通常返回对实际支持数组对象的内存的引用。如果您对返回的数组元素之一进行了更改,则数组对象本身也会更改(并且 Release...ArrayElements()
的参数通常会被忽略)。但是,如果使用压缩 GC,则 Get...ArrayElements()
函数可能会返回内存的副本。如果您在使用压缩 GC 时错误地使用该引用,则会导致内存损坏或其他问题。例如
- 如果您对返回的数组元素进行了任何更改,则在完成操作时必须调用相应的
Release...ArrayElements()
函数,以确保您所做的更改被正确地复制回底层数组对象。 - 释放内存数组元素时,您必须使用适当的模式,具体取决于您所做的更改
- 如果您没有对数组元素进行任何更改,请使用
JNI_ABORT
模式,该模式会释放内存,而不会将更改复制回底层数组对象。 - 如果您对数组进行了更改,并且不再需要该引用,请使用代码
0
(这会更新数组对象并释放内存副本)。 - 如果您对数组进行了想要提交的更改,并且想要保留数组副本,请使用
JNI_COMMIT
(这会更新底层数组对象并保留副本)。
- 如果您没有对数组元素进行任何更改,请使用
- 调用
Release...ArrayElements()
时,请返回由Get...ArrayElements()
返回的相同指针。例如,将原始指针递增(以扫描返回的数组元素),然后将递增后的指针传递给Release...ArrayElements()
是不安全的。传递此修改后的指针会导致错误的内存被释放,从而导致内存损坏。
错误处理
ART 的 JNI 在 Dalvik 没有抛出错误的多种情况下抛出错误。(再一次,您可以通过使用 CheckJNI 进行测试来捕获许多此类情况。)
例如,如果 RegisterNatives
被调用,而该方法不存在(可能是因为该方法被诸如 ProGuard 之类的工具删除),ART 现在会正确地抛出 NoSuchMethodError
08-12 17:09:41.082 13823 13823 E AndroidRuntime: FATAL EXCEPTION: main 08-12 17:09:41.082 13823 13823 E AndroidRuntime: java.lang.NoSuchMethodError: no static or non-static method "Lcom/foo/Bar;.native_frob(Ljava/lang/String;)I" 08-12 17:09:41.082 13823 13823 E AndroidRuntime: at java.lang.Runtime.nativeLoad(Native Method) 08-12 17:09:41.082 13823 13823 E AndroidRuntime: at java.lang.Runtime.doLoad(Runtime.java:421) 08-12 17:09:41.082 13823 13823 E AndroidRuntime: at java.lang.Runtime.loadLibrary(Runtime.java:362) 08-12 17:09:41.082 13823 13823 E AndroidRuntime: at java.lang.System.loadLibrary(System.java:526)
如果 RegisterNatives
被调用,而没有方法,ART 也会记录一个错误(在 logcat 中可见)
W/art ( 1234): JNI RegisterNativeMethods: attempt to register 0 native methods for <classname>
此外,JNI 函数 GetFieldID()
和 GetStaticFieldID()
现在会正确地抛出 NoSuchFieldError
,而不是简单地返回 null。类似地,GetMethodID()
和 GetStaticMethodID()
现在会正确地抛出 NoSuchMethodError
。这会导致 CheckJNI 失败,因为未处理的异常或异常被抛出到本机代码的 Java 调用者。这使得在 CheckJNI 模式下测试与 ART 兼容的应用程序尤其重要。
ART 期望 JNI CallNonvirtual...Method()
方法(如 CallNonvirtualVoidMethod()
)的用户使用方法的声明类,而不是子类,正如 JNI 规范所要求的那样。
防止堆栈大小问题
Dalvik 对本机代码和 Java 代码有单独的堆栈,默认 Java 堆栈大小为 32KB,默认本机堆栈大小为 1MB。ART 具有统一的堆栈,以提高局部性。通常,ART Thread
堆栈大小应该与 Dalvik 相似。但是,如果您显式设置堆栈大小,则可能需要重新考虑在 ART 中运行的应用程序的这些值。
- 在 Java 中,查看对指定显式堆栈大小的
Thread
构造函数的调用。例如,如果出现StackOverflowError
,则需要增加大小。 - 在 C/C++ 中,查看对
pthread_attr_setstack()
和pthread_attr_setstacksize()
的使用,这些使用针对也通过 JNI 运行 Java 代码的线程。以下是在应用程序尝试调用 JNIAttachCurrentThread()
时记录的错误示例,此时 pthread 大小太小F/art: art/runtime/thread.cc:435] Attempt to attach a thread with a too-small stack (16384 bytes)
对象模型更改
Dalvik 错误地允许子类覆盖包私有方法。ART 在此类情况下会发出警告
Before Android 4.1, method void com.foo.Bar.quux() would have incorrectly overridden the package-private method in com.quux.Quux
如果您打算在不同的包中覆盖类的某个方法,请将该方法声明为 public
或 protected
。
Object
现在具有私有字段。在类层次结构中反映字段的应用程序应该注意不要尝试查看 Object
的字段。例如,如果您正在将类层次结构向上迭代作为序列化框架的一部分,请在
Class.getSuperclass() == java.lang.Object.class
时停止,而不是继续直到该方法返回 null
。
代理 InvocationHandler.invoke()
现在在没有参数的情况下会收到 null
,而不是一个空数组。此行为以前有记录,但在 Dalvik 中未正确处理。以前版本的 Mockito 在使用 ART 进行测试时存在困难,因此请使用更新的 Mockito 版本。
修复 AOT 编译问题
ART 的提前 (AOT) Java 编译应该适用于所有标准 Java 代码。编译由 ART 的 dex2oat
工具执行;如果您在安装时遇到与 dex2oat
相关的任何问题,请告知我们(请参阅 报告问题),以便我们尽快修复这些问题。需要注意的几个问题
- ART 在安装时执行比 Dalvik 更严格的字节码验证。Android 构建工具生成的代码应该没问题。但是,某些后处理工具(尤其是执行混淆的工具)可能会生成无效文件,这些文件被 Dalvik 容忍,但被 ART 拒绝。我们一直在与工具供应商合作以查找和修复此类问题。在许多情况下,获取工具的最新版本并重新生成 DEX 文件可以解决这些问题。
- ART 验证器标记的一些典型问题包括
- 无效的控制流
- 不平衡的
monitorenter
/monitorexit
- 0 长度的参数类型列表大小
- 某些应用程序依赖于安装的
.odex
文件格式,该格式位于/system/framework
、/data/dalvik-cache
或DexClassLoader
的优化输出目录中。这些文件现在是 ELF 文件,而不是 DEX 文件的扩展形式。虽然 ART 尝试遵循与 Dalvik 相同的命名和锁定规则,但应用程序不应依赖于文件格式;该格式可能会在未经通知的情况下更改。注意:在 Android 8.0(API 级别 26)及更高版本中,
DexClassLoader
的优化输出目录已弃用。有关更多信息,请参阅DexClassLoader()
构造函数的文档。
报告问题
如果您遇到任何与应用程序 JNI 问题无关的问题,请通过 Android 开放源代码项目问题跟踪器 https://code.google.com/p/android/issues/list 报告这些问题。如果可用,请包含一个 "adb bugreport"
和指向 Google Play 商店中该应用程序的链接。否则,如果可能,请附加一个可以重现该问题的 APK。请注意,问题(包括附件)是公开可见的。