Architecture

Xamarin.Android アプリケーションは Mono 実行環境内で実行されます。 この実行環境は、Android ランタイム (ART) 仮想マシンと並行して実行されます。 どちらのランタイム環境も Linux カーネル上で実行され、開発者が基になるシステムにアクセスできるように、さまざまな API をユーザー コードに公開しています。 Mono ランタイムは C 言語で記述されます。

SystemSystem.IOSystem.Net、その他の .NET クラス ライブラリを使用して、基になる Linux オペレーティング システムの機能にアクセスできます。

Android では、オーディオ、グラフィックス、OpenGL、テレフォニーなどのほとんどのシステム機能をネイティブ アプリケーションに直接使用することはできません。これらの機能は、Java.* 名前空間または Android.* 名前空間のいずれかに存在する Android Runtime Java API を介してのみ公開されます。 アーキテクチャは、ほぼ次のようになります。

Diagram of Mono and ART above the kernel and below .NET/Java + bindings

Xamarin.Android 開発者は、知っている .NET API を呼び出すか (低レベルアクセス用)、Android ランタイムによって公開される Java API へのブリッジを提供する Android 名前空間で公開されているクラスを使用して、オペレーティング システムのさまざまな機能にアクセスします。

Android クラスが Android Runtime クラスと通信する方法の詳細については、API の設計に関するドキュメントを参照してください。

アプリケーション パッケージ

Android アプリケーション パッケージは、.apk ファイル拡張子を持つ ZIP コンテナーです。 Xamarin.Android アプリケーション パッケージの構造とレイアウトは、通常の Android パッケージと同じで、次のものが追加されています。

  • アプリケーション アセンブリ (IL を含む) は、assemblies フォルダー内に非圧縮で "格納" されます。 リリース ビルドでのプロセスの起動中に、.apk がプロセスに mmap() でマッピングされ、アセンブリがメモリから読み込まれます。 これにより、実行前にアセンブリを抽出する必要がないように、アプリの起動を高速化できます。

  • 注:Assembly.LocationAssembly.CodeBase などのアセンブリの場所情報は、リリース ビルドでは "使用できません"。 これらは個別のファイルシステム エントリとして存在せず、使用できる場所もありません。

  • Mono ランタイムを含むネイティブ ライブラリは、.apk 内に存在します。 Xamarin.Android アプリケーションには、armeabiarmeabi-v7ax86 など、目的または対象の Android アーキテクチャ用のネイティブ ライブラリが含まれている必要があります。 Xamarin.Android アプリケーションは、適切なランタイム ライブラリが含まれている場合を除き、プラットフォーム上では実行できません。

Xamarin.Android アプリケーションには、Android がマネージド コードを呼び出せるようにする "Android 呼び出し可能ラッパー" も含まれています。

Android 呼び出し可能ラッパー

  • Android 呼び出し可能ラッパーJNI ブリッジであり、Android ランタイムがマネージド コードを呼び出す必要があるときに使用されます。 Android 呼び出し可能ラッパーは、仮想メソッドをオーバーライドし、Java インターフェイスを実装する方法です。 詳細については、Java 統合の概要に関するドキュメントを参照してください。

マネージド呼び出し可能ラッパー

マネージド呼び出し可能ラッパーは JNI ブリッジであり、マネージド コードが Android コードを呼び出し、仮想メソッドのオーバーライドと Java インターフェイスの実装のサポートを提供する必要がある場合に使用されます。 Android.* および関連する名前空間全体が、.jar バインディングを介して生成されたマネージド呼び出し可能ラッパーです。 マネージド呼び出し可能ラッパーは、マネージド型と Android 型の間で変換し、JNI を介して基になる Android プラットフォーム メソッドを呼び出します。

作成されたマネージド呼び出し可能ラッパーはそれぞれ、Android.Runtime.IJavaObject.Handle プロパティを介してアクセスできる Java グローバル参照を保持します。 グローバル参照は、Java インスタンスとマネージド インスタンスの間のマッピングを提供するために使用されます。 グローバル参照は限られたリソースです。エミュレーターでは一度に 2,000 個のグローバル参照しか存在できませんが、ほとんどのハードウェアは一度に 52,000 個を超えるグローバル参照を存在させることができます。

グローバル参照が作成および破棄されるタイミングを追跡するには、gref を含む debug.mono.log システム プロパティを設定できます。

マネージド呼び出し可能ラッパーで Java.Lang.Object.Dispose() を呼び出すと、グローバル参照を明示的に解放できます。 これにより、Java インスタンスとマネージド インスタンスとの間のマッピングが削除され、Java インスタンスの収集が許可されます。 Java インスタンスにマネージド コードから再アクセスすると、新しいマネージド呼び出し可能ラッパーが作成されます。

インスタンスが誤ってスレッド間で共有される可能性がある場合は、マネージド呼び出し可能ラッパーを破棄するときは注意が必要です。インスタンスを破棄すると、他のスレッドからの参照に影響を与えるためです。 最大限の安全性を確保するために、new "または" "知っている" メソッドから割り当てられたインスタンスの Dispose() のみが、スレッド間で誤ってインスタンスを共有する可能性があるキャッシュされたインスタンスではなく、新しいインスタンスを常に割り当てます。

マネージド呼び出し可能ラッパー サブクラス

マネージド呼び出し可能ラッパー サブクラスは、すべての "興味深い" アプリケーション固有のロジックが存在する可能性がある場所です。 これには、カスタム Android.App.Activity サブクラス (既定のプロジェクト テンプレートの Activity1 型など) が含まれます。 (具体的には、RegisterAttribute カスタム属性が含まれてい "ない"、または RegisterAttribute.DoNotGenerateAcwfalse (既定値) の Java.Lang.Object サブクラスです)。

マネージド呼び出し可能ラッパーと同様に、マネージド呼び出し可能ラッパー サブクラスにも、Java.Lang.Object.Handle プロパティを介してアクセスできるグローバル参照が含まれています。 マネージド呼び出し可能ラッパーと同様に、Java.Lang.Object.Dispose() を呼び出すと、グローバル参照を明示的に解放できます。 マネージド呼び出し可能ラッパーとは異なり、インスタンスの Dispose() を行うと、Java インスタンス (Android 呼び出し可能ラッパーのインスタンス) とマネージド インスタンスの間のマッピングが中断されるため、このようなインスタンスを破棄する前に "細心の注意を払う" 必要があります。

Java Activation

Java から Android 呼び出し可能ラッパー (ACW) が作成されると、ACW コンストラクターによって対応する C# コンストラクターが呼び出されます。 たとえば、MainActivity の ACW には、MainActivity の既定のコンストラクターを呼び出す既定のコンストラクターが含まれます。 (これは、ACW コンストラクター内の TypeManager.Activate() 呼び出しを通じて行われます)。

結果のコンストラクター シグネチャはもう 1 つあります: (IntPtr, JniHandleOwnership) コンストラクター。 (IntPtr, JniHandleOwnership) コンストラクターは、Java オブジェクトがマネージド コードに公開され、JNI ハンドルを管理するためにマネージド呼び出し可能ラッパーを構築する必要がある場合に呼び出されます。 これは通常、自動的に行われます。

マネージド呼び出し可能ラッパー サブクラスで (IntPtr, JniHandleOwnership) コンストラクターを手動で指定する必要があるシナリオは 2 つあります。

  1. Android.App.Application はサブクラス化されている。 Application は特殊です。既定の Applicaton コンストラクターは呼び出され "ません"。代わりに (IntPtr, JniHandleOwnership) コンストラクターを指定する必要があります

  2. 基底クラス コンストラクターからの仮想メソッドの呼び出し。

(2) は漏洩した抽象化であることに注意してください。 Java では、C# と同様に、コンストラクターから仮想メソッドを呼び出すと、常に最も派生したメソッドの実装が呼び出されます。 たとえば、TextView(Context, AttributeSet, int) コンストラクターは、TextView.DefaultMovementMethod プロパティとしてバインドされている仮想メソッド TextView.getDefaultMovementMethod() を呼び出します。 したがって、LogTextBox 型が (1) TextView をサブクラス化し、(2) TextView.DefaultMovementMethod をオーバーライドし、(3) XML を介してそのクラスのインスタンスをアクティブ化した場合、オーバーライドされた DefaultMovementMethod プロパティは、ACW コンストラクターが実行される前に呼び出され、C# コンストラクターが実行される前に発生します。

これは、ACW LogTextBox インスタンスが最初にマネージド コードを入力したときに LogTextView(IntPtr, JniHandleOwnership) コンストラクターを使用して LogTextBox インスタンスをインスタンス化し、ACW コンストラクターの実行時に "同じインスタンスで" LogTextBox(Context, IAttributeSet, int) コンストラクターを呼び出すことによってサポートされます。

イベントの順序:

  1. Layout XML は ContentView に読み込まれます。

  2. Android は Layout オブジェクト グラフをインスタンス化し、monodroid.apidemo.LogTextBox のインスタンス (LogTextBox の ACW) をインスタンス化します。

  3. monodroid.apidemo.LogTextBox コンストラクターは、android.widget.TextView コンストラクターを実行します。

  4. TextView コンストラクターは、monodroid.apidemo.LogTextBox.getDefaultMovementMethod() を呼び出します。

  5. monodroid.apidemo.LogTextBox.getDefaultMovementMethod()LogTextBox.n_getDefaultMovementMethod() を呼び出します。このメソッドは、TextView.n_GetDefaultMovementMethod() を呼び出し、このメソッドは Java.Lang.Object.GetObject<TextView> (handle, JniHandleOwnership.DoNotTransfer) を呼び出します。

  6. Java.Lang.Object.GetObject<TextView>() は、handle に対応する C# インスタンスが既に存在するかどうかを確認します。 存在する場合は、それを返します。 このシナリオでは存在しないため、Object.GetObject<T>() で作成する必要があります。

  7. Object.GetObject<T>()LogTextBox(IntPtr, JniHandleOwneship) コンストラクターを検索し、それを呼び出し、handle と作成されたインスタンスの間のマッピングを作成し、作成されたインスタンスを返します。

  8. TextView.n_GetDefaultMovementMethod()LogTextBox.DefaultMovementMethod プロパティ ゲッターを呼び出します。

  9. コントロールが android.widget.TextView コンストラクターに返され、実行が終了します。

  10. monodroid.apidemo.LogTextBox コンストラクターが実行され、TypeManager.Activate() が呼び出されます。

  11. LogTextBox(Context, IAttributeSet, int) コンストラクターは、"(7) で作成されたのと同じインスタンスで" 実行されます。

  12. (IntPtr, JniHandleOwnership) コンストラクターが見つからない場合は、System.MissingMethodException](xref:System.MissingMethodException) がスローされます。

Dispose() 呼び出しの中断

JNI ハンドルと対応する C# インスタンスの間にはマッピングがあります。 Java.Lang.Object.Dispose() はこのマッピングを中断します。 マッピングが中断された後に JNI ハンドルがマネージド コードに入力されると、Java Activation のように見え、(IntPtr, JniHandleOwnership) コンストラクターが検査され、呼び出されます。 コンストラクターが存在しない場合は、例外がスローされます。

たとえば、次のマネージド呼び出し可能ラッパー サブクラスを指定します。

class ManagedValue : Java.Lang.Object {

    public string Value {get; private set;}

    public ManagedValue (string value)
    {
        Value = value;
    }

    public override string ToString ()
    {
        return string.Format ("[Managed: Value={0}]", Value);
    }
}

インスタンスを作成し、そのインスタンスの Dispose() を行い、マネージド呼び出し可能ラッパーが再作成される場合:

var list = new JavaList<IJavaObject>();
list.Add (new ManagedValue ("value"));
list [0].Dispose ();
Console.WriteLine (list [0].ToString ());

次のように、プログラムは終了します。

E/mono    ( 2906): Unhandled Exception: System.NotSupportedException: Unable to activate instance of type Scratch.PrematureDispose.ManagedValue from native handle 4051c8c8 --->
System.MissingMethodException: No constructor found for Scratch.PrematureDispose.ManagedValue::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateProxy (System.Type type, IntPtr handle, JniHandleOwnership transfer) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   --- End of inner exception stack trace ---
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Lang.Object.GetObject (IntPtr handle, JniHandleOwnership transfer, System.Type type) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Lang.Object._GetObject[IJavaObject] (IntPtr handle, JniHandleOwnership transfer) [0x00000

サブクラスに (IntPtr, JniHandleOwnership) コンストラクターが含まれている場合は、型の "新しい" インスタンスが作成されます。 その結果、インスタンスは新しいインスタンスであるため、すべてのインスタンス データを "失う" ように見えます。 (Value が null であることに注意してください)。

I/mono-stdout( 2993): [Managed: Value=]

マネージド呼び出し可能ラッパー サブクラスの Dispose() は、Java オブジェクトがもう使用されないことがわかっている場合、またはサブクラスにインスタンス データが含まれておらず、(IntPtr, JniHandleOwnership) コンストラクターが提供されている場合にのみ行います。

アプリケーションの起動

アクティビティやサービスなどが起動されると、Android はまず、activity や service などをホストするプロセスが既に実行されているかどうかを確認します。そのようなプロセスが存在しない場合は、新しいプロセスが作成され、AndroidManifest.xml が読み取られ、/manifest/application/@android:name 属性で指定された型が読み込まれ、インスタンス化されます。 次に、/manifest/application/provider/@android:name 属性値によって指定されたすべての型がインスタンス化されContentProvider.attachInfo%28) メソッドが呼び出されます。 Xamarin.Android は、ビルド プロセス中に mono.MonoRuntimeProviderContentProvider を AndroidManifest.xml に追加してこれにフックします。 mono.MonoRuntimeProvider.attachInfo() メソッドは、Mono ランタイムをプロセスに読み込みます。 この時点より前に Mono を使用しようとすると失敗します。 (: これが、Mono を初期化する前にアプリケーション インスタンスが作成されるため、Android.App.Application サブクラスが (IntPtr, JniHandleOwnership) コンストラクターを提供する必要がある理由です)。

プロセスの初期化が完了すると、AndroidManifest.xml は起動する activity や service などのクラス名を見つけるために参照されます。 たとえば、/manifest/application/activity/@android:name attribute 属性は、読み込む Activity の名前を決定するために使用されます。 アクティビティの場合、この型はandroid.app.Activity を継承する必要があります。 指定された型は Class.forName() を介して読み込まれ (この型は Java 型である必要があるため、Android 呼び出し可能ラッパー)、インスタンス化されます。 Android 呼び出し可能ラッパー インスタンスを作成すると、対応する C# 型のインスタンスの作成がトリガーされます。 Android は Activity.onCreate(Bundle) を呼び出します。これにより、対応する Activity.OnCreate(Bundle) が呼び出され、お客様はすぐに行動できます。