Verifying App Behavior on the Android Runtime (ART)

原文链接

在Android 5.0(API level 21)及以上的Android设备上Android runtime(ART)是默认的运行时。ART提供了很多新特性去提升Android平台以及APP的性能和流畅度。你可以在这里找到关于ART的新特性Introducing ART

但是一些技术可以在Dalvik上工作却不能在ART上工作。本文档让你了解将现有应用迁移为与ART兼容时需要注意的事项。大多数应用只能在ART运行时工作。

解决GC问题

在Dalvik下,App频繁的直接调用System.gc()可以及时的做GC。但在ART下这样做没有必要,特别是在你调用GC以防止GC_FOR_ALLOC类型的出现或减少碎片。你可以通过调用System.getProperty(“java.vm.version”)来验证正在使用的运行时,如果使用ART,则该属性的值为2.0.0或更高。

此外,在Android开源项目(AOSP)中正在开发紧凑型垃圾收集器以改进内存管理。因此,你应该避免使用与紧凑型垃圾回收不兼容的技术,比如保存指向对象实例的数据。这点对于使用JNI的APP非常重要。更多信息可以查看下面防止JNI问题

防止JNI问题

ART的JNI比Dalvik的更严格。使用CheckJNI模式捕获常见问题是一个很好的方法。 如果你的APP使用C/C++代码,你应该阅读这篇文章:使用CheckJNI调试Android JNI

检查JNI代码的GC问题

有一个ART的紧凑型垃圾回收器正在AOSP中开发。一旦该紧凑型垃圾回收器投入使用,对象就可以在内存中移动。如果你使用C/C++代码,不要执行与紧凑型垃圾回收不兼容的操作,我们增强了CheckJNI以识别一些潜在的问题(如ICS中JNI局部引用更改中所述

一个值得注意的地方是使用Get...ArrayElements()Release...ArrayElements()函数。在没有紧凑型GC的运行时,Get...ArrayElement()函数通常返回对支持数组对象的实际内存的引用。如果对一个返回的数组元素进行更改,数组对象本身就会改变(并且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

1
2
3
4
5
6
7
8
9
10
11
12
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中可见):

1
2
W/art     ( 1234): JNI RegisterNativeMethods: attempt to register 0 native
methods for <classname>

此外,JNI函数GetFieldID()GetStaticFieldID()现在抛出了NoSuchFieldError,而不是简单地返回null。类似地,GetMethodID()GetStaticMethodID()现在抛出了NoSuchMethodError。这可能导致CheckJNI失败,因为未处理的异常或异常被抛出到native code的Java调用者。这使得使用CheckJNI模式测试与ART兼容的应用程序变得尤为重要。

ART期望使用JNI CallNonvirtual...Method()方法的用户(例如CallNonvirtualVoidMethod())使用方法的声明类,而不是JNI规范所要求的子类。

防止堆栈大小问题

Dalvik对于native和Java代码有各自单独的堆栈,默认的Java堆栈大小是32k,native堆栈大小是1M。ART有一个统一的堆栈以改善局部性。通常,ART线程堆栈大小应该与Dalvik大致相同。但是,如果你明确的设置堆栈大小,则可能需要针对ART中运行的应用重新访问这些值。

  • 在Java中,检查Thread构造函数的调用以指定显式堆栈大小。例如,如果发生StackOverflowError就需要增加大小。
  • 在C/C++中,检查通过JNI运行Java代码的线程使用pthread_attr_setstack()pthread_attr_setstacksize()。下面是当应用程序尝试调用JNI AttachCurrentThread()时,pthread的大小太小时记录的错误:

    1
    2
    F/art: art/runtime/thread.cc:435]
    Attempt to attach a thread with a too-small stack (16384 bytes)

对象模型更改

Dalvik允许子类覆写package-private方法是不正确的。 ART在这种情况下发出警告:

1
2
3
Before Android 4.1, method void com.foo.Bar.quux()
would have incorrectly overridden the package-private method in
com.quux.Quux

如果你现在另一个包中覆写一个类的方法,将这个方法声明为publicprotected

对象现在有私有字段。在类层次结构中的字段上使用反射的应用程序应小心不要尝试查看Object的字段。例如,如果您正在将类层次结构作为序列化框架的一部分进行迭代,请停止,直到方法返回null。

1
Class.getSuperclass() == java.lang.Object.class

如果没有参数,代理InvocationHandler.invoke()现在接收null,而不是一个空数组。 这种行为是以前记录过,但在Dalvik中没有正确处理。以前的版本的Mockito有这方面的问题,因此在使用ART测试时要使用更新的Mockito版本。

修复AOT编译问题

ART的预编译(AOT)应该适用于所有标准的Java代码。编译由ART的dex2oat工具执行; 如果你在安装时遇到与dex2oat相关的任何问题,请告诉我们(请参阅问题报告),以便我们尽快解决这些问题。 需要注意几个问题:

  • ART在安装时比Dalvik做了更严格的字节码验证。Android生成工具生成的代码是好的。但是,一些后处理工具(特别是执行模糊处理的工具)可能会生成Dalvik可以容忍但ART会拒绝的无效文件。我们一直与工具供应商合作寻找并解决这些问题。在许多情况下,获取最新版本的工具和重新生成DEX文件可以解决这些问题。
  • 由ART验证器标记的一些典型问题包括:
    • 无效控制流
    • 不平衡监测
    • 0长度参数类型列表大小
  • 一些APP依赖于/system/framework/data/dalvik-cache或DexClassLoader的优化输出目录中安装的.odex文件格式。这些文件现在是ELF文件,而不是扩展形式的DEX文件。虽然ART尝试遵循与Dalvik相同的命名和锁定规则,但应用程序不应依赖于文件格式; 格式可能没有通知发生更改。

报告问题

如果你遇到的问题不是APP JNI问题,请通过Android开源项目问题跟踪工具报告问题。在Google Play商店中添加adb bugreport和指向该应用的链接(如果有)。否则,如果可能,请附加重现此问题的APK。请注意,问题(包括附件)会公开显示。