マルチコア デバイスと Xamarin.Android

Android は、複数の異なるコンピューター アーキテクチャで実行できます。 このドキュメントでは、Xamarin.Android アプリケーションに利用できるさまざまな CPU アーキテクチャについて説明します。 このドキュメントでは、さまざまな CPU アーキテクチャをサポートするために Android アプリケーションがどのようにパッケージ化されているかについても説明します。 アプリケーション バイナリ インターフェイス (ABI) が導入され、Xamarin.Android アプリケーションで使用する ABI に関するガイダンスが提供される予定です。

概要

Android では、"FAT バイナリ" を作成することができます。これは、複数の異なる CPU アーキテクチャをサポートするマシン コードを含む 1 つの .apk ファイルです。 これは、マシン コードの各部分をアプリケーション バイナリ インターフェイスと関連付けることで行います。 ABI は、特定のハードウェア デバイスでどのマシン コードを実行するかを制御するために使用されます。 たとえば、x86 デバイス上で実行する Android アプリケーションには、アプリケーションのコンパイル時に x86 ABI のサポートを含める必要があります。

具体的には、各 Android アプリケーションは 1 つ以上の埋め込みアプリケーション バイナリ インターフェイス (EABI) をサポートします。 EABI とは、埋め込みソフトウェア プログラムに固有の規則です。 一般的な EABI は次のようなことを記述します。

  • MMX 命令セット。

  • 実行時に格納および読み込むメモリのエンディアン。

  • オブジェクト ファイルとプログラム ライブラリのバイナリ形式、およびこれらのファイルとライブラリで許可またはサポートされるコンテンツの種類。

  • アプリケーション コードとシステム間でデータを渡すために使用されるさまざまな規則 (例: 関数が呼び出されるときのレジスタまたはスタックの使用方法、配置制約など)。

  • 列挙型、構造体、フィールド、および配列の配置とサイズの制約。

  • 実行時にマシン コードで使用できる (通常は厳選されたライブラリのセットからの) 関数シンボルの一覧。

armeabi とスレッド セーフ

アプリケーション バイナリ インターフェイスについては以降で詳しく説明しますが、Xamarin.Android で使用される armeabi ランタイムは、スレッド セーフではないことに注意してください。 armeabi をサポートしているアプリケーションを armeabi-v7a デバイスに展開する場合、変わった予期しない例外が数多く発生します。

Android 4.0.0、4.0.1、4.0.2、および 4.0.3 のバグにより、armeabi-v7a ディレクトリが存在し、デバイスが armeabi-v7a デバイスであっても、armeabi からネイティブ ライブラリが取得されます。

Note

Xamarin.Android では、.so が正しい順序で APK に追加されます。 このバグは Xamarin.Android のユーザーにとっては問題にはなりません。

ABI の説明

Android でサポートされている各 ABI は、一意の名前で識別されます。

armeabi

これは、最低限 ARMv5TE 命令セットをサポートする ARM ベースの CPU の EABI の名前です。 Android では、リトル エンディアン ARM GNU/Linux ABI に従います。 この ABI は、ハードウェア依存の浮動小数点演算をサポートしていません。 すべての FP 演算は、コンパイラの libgcc.a スタティック ライブラリに由来するソフトウェア ヘルパー関数で実行されます。 SMP デバイスは armeabi でサポートされていません。

重要

Xamarin.Android の armeabi コードはスレッド セーフではないため、マルチ CPU の armeabi-v7a デバイスでは使用しないでください (以下で説明)。 シングル コアの armeabi-v7a デバイスで armeabi コードを使用するのは安全です。

armeabi-v7a

これは、上記で説明した armeabi EABI を拡張する別の ARM ベースの CPU 命令セットです。 armeabi-v7a EABI は、ハードウェアの浮動小数点演算と複数の CPU (SMP) デバイスをサポートしています。 armeabi-v7a EABI を使用するアプリケーションは、armeabi を使用するアプリケーションに比べて大幅なパフォーマンスの向上が期待できます。

Note

armeabi-v7a マシン コードは、ARMv5 デバイスでは実行できません。

arm64-v8a

これは、ARMv8 CPU アーキテクチャに基づく 64 ビット命令セットです。 このアーキテクチャは、Nexus 9 で使用されています。 Xamarin.Android 5.1 ではこのアーキテクチャのサポートが導入されています (詳細については「64-bit runtime support (64 ビット ランタイムのサポート)」を参照)。

x86

これは、一般的な x86 または IA-32 という名前の命令セットをサポートする CPU の ABI の名前です。 この ABI は、Pentium Pro 命令セット (MMX、SSE、SSE2、および SSE3 の命令セットを含む) の命令に対応します。 次のような、その他のオプションの IA-32 命令セットの拡張機能は含まれません。

  • MOVBE 命令。
  • 追加の SSE3 拡張機能 (SSSE3)。
  • SSE4 の任意のバリアント。

Note

Google TV は x86 上で実行されますが、Android の NDK ではサポートされていません。

x86_64

これは、64 ビット x86 命令セット (x64 または AMD64 とも呼ばれます) をサポートする CPU の ABI の名前です。 Xamarin.Android 5.1 ではこのアーキテクチャのサポートが導入されています (詳細については「64-bit runtime support (64 ビット ランタイムのサポート)」を参照)。

APK ファイル形式

Android アプリケーション パッケージは、Android のアプリケーションに必要なコード、アセット、リソース、および証明書をすべて保持するファイル形式です。 これは .zip ファイルですが、.apk ファイル名拡張子を使用します。 展開すると、次のスクリーンショットのような、Xamarin.Android によって作成された .apk のコンテンツが表示されます。

Contents of the .apk

.apk ファイルのコンテンツを簡単に説明します。

  • AndroidManifest.xml – バイナリ XML 形式の AndroidManifest.xml ファイルです。

  • classes.dex – Android ランタイム VM によって使用される dex ファイル形式にコンパイルされたアプリケーション コードが含まれます。

  • resources.arsc – このファイルには、アプリケーションのプリコンパイル済みリソースがすべて含まれています。

  • lib – このディレクトリには、各 ABI のコンパイル済みコードが保持されています。 前のセクションで説明した各 ABI に対して 1 つのサブフォルダーが格納されます。 上記のスクリーンショットでは、該当する .apk には armeabi-v7ax86 の両方のネイティブ ライブラリがあります。

  • META-INF – このディレクトリ (存在する場合) は、署名情報、パッケージ、および拡張機能の構成データを格納するために使用されます。

  • res – このディレクトリには、resources.arsc にコンパイルされなかったリソースが保持されます。

Note

ファイル libmonodroid.so は、すべての Xamarin.Android アプリケーションで必要なネイティブ ライブラリです。

Android デバイスの ABI のサポート

各 Android デバイスは、最大 2 つの ABI でネイティブ コードの実行をサポートします。

  • "プライマリ" ABI – これは、システム イメージで使用されるマシン コードに対応します。

  • "セカンダリ" ABI – これも、システム イメージでサポートされるオプションの ABI です。

たとえば、典型的な ARMv5TE デバイスでは、armeabi のプライマリ ABI しかないのに対し、ARMv7 デバイスでは、armeabi-v7a のプライマリ ABI と armeabi のセカンダリ ABI を指定します。 典型的な x86 デバイスでは、x86 のプライマリ ABI のみを指定します。

Android のネイティブ ライブラリのインストール

パッケージのインストール時に、.apk 内のネイティブ ライブラリがアプリのネイティブ ライブラリのディレクトリに抽出されます。このディレクトリは、通常、/data/data/<package-name>/lib で、以降は $APP/lib とします。

Android のネイティブ ライブラリのインストール動作は、Android のバージョンによって大幅に異なります。

ネイティブ ライブラリのインストール: Android 4.0 より前のバージョン

Android 4.0 Ice Cream Sandwich より前のバージョンでは、.apk 内の 1 つの ABI からのみネイティブ ライブラリを抽出します。 この古い Android アプリが最初にプライマリ ABI のすべてのネイティブ ライブラリの抽出を試みて、ライブラリが存在しない場合は、次に Android がセカンダリ ABI のすべてのネイティブ ライブラリを抽出します。 "マージ" は行われません。

たとえば、アプリケーションが armeabi-v7a デバイスにインストールされている状況を考えてみましょう。 armeabiarmeabi-v7a の両方をサポートする .apk, の中には、次の ABI lib ディレクトリとファイルがあります。

lib/armeabi/libone.so
lib/armeabi/libtwo.so
lib/armeabi-v7a/libtwo.so

インストール後、ネイティブ ライブラリ ディレクトリに次のものが含まれます。

$APP/lib/libtwo.so # from the armeabi-v7a directory in the apk

つまり、libone.so はインストールされません。 これは、実行時に読み込むアプリケーションの libone.so が存在しないため、問題が発生します。 この動作は予期しないものですが、バグとしてログに記録され、"目的どおりに動作" として再分類されています。

その結果、Android 4.0 より前のバージョンを対象とする場合には、アプリケーションがサポートする ABI に対して、すべてのネイティブ ライブラリを提供する必要があります。つまり、.apk を含める必要があります。

lib/armeabi/libone.so
lib/armeabi/libtwo.so
lib/armeabi-v7a/libone.so
lib/armeabi-v7a/libtwo.so

ネイティブ ライブラリのインストール: Android 4.0 – Android 4.0.3

Android 4.0 Ice Cream Sandwich では抽出ロジックが変更されています。 すべてのネイティブ ライブラリが列挙され、ファイルのベース名が既に抽出されているかどうか、および次の両方の条件が満たされているかどうかを確認してから、ライブラリが抽出されます。

  • まだ抽出されていない。

  • ネイティブ ライブラリの ABI がターゲットのプライマリまたはセカンダリ ABI と一致している。

これらの条件を満たすと、"マージ" 動作が可能になります。つまり、.apk と次のコンテンツがある場合、

lib/armeabi/libone.so
lib/armeabi/libtwo.so
lib/armeabi-v7a/libtwo.so

インストール後、ネイティブ ライブラリ ディレクトリに次のものが含まれます。

$APP/lib/libone.so
$APP/lib/libtwo.so

ただし、この動作は、ドキュメント「Issue 24321: Galaxy Nexus 4.0.2 uses armeabi native code when both armeabi and armeabi-v7a is included in apk」 (問題 24321: apk に armeabi と armeabi v7a の両方が含まれていると、Galaxy Nexus 4.0.2 では armeabi ネイティブ コードが使用される) で説明されているように、順序依存です。

ネイティブ ライブラリは、(たとえば、unzip で一覧表示されているように) "順番に" 処理され、最初の一致が抽出されます。 .apk には libtwo.soarmeabi バージョンと armeabi-v7a バージョンが含まれており、armeabi が最初にリストされているため、armeabi-v7a バージョンではなくarmeabi バージョンが抽出されます。

$APP/lib/libone.so # armeabi
$APP/lib/libtwo.so # armeabi, NOT armeabi-v7a!

さらに、(後述のセクション「サポートされる ABI の宣言」で説明されているように) armeabiarmeabi-v7a の両方の ABI が指定されている場合でも、Xamarin.Android では以下に含まれる次の要素が作成されます。 csproj:

<AndroidSupportedAbis>armeabi,armeabi-v7a</AndroidSupportedAbis>

その結果、armeabilibmonodroid.so は最初に .apk 内で見つかり、armeabi-v7alibmonodroid.soが存在し、ターゲット用に最適化されていても、抽出されるのは armeabilibmonodroid.so になります。 armeabi が SMP セーフではないため、あいまいな実行時エラーが発生する可能性もあります。

ネイティブ ライブラリのインストール: Android 4.0.4 以降

Android 4.0.4 では、抽出ロジックが変更されています。すべてのネイティブ ライブラリが列挙され、ファイルのベース名の読み取り後、プライマリ ABI バージョン (存在する場合)、またはセカンダリ ABI (存在する場合) が抽出されます。 これにより、"マージ" 動作が可能になります。つまり、.apk と次のコンテンツがある場合、

lib/armeabi/libone.so
lib/armeabi/libtwo.so
lib/armeabi-v7a/libtwo.so

インストール後、ネイティブ ライブラリ ディレクトリに次のものが含まれます。

$APP/lib/libone.so # from armeabi
$APP/lib/libtwo.so # from armeabi-v7a

Xamarin.Android と ABI

Xamarin.Android では、次の "64 ビット" アーキテクチャがサポートされています。

  • arm64-v8a
  • x86_64

Note

2018 年 8 月から、新しいアプリは API レベル 26 をターゲットにすることが必須となります。また、2019 年 8 月から、アプリは 32 ビット バージョンに加えて 64 ビット バージョンを提供することが必須となります

Xamarin.Android では、次の 32 ビット アーキテクチャがサポートされています。

  • armeabi ^
  • armeabi-v7a
  • x86

Note

^Xamarin.Android 9.2 以降、armeabi はサポート対象から除外されました。

Xamarin.Android では現在、mips をサポートしていません。

サポートされる ABI の宣言

既定では、Xamarin.Android のリリース ビルドには armeabi-v7a が既定となり、デバッグ ビルドには armeabi-v7ax86 が既定となります。 さまざまな ABI のサポートは、Xamarin.Android プロジェクトのプロジェクト オプションから設定できます。 Visual Studio では、次のスクリーンショットで示すように、これらの値は、プロジェクトの [プロパティ][Android オプション] ページの [詳細設定] タブの下で設定できます。

Android Options Advanced properties

Visual Studio for Mac では、次のスクリーンショットで示すように、サポートされるアーキテクチャは、[プロジェクト オプション][Android のビルド] ページの [詳細設定] タブの下で選択できます。

Android Build Supported ABIs

状況によっては、追加の ABI のサポートを宣言する必要があります。たとえば次のような状況が考えられます。

  • アプリケーションを x86 デバイスに展開する。

  • スレッド セーフを保証するためにアプリケーションを armeabi-v7a デバイスに展開する。

まとめ

このドキュメントでは、Android アプリケーションを実行できるさまざまな CPU アーキテクチャについて説明しました。 アプリケーション バイナリ インターフェイスと、それが異なる CPU アーキテクチャをサポートするために Android でどのように使用されているかについて説明しました。 また、Xamarin.Android アプリケーションでの ABI サポートを指定する方法について説明し、Xamarin.Android アプリケーションを armeabi のみを対象とした armeabi-v7a デバイスで使用するときに発生する問題を明らかにしました。