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" 以上になります。

さらに、Android オープンソース プロジェクト(AOSP)では、メモリ管理機能を改善するために、コンパクション対応のガベージ コレクターを開発しています。そのため、コンパクション対応の GC と互換性のない技術(オブジェクトのインスタンス データへのポインタを保存することなど)の使用を避ける必要があります。これは、Java Native Interface(JNI)を使用するアプリでは特に重要です。詳細については、JNI の問題の防止をご覧ください。

JNI の問題の防止

ART の JNI は、Dalvik の JNI よりも若干厳密です。一般的な問題の検出には CheckJNI モードを使用することをお勧めします。アプリで C/C++ コードを使用している場合は、次の記事を確認してください。

CheckJNI を使った Android JNI のデバッグ

ガベージ コレクションの問題に対応するために JNI コードを確認

ART には、Android オープンソース プロジェクト(AOSP)で開発中のコンパクション対応のガベージ コレクターが備わっています。コンパクション対応のガベージ コレクターを使用すると、オブジェクトをメモリに移動することができます。C/C++ コードを使用している場合は、コンパクション対応の GC と互換性のない操作を実行しないでください。Google では CheckJNI を強化して、いくつかの潜在的な問題を検出できるようにしています(ICS の JNI ローカル リファレンスの変更点で説明しています)。

特に注意が必要なのは、Get...ArrayElements() 関数と Release...ArrayElements() 関数を使用する場面です。コンパクション非対応の GC を備えたランタイムでは、通常、Get...ArrayElements() 関数により、配列オブジェクトをバックアップする実際のメモリへの参照が返されます。返された配列要素のいずれかに変更を加えた場合、配列オブジェクト自体が変更されます(通常、Release...ArrayElements() の引数は無視されます)。ただし、コンパクション対応の GC を使用している場合は、Get...ArrayElements() 関数がメモリのコピーを返すことがあります。コンパクション対応の GC を使用しているときに参照を誤用すると、メモリの破損やその他の問題が発生する可能性があります。次に例を示します。

  • 返された配列要素に変更を加える場合は、変更が完了したときに、適切な Release...ArrayElements() 関数を呼び出して、加えた変更が基盤の配列オブジェクトに適切にコピーされるようにします。
  • メモリの配列要素を解放するときは、加えた変更に応じて、適切なモードを使用する必要があります。
    • 配列要素に変更を加えなかった場合は、JNI_ABORT モードを使用します。このモードでは、変更を基盤の配列オブジェクトにコピーすることなく、メモリが解放されます。
    • 配列に変更を加えて、参照が不要になった場合は、コード 0(配列オブジェクトを更新し、メモリのコピーを解放する)を使用します。
    • commit する必要のある配列に変更を加えて、配列のコピーを保持する必要がある場合は、JNI_COMMIT(基盤の配列オブジェクトを更新し、コピーを保持する)を使用します。
  • Release...ArrayElements() を呼び出すと、Get...ArrayElements() によって返された元のポインタと同じポインタが返されます。たとえば、元のポインタを増分してから(返された配列要素をスキャンするために)、増分したポインタを Release...ArrayElements() に渡すことは安全ではありません。この変更されたポインタを渡すと、不正なメモリが解放され、メモリが破損する可能性があります。

エラー処理

ART の JNI は、多くのケース(Dalvik ではエラーがスローされない)でエラーをスローします(繰り返しますが、CheckJNI でテストすることにより、そのような多くのケースを検出することができます)。

たとえば、存在しないメソッド(ProGuard などのツールでメソッドが削除されている場合など)で RegisterNatives を呼び出すと、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() は現在、単に null を返す代わりに、NoSuchFieldError を適切にスローします。同様に、GetMethodID()GetStaticMethodID() は現在、NoSuchMethodError を適切にスローします。これにより、例外が未処理になったり、ネイティブ コードの Java 呼び出し元に例外がスローされたりするため、CheckJNI の失敗が発生する可能性があります。つまり、CheckJNI モードで ART 対応アプリをテストすることが特に重要になります。

ART では、JNI CallNonvirtual...Method() メソッド(CallNonvirtualVoidMethod() など)のユーザーは、JNI の仕様で求められているように、メソッドのサブクラスではなく、宣言クラスを使用する必要があります。

スタックサイズの問題の防止

Dalvik には、ネイティブ コードと Java コード用の個別のスタックがあります。デフォルトの Java スタックのサイズは 32 KB で、デフォルトのネイティブ スタックのサイズは 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 では、package-private メソッドのオーバーライドをサブクラスに不適切に許可していました。このような場合、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 には private 項目があります。クラス階層の項目に反映されるアプリでは Object のフィールドを検索しないように注意する必要があります。たとえば、シリアル化フレームワークの一部としてクラス階層を反復しているときは、次の場合に停止してください。

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

メソッドが null を返すまで継続してはなりません。

現在、プロキシ InvocationHandler.invoke() は、引数がない場合に、空の配列ではなく、null を受け取ります。この動作は既にドキュメント化されていますが、Dalvik では適切に処理されません。前のバージョンの Mockito ではこの処理が困難なため、ART でテストを行うときは、アップデートされた Mockito バージョンを使用してください。

AOT コンパイルの問題の修正

ART の Ahead-Of-Time(AOT)Java コンパイルは、標準のすべての Java コードに対して機能するはずです。コンパイルは、ART の dex2oat ツールによって実行されます。インストール時に dex2oat に関連する問題が発生した場合は、問題を可能な限り迅速に修正しますので、Google までお知らせください(問題の報告をご覧ください)。次のようないくつかの問題に注意してください。

  • ART では、インストール時のバイトコードの検証を Dalvik よりも厳密にしています。Android ビルドツールによって生成されたコードは適切に機能するはずです。しかし、一部の後処理ツール(特に難読化を実行するツール)は、Dalvik では許容されるが、ART では拒否される無効なファイルを生成する場合があります。Google はツールベンダーと協力して、こうした問題の発見と修正に努めています。多くの場合、最新バージョンのツールを取得して、DEX ファイルを再生成すると、これらの問題を修正できます。
  • ART の検証ツールによって、次のような典型的な問題が報告されます。
    • 無効な制御フロー
    • バランスが悪い moniterenter/moniterexit
    • パラメータの型のリストサイズの長さが 0
  • 一部のアプリには、/system/framework/data/dalvik-cache、または DexClassLoader の最適化された出力ディレクトリにあるインストール済みの .odex ファイル形式との依存関係があります。現在、これらのファイルは ELF ファイルであり、DEX ファイルの拡張形式ではありません。ART では、Dalvik と同じ命名規則およびロック規則の遵守に努めていますが、アプリはファイル形式に依存してはなりません。ファイル形式は、通知なしに変更される可能性があります。

    注:Android 8.0(API レベル 26)以降では、DexClassLoader の最適化された出力ディレクトリは、非推奨となっています。詳細については、DexClassLoader() コンストラクタのドキュメントをご覧ください。

問題の報告

アプリの JNI の問題に起因しない問題が発生した場合は、AOSP Issue Tracker(https://code.google.com/p/android/issues/list)を介して問題を報告してください。可能な場合は、"adb bugreport" と Google Play ストアのアプリへのリンクを含めてください。または、可能であれば、問題を再現する APK を添付してください。問題(添付ファイル含めて)は一般に公開されることに注意してください。