ものがたり(旧)

atsushieno.hatenablog.com に続く

monodroid: API design

これは http://monodroid.net/Documentation/API_Design の現時点での日本語訳。

概要

MonoDroidでは、Monoのコア基本クラスライブラリに加えて、さまざまなAndroid APIバインディングを提供しており、これによってネイティブAndroidアプリケーションをMonoで開発できるようになっています。

MonoDroidのコアには、C#その他の.NETの言語からJavaAPIにアクセス出来るようにするための、C#の世界とJavaの世界を橋渡しする相互運用エンジンが存在しています。

設計原理

MonoDroidのバインディングにはいくつかの設計原理があります。

  • フレームワーク設計ガイドラインに準拠します
  • 開発者がJavaクラスをサブクラスできるようにします
    • サブクラスではC#の標準の構成概念が機能するはずです
      • 既存のクラスからの派生
      • 基底コンストラクタのチェイン
      • メソッドのオーバーライドがC#のオーバーライドに準拠すること
  • 共通のJavaタスクを簡単にし、難しいJavaタスクを可能にします
  • JavaのプロパティをC#のプロパティとして公開します
  • 強く型付けされたAPIを提供します
    • 型安全性が高まります
    • 実行時エラーを最小限にします
    • 戻り値型でIDEのインテリセンスが利用可能になります
    • IDEを活用したAPIの探検を推奨できます
  • Javaクラスライブラリの表出を、(.NET)フレームワークにおける代替を活用して、最小限に抑えます
  • C#のデリゲート(ラムダ、匿名メソッドあるいはSystem.Delegate)を公開します
  • 任意のJavaライブラリを呼び出すメカニズムを提供します

アセンブリ

MonoDroidには、MonoTouchプロファイルを構成するアセンブリが多く含まれます。Assemblyのページには詳細が載っています。

Androidプラットフォームのバインディングはmonodroid.dllアセンブリに含まれています。このアセンブリにはAndroid APIを使用しDalvik VMと通信するバインディング全体が含まれています。

バインディングの設計

コレクション

Android APIではjava.utilコレクションを大幅に利用して、リスト、セット、マップを提供します。わたしたちのバインディングでは、これらの要素を、System.Collections.Genericインターフェースにして公開します。基本的な対応関係は以下の通りです。

コレクションの対応関係
Javaシステム型ヘルパークラス
java.util.Set<E>ICollection<T>Android.Runtime.JavaSet<T>
java.util.List<E>IList<T>Android.Runtime.JavaList<T>
java.util.Map<K,V>IDictionary<K,V>Android.Runtime.JavaDictionary<K,V>

わたしたちは、これらの型でコピーのないマーシャリングを行うために、3つのヘルパークラスを用意しました。可能な場合は、(.NET)フレームワークが提供するList<T>やDictionary<K,V>のような実装の代わりに、これらのコレクションを利用することをお薦めします。わたしたちの実装では、内部的にJavaコレクションを使用しており、Android APIメンバーに渡す時に、ネイティブのコレクションとの間でのコピーが不要になります。

.NETインターフェースを実装するクラスであれば、何でも既存のコレクションに渡すことが出来ます。ただし、バインディングではこれをネイティブJavaコレクションにコピーすることになり、パフォーマンスの劣化とメモリ使用量の拡大に繋がります。

イベントとリスナー

Android APIJava上で実装されており、そのコンポーネントにおけるイベントリスナーのフックはJavaパターンに準拠しています。このパターンは、ユーザーに匿名クラスを作成してそのメソッドをオーバーライドすることを要求する、面倒になりがちなもので、たとえばAndroidJavaでは次のようになります。

final android.widget.Button button = new android.widget.Button(context);

button.setText(this.count + " clicks!");
button.setOnClickListener (new View.OnClickListener() {
    public void onClick (View v) {
        button.setText(++this.count + " clicks!");
    }
});

C#でイベントを使用した場合は次のようになります。

var button = new Android.Widget.Button (context) {
    Text = string.Format ("{0} clicks!", this.count),
};
button.Click += v => {
    button.Text = string.Format ("{0} clicks!", ++this.count);
};

C#のイベントは、Androidのイベント処理メソッドが以下の条件を満たす場合にのみ生成されることに注意して下さい:

  1. setOnClickListenerなどのsetプレフィックスをもつ
  2. 戻り値型がvoid
  3. 1つだけのパラメータを受け取り、そのパラメータがインターフェースであり、そのインターフェースに1つだけのメソッドがあり、そのインターフェース名がListenerで終わる場合(例: View.OnClickListener

もしこれに該当するイベントでない場合は、C#でリスナーが使用されなければなりません。また、既存のJavaコードを移植していたり、Javaアプローチを好む理由がある場合は、リスナーを使用したいかもしれません。

これらのリスナーインターフェースは全て、バインディングの実装の詳細のために、Android.Runtime.IJavaObjectインターフェースを実装するので、リスナークラスでもこのインターフェースを実装しなければなりません。これは、Java.Lang.Objectのサブクラス上にこのリスナーインターフェースを実装したり、Android activityなどJavaオブジェクトのラッパー上に実装することで可能です。

Runnable

Javaではjava.lang.Runnableインターフェースを使用してデリゲートのメカニズムを提供します。java.lang.Threadクラスがこのインターフェースの重要な活用事例です。AndroidでもこのAPIを援用しています。Activity.runOnUiThreadやView.postなどが重要な例です。

このrunnableインターフェースには1つのvoid型メソッドRunがあります。そこで、これはC#バインディングではSystem.Actionデリゲートとして代替できます。わたしたちは、ネイティブAPIでRunnableを受け取るAPI要素については、バインディング中ではActionパラメータを受け取るオーバーロードを提供します。

さまざまな型がこのインターフェースを実装していて、runnableとして直接渡すことが可能になっていることに鑑みて、わたしたちは、IRunnableオーバーロードを、置き換えるのではなく残すことにしました。

リソース

画像、レイアウト定義、バイナリblob、文字列辞書は、リソースファイルとしてアプリケーションに含めることができます。さまざまなAndroid APIが、画像、文字列、バイナリblobを直接処理する代わりに、リソースIDを使用して処理するように設計されています。

たとえば、ユーザーインターフェース レイアウト (main.xml)、国際化テーブル文字列 (strings.xml)、そしていくつかのアイコン (drawable-XXX/icon.png) を含むサンプルアプリケーションは、"Resources" ディレクトリとそれに含まれるリソースを保持するでしょう。


Resources/
drawable-hdpi/
icon.png

drawable-ldpi/
icon.png

drawable-mdpi/
icon.png

layout/
main.xml

values/
strings.xml

ネイティブのAndroid APIでは、ファイル名を直接操作する代わりに、リソースIDを操作します。リソースを使用するAndroidアプリケーションをコンパイルすると、ビルドシステムはそのリソースを配布物にパッケージして、各リソースに対応するトークンを含む"R"と呼ばれるクラスを生成します(これはAndroid上での命名方法です)。たとえば、上記のResourcesの構成については、以下のようなRクラスが生成されます。

public class R {
    public class drawable {
        public const int icon = 0x123;
    }

    public class layout {
        public const int main = 0x456;
    }

    public class strings {
        public const int first_string = 0xabc;
        public const int second_string = 0xbcd;
    }
}

そうしたら、R.drawable.iconを使用して、drawble/icon.pngファイルを参照したり、R.layout.main を使用してlayout/main.xmlファイルを参照したり、R.strings.first_stringを使用して、辞書ファイルvalues/strings.xmlにある最初の文字列を参照したりできます。

定数と列挙型

ネイティブのAndroid APIには、何らかの意味をもつ定数フィールドに対応するintを受け取ったり返したりするメソッドが数多く含まれています。これらのメソッドを使用するためには、ユーザーはドキュメントを見てどの定数が妥当な値となるかを判断しなければならず、これは望ましくありません。

たとえば次のようなメソッドがそうです: Activity.RequestWindowFeature (int featureID)

このような場合は、わたしたちは関連する定数を1つの.NET列挙型に当てはめて、(intの)代わりにその列挙型を受け取るように、このメソッドを再度マッピングします。これによって、インテリセンスが利用可能な値の選択肢を提供できるようになります。

上記の例は次のようになります: Activity.RequestWindowFeature (WindowManagerFlags featureID)

どのような定数がまとめ上げられ、どのAPIがそれを使用するかを判断するのは、ほとんど手作業で行われていることに注意して下さい。API上で列挙型で表現された方が好ましいような定数を発見した場合は、バグレポートを送ってください。