連載
» 2012年04月20日 00時00分 公開

Androidで動く携帯Javaアプリ作成入門(31):Android 4.0のサービス/プロセス間通信の基本 (2/3)

[緒方聡,株式会社イーフロー]

プロセス間通信用インターフェイス定義言語「AIDL」

 「AIDL」とはAndroidのプロセス間通信のためのインターフェイス定義言語で、Javaのinterfaceを定義する書式に似ている独自言語で記述します。

 拡張子は「.aidl」で、慣習としてインターフェイスには「I」で始まる名前が付けられます。このファイルをEclipseのプロジェクト内に入れておけば、自動的に対応するJavaのソースコードが「gen」ディレクトリに生成されます。

AIDLのコード例

 以下が今回のサンプルで使用しているAIDLです。

package com.example.android.service; // 【1】
 
import com.example.android.service.ICalculatorCallback; // 【2】
import com.example.android.service.CalculatorExpression;
 
interface ICalculatorService { // 【3】
    oneway void registerCallback(ICalculatorCallback callback); // 【4】
    oneway void unregisterCallback(ICalculatorCallback callback);
    int add(int lhs, int rhs);
    void sum(in List values); // 【5】
    void rotate(inout int[] array, int num);
    int eval(in CalculatorExpression exp);
}
 
ICalculatorService.aidl
package com.example.android.service;
 
oneway interface ICalculatorCallback { // 【6】
    void resultSum(int value);
}
 
ICalculatorCallback.aidl
package com.example.android.service;
 
parcelable CalculatorExpression; // 【7】
CalculatorExpression.aidl

 青い部分がAIDL固有のキーワードです。

 【1】のように、Javaと同じパッケージ宣言が必要です。一方で、Javaとは異なり、【2】のように同一パッケージのAIDLも明示的にインポートしなければなりません。【3】のようにインターフェイス名を宣言します。この名前とファイル名は同一でなければなりません。

 【4】の「oneway」というキーワードは、そのメソッドの終了を待つ必要がないことを意味しています。【5】のように、オブジェクトの引数には「in」「out」「inout」のいずれかのキーワードを指定します。プリミティブ型には、この指定は必要ありません。「in」が入力にのみ使用する、「out」は出力にのみ使用する、「inout」は入出力に使用することを意味します。適切に指定することで、プロセス間でのデータ転送量を抑えられます。

 【6】のように、インターフェイスのすべてのメソッドにonewayを指定する代わりに、インターフェイスにonewayを指定することも可能です。

 【7】の「parcelable」キーワードで指定したクラスが「Parcelable」であることを指定します。このクラス名とファイル名は同一でなければなりません。

AIDLの書き方

 AIDLを一般化すると、次のような形式になります。

package <PackageName>;
[import <FQCN>;]
[parcelable <FQCN>;]
 
[oneway] interface <InterfaceName> {
    [oneway]<ReturnType> <MethodName> ([in|out|inout] <ArgType> <ArgName> ……);
}

 上記の「ReturnType」「ArgType」、戻り値と引数に使用可能な型には制限があります。後ほど一覧にして示します。

 AIDLは、簡潔な定義でプロセス間通信が行え、かつコールバックまで実現可能です。コールバックを介して、サービスとクライアントで相互通信が実現できるわけです。

AIDLの利点

 もし、AIDL以外でプロセス間通信を行おうとすると、TCP/IPを使用する(非公開クラスの「WindowManagerService」「ViewServer」のように)、UNIXドメインを使用する(「netd」「ndc」のように)、共有メモリを使用する(「Ashmem」「Zygote」のように)、といったローテクな方法に頼らざるを得ません。

 AIDLは定義ファイルだけをクライアントに配布することで、Javaコンパイラで保証される安全で簡単なプロセス間通信が行えます。サービスとクライアントの相互通信については、次回詳しく解説します。

プロセス間通信時のデータをバイト配列化する「Parcel」

 Androidの根幹をなすInetntによるメッセージングを支える技術として「Parcel」「Parcelable」というものが存在します。これは、普通にAndroidアプリを作成していても特に気が付かないかもしれませんが、とても多くの場所で使われています。

 誤解を恐れずに簡潔に説明すると、ParcelableはJavaの「Serializable」で、ParcelはParcelable専用のストリームです。Parcelは、カーネルを経由してプロセス間で受け渡しされるデータを抽象化したクラスです。

 Parcelというのは日本語では「小包」という意味で、Androidにおいては小さいデータを意味します(具体的にはデータが128bytes以内であれば、データコピーのオーバーヘッドが抑えられるように設計されています)。

Parcelのメソッド

 Parcelには、データを書き込む「writeXxx」というメソッドと、書き込んだデータを読み出す「readXxx」というメソッドが存在します。

 書き込まれたデータはJavaヒープの外にバイト配列として展開されます。データが追加されるたびに「realloc」で領域が確保されます。Javaヒープの外にデータを保持するのは、プロセス間通信でカーネルモジュールがダイレクトにアクセスできるようにするためです。

 Parcelには、「marshall()」「unmarshall(byte[], int, int)」が用意されており、それぞれ、Parcelをbyte[]に変換するメソッドと、byte[]からParcelに変換するメソッドです。これらのメソッドはParcelableをバイト配列に変換したり、バイト配列からParcelableに戻したりできるため、プロセス間通信以外でも便利に使えます。

Parcelは永続化やマシン間通信には使用しないように

 ただ残念ながら、Parcelableが入れ子になっていると、marshall()が行えない(例外が発生する)という制限が存在します。Parcelを使用すると、制限の範囲内でインスタンスをストレージに保存して永続化可能です。

 ただしParcelは、あくまでもプロセス間通信などの一時的なデータの受け渡しに使うことを想定しており、将来的なデータフォーマットの互換を保証するものではないため、Javadocでは永続化やマシン間通信には使用しないように忠告しています。

ParcelとAIDLで扱えるデータ型

 Parcelに読み書き可能なデータ型は、AIDL同様に制限されています。以下の表は、ParcelとAIDLで扱えるデータ型の一覧です。

種別 Parcelの型 AIDLの型 補足
プリミティブ byte byte
double double
float float
int int
long long
String String 文字列はプリミティブ扱い
プリミティブ配列 boolean[] boolean[]
byte[] byte[]
char[] char[]
double[] double[]
float[] float[]
int[] int[]
long[] long[]
String[] String[]
SparseBooleanArray × キーがint、値がbooleanのマップ型
Parcelables Parcelableを実装したクラスと、その配列すべて Parcelableを実装したクラスと、その配列すべて 読み出す際にクラスローダを指定する必要がある
Bundles Bundle × 読み出す際にクラスローダを指定する必要がある場合がある
アクティブオブジェクト IBinder、IBinder[] IBinder、IBinder[]
IInterface、IInterface[] ×
ParcelFileDescriptor × ファイルディスクリプタ
コンテナ Object、Object[] × Serializableも扱える
List List
Map Map
SparseArray × キーがint、値がオブジェクトのマップ型。読み出す際にクラスローダを指定する必要がある

 ParcelやAIDLでは、charやshortなど、すべてのプリミティブ型ないしプリミティブ配列型をサポートしていないことに注意してください。

 ParcelとAIDLでは、サポートする型に違いがあり、AIDLの方が若干少ないです。しかし、Parcelableを実装した独自コンテナではParcelを直接扱え、AIDLはParcelableが扱えるため、実質Parcelで扱える型のすべてがプロセス間通信で扱えるようになります。

 次ページでは、Parcelableそして、いよいよbindService()のライフサイクルについて説明します。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。