在 Android 執行階段 (ART) 驗證應用程式行為

Android 執行階段 (ART) 是搭載 Android 5.0 (API 級別 21) 以上版本的裝置預設執行階段。這個執行階段提供一些功能,可以改善 Android 平台和應用程式的效能和順暢度。如要進一步瞭解 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 Native Interface (JNI) 的應用程式而言特別重要。詳情請參閱「預防 JNI 問題」。

防止 JNI 問題

ART 的 JNI 比 Dalvik 還嚴格。特別適合使用 CheckJNI 模式找出常見問題。如果應用程式使用 C/C++ 程式碼,請詳閱下列文章:

使用 CheckJNI 對 Android JNI 進行偵錯

檢查 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,而非只傳回空值。同樣地,GetMethodID()GetStaticMethodID() 現在可正確擲回 NoSuchMethodError。這可能會導致未處理的例外狀況,或原生程式碼向 Java 呼叫端擲回的例外狀況,導致 CheckJNI 失敗。因此,使用 CheckJNI 模式測試與 ART 相容的應用程式特別重要。

ART 預期 JNI CallNonvirtual...Method() 方法 (例如 CallNonvirtualVoidMethod()) 的使用者會使用方法的宣告類別,而非 JNI 規格要求的子類別。

避免堆疊大小問題

Dalvik 有不同的原生和 Java 程式碼堆疊,其預設 Java 堆疊大小為 32KB,預設原生堆疊大小為 1 MB。ART 有整合式堆疊,以提升位置。一般來說,ART Thread 堆疊大小應與 Dalvik 大致相同。不過,如果您明確設定堆疊大小,可能需要針對在 ART 中執行的應用程式重新查看這些值。

  • 在 Java 中,查看對指定明確堆疊大小的 Thread 建構函式的呼叫。舉例來說,如果發生 StackOverflowError,就必須增加大小。
  • 在 C/C++ 中,檢查也會透過 JNI 執行 Java 程式碼的執行緒使用 pthread_attr_setstack()pthread_attr_setstacksize() 的情形。以下是應用程式在 pthread 大小太小時,嘗試呼叫 JNI AttachCurrentThread() 時記錄的錯誤示例:
    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

如要覆寫其他套件中的類別方法,請將該方法宣告為 publicprotected

Object」現在有私人欄位。如果在類別階層中反映欄位,應用程式應謹慎處理,不要嘗試查看 Object 的欄位。舉例來說,如果您要在序列化架構中疊代類別階層,請停止

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

,直到方法傳回 null 為止。

如果沒有任何引數,而不是空白陣列,Proxy InvocationHandler.invoke() 現在會收到 null。系統先前已記錄過這個行為,但在 Dalvik 中並未正確處理。舊版 Mockito 會遇到這個問題,因此透過 ART 進行測試時,請使用更新的 Mockito 版本。

修正 AOT 編譯問題

ART 的預先 (AOT) Java 編譯應適用於所有標準 Java 程式碼。編譯作業是由 ART 的 dex2oat 工具執行;如果您在安裝時遇到任何與 dex2oat 相關的問題,請告訴我們 (請參閱「回報問題」一節),以便我們盡快修正。請注意以下幾個問題:

  • 相較於 Dalvik,ART 在安裝時執行的位元碼驗證更加嚴格。Android 建構工具產生的程式碼應該沒有問題。不過,部分後續處理工具 (尤其是執行模糊處理的工具) 可能會產生 Dalvik 容許但遭到 ART 拒絕的無效檔案。我們一直與工具供應商合作,以找出並修正這類問題。在許多情況下,取得最新版工具並重新產生 DEX 檔案,即可修正問題。
  • ART 驗證器標記的一些常見問題包括:
    • 控制流程無效
    • 不平衡:monitorenter/monitorexit
    • 0 長度參數類型清單大小
  • 某些應用程式的依附元件依附於 /system/framework/data/dalvik-cacheDexClassLoader 的最佳化輸出目錄中,已安裝的 .odex 檔案格式。這些檔案現在是 ELF 檔案,而不是擴展的 DEX 檔案。雖然 ART 會嘗試遵循與 Dalvik 相同的命名和鎖定規則,但應用程式不應依附於檔案格式;格式如有變更,恕不另行通知。

    注意:在 Android 8.0 (API 級別 26) 以上版本中,DexClassLoader 最佳化輸出目錄已淘汰。詳情請參閱 DexClassLoader() 建構函式的說明文件。

報表問題

如果您遇到並非因應用程式 JNI 問題而產生的任何問題,請透過 https://code.google.com/p/android/issues/list 的 Android 開放原始碼計畫 Issue Tracker 回報問題。加入 "adb bugreport" 和 Google Play 商店中的應用程式連結 (如有)。否則,如果可以,請附加可重現問題的 APK。請注意,問題 (包括附件) 會公開顯示。