読者です 読者をやめる 読者になる 読者になる

android aidlツールの覚書

ひさびさにAndroid小ネタを書こう。

Androidで他のアプリケーションとやり取りするには、AIDLとServiceを使う。http://developer.android.com/guide/developing/tools/aidl.html

AIDLで規定されたインターフェースに従ってメッセージを送信すると、その規定に従ったメッセージが返ってくる、というRPCの一類型だ(一方通行の場合もある)。.NETで言うところのservice contractみたいなものだ。

メッセージはParcelというオブジェクトで表される。SOAPは封筒(Envelope)だがこっちは小包だということだろうか。

service contractで使用できる型が限られているように、Androidサービスの通信で利用できる型も限られている。複雑な型にはParcelableというインターフェースを使う。.NETのISerializableみたいなものと言えばよいだろうか。

仕様上許される型は以下のものに限定されている:

  • 全てのprimitive type (boolean, char, byte, short, int, long, float, double)
  • String
  • CharSequence
  • List (実装が生成される場合はArrayList
  • Map (実装が生成される場合はHashMap)
  • Parcelableな型(要宣言)

実際にaidlのソースを眺めると、以下のTypeが定義されている。ただしこれはASTとして生成されるツリーの中で参照されているのみで生成されないような型も含まれている。

  • Type: (base for everything else)
  • TEXT_UTILS_TYPE
    • これはCharSequenceサポートの実装の中で使われていて、生成されたコードで使われるわけではない
  • BasicType: VOID_TYPE, BYTE_TYPE, INT_TYPE, LONG_TYPE, FLOAT_TYPE, DOUBLE_TYPE,
  • BooleanType: BOOLEAN_TYPE
  • CharType: CHAR_TYPE;
  • StringType: STRING_TYPE;
  • CharSequenceType: CHAR_SEQUENCE_TYPE;
    • ちょっとだけ特殊
  • RemoteExceptionType: REMOTE_EXCEPTION_TYPE;
  • RuntimeExceptionType: RUNTIME_EXCEPTION_TYPE;
  • BinderType: BINDER_NATIVE_TYPE;
  • BinderProxyType: BINDER_PROXY_TYPE;
  • ClassLoaderType: CLASSLOADER_TYPE;
  • IInterfaceType: IINTERFACE_TYPE;
  • ParcelableInterfaceType: PARCELABLE_INTERFACE_TYPE;
  • ParcelType: PARCEL_TYPE;
    • これらはParcel上では生成されず、aidl(ツール)で生成されるASTにおける型参照としてのみ用いられる。
  • IBinderType: IBINDER_TYPE;
  • MapType: MAP_TYPE;
  • ListType: LIST_TYPE;
  • ParcelableType
    • これは、ユーザーが定義したParcelableの定義に対応する
  • InterfaceType
    • これは、ユーザーが定義した(Parcelableでない)型の定義に対応する
  • GenericType
    • まだ実装できていないようだ
  • GenericListType
    • これらは、MapとListの実装の内部で参照され、生成には用いられない

われわれ開発者がAIDLのファイル(拡張子.aidl)で記述することになるのは、parcelableな型とinterfaceの2種類だ。

このうち、parcelableについては、単に「この型はParcelableですよ」と宣言するためのものでしかない。aidlツールは特にライブラリを参照せずに型名から型の種類を解決しなければならず、名前だけでは特定の型をParcelableとして処理することができないので、個別にparcelable FooBar; として宣言してやる必要がある。正直こんなのは各ファイルでimport宣言する時についでに書けるようにしておけば("import android.os.Bundle parcelable;"とか)、個別に宣言する必要などないのだから、AIDLがいかにやっつけ言語であるかが分かるというものだ。

一方、インターフェースを宣言する*.aidlファイルについては、aidlツールはスタブ型などを生成する。生成されるのはサービスのスタブクラス (Stub)、そしてそのクライアントとして使えるProxyだ。ProxyはIBinderを実装したremoteを経由して当該インターフェースのメソッドをTransact()で呼び出しているだけだが、スタブクラスのコードを一部使いまわすのでスタブクラスの中に入っている。

サービスのStubクラスは、当該インターフェースを実装するものとして生成されるので、サービスを公開しようとする開発者が、自分でインターフェースメソッドを実装しなければならない。Stubはabstractとして宣言されるので、いずれにしろこの生成コードを直接いじるのではなく、自分のクラスで実装することになろう。

stubおよびproxyでは、(サポートされている範囲内で)任意のオブジェクトから、Parcelを生成してやり取りするロジックを、メソッドの引数型および戻り値型に応じて生成している。

最後におまけになるが、Parcelableインターフェースにはやや癖があるので、少し補足しておきたい。というかJavaはインターフェースがAPIを十分に規定しない傾向があると思うので、どうにかしたほうがいいと思う。Serializableインターフェースを実装するのに、宣言されていないreadObject() / writeObject()が必要だったりとか、何なんだろう。

  • Parcelable.Creator型のCREATORフィールドをstaticで宣言してやる必要がある(staticだからインターフェース上で宣言出来なかったというわけだ)。これはstubメソッドのin修飾子付きの引数あるいはstubおよびproxyメソッドの戻り値でParcelableオブジェクトを返す時に呼び出される。これはAIDLのドキュメントでも説明されている。
  • Parcelableインターフェースの実装には、宣言されていないvoid readFromParcel(Parcel)メソッドを宣言してやる必要がある。このメソッドは、Parcelからそのオブジェクトの内容を読み出すことが期待されている。このメソッドの呼び出しはaidlツールで自動生成されるが、AIDLのドキュメントには記載されていない。(この問題はandroidのissuesにも登録してあるが、未解決だ)

とりあえずこんなところで。