Android Remote Service

資料

http://developer.android.com/guide/developing/tools/aidl.html

Designing a Remote Interface Using AIDL

前提

ローカルなService作成演習を経験していること。

 

1.1 ローカルサービスのレビュー

図1.1はローカルサービスを利用する手順を図にしたものです

 

 

 

 

 

 

 

 

 

 

 

 

 


図1.1 ローカルサービスの利用手順

 

①Serviceの利用者は、Context.bindService()メソッドを使ってbindをAndroidOSに要求します。

②AndroidOSはServiceのonBind()メソッドを呼び出します。

③onBindメソッドは型がIBinderの戻り値を戻します。この中に該当Serviceの参照を取得するメソッドを仕組んで起きます。IBinderはServiceの参照そのものではありません。

④AndroidOSはService利用者の定められたメソッド(onServiceConnected)を呼び出します。このメソッドを含むオブジェクトはContext.bindService()メソッドの引数としてAndroidOSに知らされています。

⑤Service利用者は、onServiceConnected()メソッドの引数から、Serviceの参照を取得します。

⑥Service利用者は、この参照を使って、Serviceのメソッドを呼び出します。

 

1.2 リモートサービスとローカルサービスの違い

リモートサービスを利用する手順は、ローカルサービスと同じです。ただ、しくみは、そのまま使えません。異なったアプリ(異なったプロセス)間では、サービスのメソッドを直接呼び出せません。オブジェクトの参照は同じアプリ内で有効です。そもそも、異なるアプリのクラスをインスタンシェートすることはできません。

このため、リモートサービスは図1.2に使う方法を使います。

 

 

 

 

 

 

 

 

 

 

 

 

 

 


図1.2 リモートサービスのメソッドの呼び出し

 

サービスのメソッドの呼び出しは、スタブ(stub)と呼ばれる特殊なオブジェクトのメソッドを呼び出します。その後は、AndroidOSを介して、サービス側のスタブが呼び出されます。そのスタブの延長線上に、目的のメソッドが呼び出されます。戻り値がある場合は、逆の経路で戻されます。

スタブの作り方は、後に説明します。ここでは次の3点が、ローカルサービスと違う点に注意して下さい。

 

(1)サービスとその利用者間でやり取りするデータの型

AndroidOSは利用者からサービスに渡す又は逆に戻すデータの型は意識しません。極端に考えると、データをバイトの列としてしか見ないということです。実際には、Javaのプリミティブ型とString型が使えます。コレクション型も限定された範囲ですが使えます。これに対する対処法は後に説明します。なお、アプリ(プロセス)間を往来できるデータに整列する処理をmarshaling(又はmarshalling)といいます。

 

(2)サービスの利用者の中で、スタブの参照を取得しなければなりません。

サービスの参照を取得するわけではありません。たとえ、取得できたとしてもアプリ間では役に立ちません。

 

(3)サービスの実装は、スタブと関係を持たせる必要があります。

サービスは、スタブを経由して呼び出されますから、スタブとメソッドの実装は関係付けられていなければなりません。

 

1.3 AIDL

スタブの作り方を説明します。

スタブを作るには、サービスのインタフェースを記述しなければなりません。Javaのinterfaceで記述できると簡単ですが、それは使えません。IDL(インタフェース定義言語、Interface Definition language)を使います。IDLは一般的な用語で、AndroidのIDLはAIDL(Android IDL)と呼ばれます。

IDLの文法の詳細は、資料を参照して下さい。その概要は付録を参照して下さい。ここでは、リモートサービスの全体の構造を知ることを第1にして、そのために必要な範囲でAIDLを説明します。

 

簡単な例を示します。ファイル名はIService_1_0.aidlです。

package com.example.service;

// Declare the interface.

interface IService_1_0{

    String echo(in String s);

}

注 この例の場合、Javaのinterfaceと違いは、メソッドの引数の前にinが付いている点です。

 

ファイル名は、インタフェース名+  ".aildl"です。Javaのソースと同じく、パッケージに対応するディレクトリの下におきます。

AIDLで記述したインターフェースをAIDLのコンパイラでコンパイルします。その結果Javaのインタフェースが生成されます。結果のファイルの名前は、入力のファイル名の拡張子をjavaに変えたものです。

このインタフェースは、Stubという名称のstaticな抽象クラス(*1)を含んでいます。このクラスの中に、AIDLで定義したインタフェースに対応する抽象メソッドが定義されています。

注*1 staticなクラスは、クラス又はインタフェースの中に記述され、その親のクラス又はインタフェースと独立に使えます。親のクラス又はインタフェースはクラスを宣言する"場所"を提供するだけです。この親子はJavaの言語レベルでは無関係ですが、論理的にグループとして扱いたい場合には、束ねる役割があるので便利です。AIDLから作られる(Javaの)interfaceはグループを束ねる役割を持っています。

 

 

 

 

 


図1.3AIDLコンパイル

 

Eclipseを使っている場合は、自動でコンパイルされます。結果はgenディレクトリに出力されます。

このコンパイル結果がStubです。

 

1.4 サービスの実装

AIDLで定義したインタフェースは、Stub抽象クラスを継承して、実装します。

 

内部クラスを使ったコードの例を示します。

    private final IService_1_0.Stub service = new IService_1_0.Stub(){

        public String echo(String s){

            return "r:" + s;

        }

    };

 

Serviceクラスを継承するクラスは、onCreate()メソッド等と共に、onBind()メソッドを実装しなければなりません。この点はローカルサービスと変わりません。onBind()メソッドの戻り値として、Stubを実装したクラスのオブジェクトを戻します。ローカルサービスの実装では、この中に"getService()"というメソッドを作成しました。この方法はローカルサービスを実装する方法の一つに過ぎません。しかし、onBind()メソッドの戻り値として、Stubを実装したクラスのオブジェクトを戻すことはリモートサービスを実装する規則です。

 

    public IBinder onBind(Intent intent) {

          return service;

    }

 

1.5 サービスの取得

サービスの利用者は、サービスの参照を取得する必要があります。これはStubのstaticなメソッドasInterface()メソッドを使います。引数はIBinder型で、onServiceConnected()メソッドが呼ばれたときの引数として渡されます。コード例を次に示します。

 

    private IService_1_0 service;

        ...

 

    public void onCreate(Bundle savedInstanceState) {

        ...

        Intent intent = new Intent(IService_1_0.class.getName());

        bindService(intent,serviceConnection,Context.BIND_AUTO_CREATE);

    }

        ...

    private ServiceConnection serviceConnection = new ServiceConnection(){

        public void onServiceConnected(ComponentName className, IBinder binder){

            service = IService_1_0.Stub.asInterface(binder);

            /*

             * 実用のプログラムでは、具体的な処理をするメソッドをここから呼ぶ。

             * 時間が掛かる処理は別スレッドで実行する。

             */

             ...

        }

        public void onServiceDisconnected(ComponentName className){

            service = null;

        }

    };

 

bindService()メソッドはローカルの場合と変わりません。

ServiceConnectionの実装の中のonServiceConnected()メソッドの中で、Stub.asInterface()メソッドが使われています。その引数は、onServiceConnected()メソッドの引数binderです。

注 このコードのIntentの引数はアクション名です。アクション名としてクラス名を使っています。これは必須の命名法ではありません。

 

実際にサービスを使う場合はつぎのコードになります。

 

            try{

                String result= service.echo("ABCxyz");

                Log.d(tag,"echo result from service = " + result);

            }catch(RemoteException e){ // RemoteException例外が起こりえます。

                Log.e(tag,"RemoteException, msg= " + e.getMessage());

            }

 

1.6 簡単な例の実装

リモートサービスの説明に使った例を実装する手順をまとめます。

①パッケージcom.example.serviceにService_1_0.aidlファイルを作成します。

②Service_1_0.aidlの中身は、説明の中の例と同じです。

③Service_1_0.aidlを保存した後、genディレクトリの下のパッケージに対応するディレクトリにService_1_0.javaのソースができていることを確認します。AIDLの記述に誤りがあると、このファイルはできません。

④Serviceクラス(名前Service_1_0)を作ります。echoは引数をそのまま戻します。

⑤次にサービスの利用者側を作ります。まず同じアプリ内で作成します。

⑥自動生成されたActivityの中に、サービスの取得の説明で使ったコードを含めます。

⑦実際にサービスを使う処理を追加します。serviceを設定した直後に、echoを呼び出す処理を入れ、結果をログに書きます。実用のプログラムでは、この部分はもっと技巧が必要です。

⑧AndroidManifest.xmlにサービスを登録します。

 

関連演習

別のアプリからサービスを使えるか確認して下さい。

①アプリのパッケージはサービスを含むものとは別にします。同じパッケージの場合、デバイス(ケイタイ)の中には、後で登録した方だけ残ります。パッケージが同じとは、パッケージ全体が一致することです。たとえば、jp.co.ichijp.co.ichi.leafは異なるパッケージです。

②新しいアプリ(プロジェクト)に、IService_1_0をコピーします。srcディレクトリの下の、パッケージに対応する場所にコピーして下さい。前の例題ではgenディレクトリの下ですが、新しいアプリでは自動生成される対象ではありませんからsrcディレクトリの下に入れます。(.aidlファイルは新しいアプリには不要です)

 

1.7 方向タグ

前の例で、AIDLで定義したメソッドの引数の型の前にinをつけました。inに類するものとして、outinoutがあります。これは方向タグ(directional tag)といわれています。引数の行く方向を示しています。inはサービスに入り込む引数、outは逆にサービスから出て行く引数、inoutは入りかつ出て行く引数です。

inとoutで各1回マーシャリングが必要です。マーシャリングはリソースを消費する処理なので、それをできるだけ少なく済ませるための指定です。

プリミティブ型には方向タグはつける必要はありません。プリミティブの方向タグは、常に"in"が想定されています。

out又はinoutの方向タグを持つためには、そのオブジェクトがParcelableインタフェースを実装していなければなりません。

方向タグがinであっても、プリミティブと変更不可(immutable)のオブジェクト以外は、サービスで行ったオブジェクトの変更は、サービスの利用者側に伝わります。方向タグは、オブジェクトそのもの(参照ではなく、参照がさしているモノ)の入出を意味しています。これは、次に述べるcall by valueとcall by referenceに関係しています。

 

1.8call by valuecall by reference

Javaの言語のレベルでは、プリミティブ型の引数はcall by valueでメソッドに渡されます。クラスのインスタンスはcall by referenceでメソッドで渡されます。

図1.4と図1.5はそれぞれ、by referenceとby valueによる呼び出しを図示したものです。プリミティブは、これらの図の中で、インスタンスを持たない場合です。

 

 

 

 

 

 

 


図1.4  by referenceによる呼び出し

 

 

 

図1.4  by referenceeによる呼び出し

 

 

 

 

 

 

 

 

 

 

 


図1.5  by valueによる呼び出し

 

call by referenceで渡された引数は、その先のオブジェクトが、呼び出し側と呼び出された側で同じなので、呼び出された側で行ったオブジェクトへの変更が、呼び出し側にも伝わります。

call by valueで渡された引数は、呼び出された側のメソッドに、同じモノをコピーします。したがって、呼び出された側で値を変更しても、呼び出し元の引数(実引数)は変更されません。呼び出し元のオブジェクトを呼び出したメソッドで変更されないようにするために使います。副作用がないので「安全」です。

リモートサービスでは、プリミティブに関してはJavaの言語と同じです。しかし、オブジェクトのうち、特別の仕掛けを持ったオブジェクトは、call by valueで呼び出しできます。

この「特別の仕掛けを持ったオブジェクト」はparcelableといいます。Parcelableインタフェースを実装するなどの約束事(プロトコル)が満たされているオブジェクトです。

図1.5に示すように、by referenceで呼び出される場合も、参照そのもの(引数そのもの)は、プリミティブ型と同じくcall by valueです。したがって、呼び出された側で参照を変更しても、呼び出し元(実引数)の参照そのものには影響しません。

by valueの場合にインスタンスの中から参照されるインスタンスはby valueとby referenceのどちらになるのか気になります。後で説明するParcelableプロトコルに従えば、原則はby valueになります。もちろん、Parcelableプロトコルの実装によります。

 

重要:方向タグinとby value(Parcelable)を組み合わせて使うことに意味があるはずですが、同じプロセス間ではinは有効でないようです。同じプロセス間では、inとby value(Parcelable)を指定した引数でもメソッド内の変更を呼び出し側のオブジェクトに反映してしまいます。異なるプロセス間で実行するとinとby value(Parcelable)の指定は期待通りに動きます。これを避けるには、同じアプリの中では、サービスを実行するprocessを明示的に指定しなければなりません。次のコードはprocessを指定する例です。

例  AndroidManifest.xmlのサービスの記述

       <service android:name=... android:process=":myservice">

 

1.9Parcelableプロトコル

Parcelableプロトコルとはつぎのことを意味します。

(1)Parselableインタフェースを実装している。

(2)Parcelable.Creater<T>を実装したクラスのインスタンスがstaticなフィールドCREATERに設定されている。

(3)AIDLをつかってParcelableであることを宣言する。

 

1.9.1 Parcelableインタフェース

次の資料に説明と例題が掲載されています。

原資料のリファレンス

http://developer.android.com/reference/android/os/Parcelable.html

 

Parcelableインタフェースで定義されているメソッドは次の通りです。

 

describeContents ()メソッド

describeContents ()

引数 なし

戻り値int

Parserableによって、マーシャルされた後の(marshalled)データの中に含まれる特殊なオブジェクトの種類を表わす整数。ビット単位の和(各ビットとオブジェクトの種類が対応している)。使える値として記述されているのは次の定数だけです。

CONTENTS_FILE_DESCRIPTOR

0の場合は特殊なオブジェクトがないことを意味します(明記されていないが推測)。

 

writeToParcel ()メソッド

writeToParcel (Parcel dest, int flags)

引数

#

引数

説明

1

dest

これを実装しているオブジェクトを書き込むPacelオブジェクト

2

flags

オブジェクトの書き方を決めるためのフラグ。0又は、PARCELABLE_WRITE_RETURN_VALUE

 

オブジェクトの内容(フィールド)をParcelオブジェクトに書き込んで、そのオブジェクトをバイトの列に変換します(*1)

注*1 flatten(平たくする)をこのように訳しておきます。オブジェクトのserializeと同義と思います。serializeの一歩手前、「平面」にするという意味が込められているかも知れません。serializeは直線と解釈できますから。

 

PARCELABLE_WRITE_RETURN_VALUE

戻り値になりえるオブジェクトであることを意味します。また、方向タグout、inoutが付く引数にもなりえるオブジェクトです。

 

readFromParcel()メソッド

このメソッドは、Parcelableインタフェースに定義されていません。しかし、リモートサービスで、Parcelableプロトコルを満たすクラスとして使うには、readFromPacel()メソッドが必要です。AIDLによるインタフェース記述から生成されるStubの中でこのメソッドを使っています(常にかどうかは不明です)。

readToParcel (Parcel source)

引数

#

引数

説明

1

source

writeToParcel()メソッドで書き込んだParcelオブジェクト。ここから、データを読み、オブジェクトにそれを設定することによって、マーシャルされたオブジェクトを通常のオブジェクトに戻す。

 

1.9.2 Parcelable.Creator<T>インタフェース

次の資料に説明と例題が掲載されています。

原資料のリファレンス

http://developer.android.com/reference/android/os/Parcelable.html

 

Parcelable.Creatorインタフェースで定義されているメソッドは次の通りです。

 

createFromParcel ()メソッド

public abstract T createFromParcel (Parcel source)

引数

#

引数

説明

1

source

Parcelableインタフェースの中で説明したreadToParcel()メソッドと同じ。通常、readToParcel()メソッドは、このメソッドのhelperとして働きます。

 

戻り値T型(パラメータで指定された型)

新しいTのインスタンスに、マーシャルされてたオブジェクト(source)のデータを設定したオブジェクト

 

newArray (int size)メソッド

public abstract T[] newArray (int size)

引数

#

引数

説明

1

size

配列の長さ

 

戻り値

Tクラスの配列。長さは引数sizeで与えられる。配列の各要素はnull。

 

Tクラスの配列を作成する。

 

1.9.3 Parcelableクラスの宣言

Pacelableインタフェースを実装したクラスをサービスのメソッドの引数又は戻り値として使うには、aidlファイルで宣言しなければなりません。そのaidlファイルに対応して生成されるJavaのインターフェースはありません。Parcelableプロトコルはaidlに関係なく実装できますから、Parcelableでない場合のように、Stubを実装する必要はありません。

Parcelableを実装したクラスは、サービスを含むアプリとそれを使う利用者を含むアプリの両方になければなりません。それらが同じアプリの中にある場合は、もちろん一つで済みます。

 

1.9.4 Parcelableクラスの例

引数で与えたDateオブジェクトの日付を、その翌日の日付に変えて、呼び出し元に戻すメソッドを持つサービスを考えます。

JavaのDateクラスでは実現できませんので独自のクラスを作ります。このクラスをParcelableにします。コードの例を次に示します。

package com.example.service;

 

import java.util.Date;

 

import android.os.Parcel;

import android.os.Parcelable;

 

public class MyDate2 implements Parcelable {

    /*

     * Parcelableにするための処理

     */

    public static final Parcelable.Creator<MyDate2> CREATOR             ①

         = new Parcelable.Creator<MyDate2>() {

        public MyDate2 createFromParcel(Parcel in){                     ②

            return new MyDate2(in);

        }

        public MyDate2[] newArray(int size) {                            ③

            return new MyDate2[size];       

        }   

    };

    public int describeContents(){                                       ④

        return 0;

       

    }

    public void writeToParcel(Parcel out, int k){                         ⑤

        out.writeLong(date.getTime());

    }

    public void readFromParcel(Parcel in){                                ⑥

       //ParcelableのメソッドではないがStubでつかっているので必要(仕様には記載していない)

        date = new Date(in.readLong());

    }

   

    private MyDate2(Parcel in){                                            ⑦

        readFromParcel(in);

    }

    /*

     * 本来のMyDate2クラスの記述

     * class to wrap Date

     */

    private Date date;                                                     ⑧

    public MyDate2(){

        date = new Date();

    }

    public long getTime(){

        return date==null?(-1L):date.getTime();

    }

    public void setTime(long t){

        this.date = new Date(t);

    }

}

 

①CREATORフィールドの設定

②createFromParcel()メソッドの実装。コンストラクタを呼び出している。

③newArray()メソッドの実装

②と③がParcelable.Creatorインタフェースの実装になります。

④describeContents()メソッドの実装

⑤writeToParcel()メソッドの実装{

④と⑤がParcelableインタフェースの実装になります。

⑥readFromParcel()メソッドの実装。ParcelableインタフェースにはないがStubの中で使われます。

⑦Parcelから通常のオブジェクトにするためのコントラクタ。必要ならpublicでもかまわない。

⑧これ以降は通常のクラスの記述。逆に言えば、Parcelableにするには①~⑦が必要です。

 

このクラスをリモートサービスで使うためにはAIDLで宣言しなければなりません。

package com.example.service;

parcelable MyDate2;// ファイル名と一致していなければならない

 

このファイル名は、MyDate2.aidlでなければなりません。

 

最後にサービスのインタフェースをAIDLで記述します。

package com.example.service;

 

import com.example.service.MyDate2;            ①

// Declare the interface.

interface IService_1_2 {

    MyDate2 tomorrow(inout MyDate2 date1, in MyDate2 date2);       ②

}

 

①importします。Parcelableクラスには、同じパッケージであってもimport文が必要です。

②inoutとin、戻り値の働きを調べるため、二つの引数と戻り値すべてMyDate2型にします。

付録

付録A AIDLの文法

資料

http://developer.android.com/guide/developing/tools/aidl.html

Designing a Remote Interface Using AIDL

 

AIDLは、1つ以上のメソッドを持つインタフェースを宣言するための簡潔な表記法です。宣言するメソッドは引数を持ち、値を戻すことができます。これらの引数および戻り値に使える型には制限があります(*1)。AIDLで記述した別のインタフェースを引数の型に使えます。しかし、たとえインタフェースと同じパッケージに定義された型であっても、組み込みでない型はすべてインポートしなければならないことに注意することが大切です。次は、AIDLがサポートしているデータ型です。

注*1 原文では任意の型が使える(can be of any type)と書いてあります。ここに記述されているような制限があるので、訳は原文と反対にしました。このぐらいの制約は「can be of any type」なのかもしれません。

 

#

I*1

説明

1

Primitive

プログラミング言語のプリミティブ型(int, boolean, etc)。

2

String

String型

3

List

要素はこの表にある型のうちのどれかでなければなりません。ジェネリック(generic)クラス(例:List<String>)でもかまいません。オブジェクトを生成する側はListインタフェースを実装した任意のクラスが使えますが、受け側の具体的なクラスは、常にArrayListです。

4

Map

要素はこの表にある型のうちのどれかでなければなりません。ジェネリック(generic)クラス(例:Map<String,Integer>)はサポートしていません。オブジェクトを生成する側はListインタフェースを実装した任意のクラスが使えますが、受け側の具体的なクラスは、常にHashMapです。

5

CharSequence

CharSequence型。ウイジェットでよく使われています。

6

AIDLで記述した型

AIDLで記述したインタフェース。常にby referenceで渡されます。

import文が常に必要

7

Parcelable

な型

Parcelableプロトコルを実装した型でby valueで渡される(*1)

import文が常に必要

注*1 import文の要・不要。

 

付録 演習

A.1 AIDLで定義した型の例

AIDLで記述するインタフェースの引数や戻り値に指定できる型は限定されています。前の例は引数と戻り値が共にString型でしたので、その型が引数と戻り値に使えました。

次はDate(java.util)を使った例です。引数で与えたDateオブジェクトの日付の翌日の日付を設定したDateオブジェクトを戻り値として戻すメソッドを考えます。

Dateクラスを使ったコード例を次に示します。

public Date tomorrow(Date date){

    // by reference呼び出しを確認するため、dateも変更したいがdateは変更できない。

    return new Date(date.getTime() + (24*60*60));

}

 

リモートサービスの引数や戻り値はDate型を使えません。MyDateというDate型を包む型を使うことにします。

インタフェースは次の通りとします。

package com.example.service;

// Declare the interface.

interface IMyDate {

    long getTime();

    void setTime(long t);

}

 

MyDateはDateに対応するlong型整数のgetterとsetterを持ちます。

実際に実装したコードは次の通りです。

public class MyDate extends IMyDate.Stub {

    private Date date;

    public MyDate(){

        date = new Date();

    }   

    public long getTime(){

        return date==null?(-1L):date.getTime();

    }

    public void setTime(long t){

        this.date = new Date(t);

    }

}

 

この後は、echoを実装したのと同じ手順で実装します。

①パッケージcom.example.serviceにService_1_1.aidlファイルを作成します。

package com.example.service;

import com.example.service.IMyDate;

interface Service_1_1 {

    IMyDate tomorrow(in IMyDate date);

}

 

②Serviceクラス(名前Service_1_1)を作ります。Dateクラスを使った例を参考に実装して下さい。

③次にサービスの利用者側を作ります。まず同じアプリ内で作成します。

⑥自動生成されたActivityの中のonCreate()メソッドからbindService()メソッドを呼びます。

⑦実際にサービスを使う処理を追加します。serviceを設定した直後に、tomorrowを呼び出す処理を入れ、結果をログに書きます。

    try{

        IMyDate today = new MyDate();

        Log.d(tag,"today to service   = " + (new Date(today.getTime())));               ①

        IMyDate result = service.tomorrow(today);

        Log.d(tag,"today from service = " + (new Date(today.getTime())));               ②

        Log.d(tag,"tomorrow result from service = " + (new Date(result.getTime())));    ③

    }catch(RemoteException e){

        Log.e(tag,"RemoteException, msg= " + e.getMessage());

    }

 

①tomorrow呼び出し前のtodayの値

②tomorrow呼び出し後のtodayの値

③tomorrowの戻り値の値

 

①と②の値から、方向性タグがinであっても、オブジェクトが変更されることを確認します。

 

メソッドtomorrowを持つサービスのAIDLで定義したインタフェースは次の通りです。

package com.example.service;

 

import com.example.service.IMyDate;

// Declare the interface.

interface IService_1_1{

    IMyDate tomorrow(in IMyDate date);                                      ①

}

 

①方向タグはinしか使えません。「inではオブジェクトが変更されない。」と誤解しないようにしてください。

 

tomorrow()メソッド実装は次の通りです。

    private final Service_1_1.Stub service = new Service_1_1.Stub(){

        public IMyDate tomorrow(IMyDate date){

            IMyDate d=null;

            try{

                long t = date.getTime() + (24*60*60*1000);

                d = new MyDate();

                d.setTime(t);                                            ①

                date.setTime(t);// by referenceの確認                   ②

            }catch(RemoteException e){

                Log.e(tag,"RemoteException msg=" + e.getMessage());

            }

            return d;

        }

    };

 

①戻り値に値を設定します。

②引数の(引数が指す)オブジェクトを変更します。①だけではなく②の変更がtomorrow()メソッド呼び出し側に伝わることを確認するための処理です。

 

関連演習

別のアプリからサービスを使えるか確認して下さい。

戻り値になるクラスは、Stubを実装したクラスであっても、サービスを含むアプリとそれを使う利用者を含むアプリの両方になければなりません。

 

付録 簡単な例のソースコード

IService_1_0.aidl

package com.example.service;

// Declare the interface.

interface IService_1_0{

    String echo(in String s);

}

Service

public class Service_1_0 extends Service {

    public IBinder onBind(Intent intent) {

          return service;

    }

 

    @Override

    public void onCreate() {

      super.onCreate();

    }

 

    @Override

    public void onDestroy() {

      super.onDestroy();

    }

 

    private final IService_1_0.Stub service = new IService_1_0.Stub(){

        public String echo(String s){

            return "r:" + s;

        }

    };

}

Activity

public class HelloService_1_0_echo extends Activity {

    private String tag = this.getClass().getSimpleName();

    private IService_1_0 service;

   

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        Intent intent = new Intent(IService_1_0.class.getName());

        bindService(intent,serviceConnection,Context.BIND_AUTO_CREATE);

    }

    public void onDestroy() {

        super.onDestroy();

        unbindService(serviceConnection);

    }

    private ServiceConnection serviceConnection = new ServiceConnection(){

        public void onServiceConnected(ComponentName className, IBinder binder){

            service = IService_1_0.Stub.asInterface(binder);

            /*

             * 実用のプログラムでは、具体的な処理をするメソッドをここから呼ぶ。

             * 時間が掛かる処理は別スレッドで実行する。

             */

            try{

                String result = service.echo("ABCxyz");

                Log.d(tag,"echo result from service = " + result);

            }catch(RemoteException e){

                Log.e(tag,"RemoteException, msg= " + e.getMessage());

            }

        }

        public void onServiceDisconnected(ComponentName className){

            service = null;

        }

    };

}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest ...>

    <application  ... >

       ...

       <service android:name=".Service_1_0"

            android:enabled="true" android:exported="true" android:process=":myservice">

            <intent-filter>

                <action android:name="com.example.service.IService_1_0" />

            </intent-filter>

       </service>

       ...

    </application>

</manifest>

 

付録 AIDLで定義した例のソースコード

IService_1_1.aidl

package com.example.service;

 

import com.example.service.IMyDate;

// Declare the interface.

interface IService_1_1{

    IMyDate tomorrow(in IMyDate date);

}

Service

public class Service_1_1 extends Service {

    private String tag = this.getClass().getSimpleName();

    public IBinder onBind(Intent intent) {

          return service;

    }

 

    @Override

    public void onCreate() {

      super.onCreate();

    }

 

    @Override

    public void onDestroy() {

      super.onDestroy();

    }

 

    private final IService_1_1.Stub service = new IService_1_1.Stub(){

        public IMyDate tomorrow(IMyDate date){

            IMyDate d=null;

            try{

                long t = date.getTime() + (24*60*60*1000);

                d = new MyDate();

                d.setTime(t);

                date.setTime(t);

            }catch(RemoteException e){

                Log.e(tag,"RemoteException msg=" + e.getMessage());

            }

            return d;

        }

    };

 

}

IMyDate.aidl

package com.example.service;

// Declare the interface.

interface IMyDate {

    long getTime();

    void setTime(long t);

}

MyDate

public class MyDate extends IMyDate.Stub {

    private Date date;

    public MyDate(){

        date = new Date();

    }   

    public long getTime(){

        return date==null?(-1L):date.getTime();

    }

    public void setTime(long t){

        this.date = new Date(t);

    }

}

Activity

public class HelloService_1_1_date extends Activity {

    private String tag = this.getClass().getSimpleName();

    private IService_1_1 service;

   

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        Intent intent = new Intent(IService_1_1.class.getName());

        bindService(intent,serviceConnection,Context.BIND_AUTO_CREATE);

    }

    public void onDestroy() {

        super.onDestroy();

        unbindService(serviceConnection);

    }

    private ServiceConnection serviceConnection = new ServiceConnection(){

        public void onServiceConnected(ComponentName className, IBinder binder){

            service = IService_1_1.Stub.asInterface(binder);

            /*

             * 実用のプログラムでは、具体的な処理をするメソッドをここから呼ぶ。

             * 時間が掛かる処理は別スレッドで実行する。

             */

            try{

                IMyDate today = new MyDate();

                Log.d(tag,"today to service   = " + (new Date(today.getTime())));

                IMyDate result = service.tomorrow(today);

                Log.d(tag,"today from service = " + (new Date(today.getTime())));

                Log.d(tag,"tomorrow result from service = " + (new Date(result.getTime())));

            }catch(RemoteException e){

                Log.e(tag,"RemoteException, msg= " + e.getMessage());

            }

        }

        public void onServiceDisconnected(ComponentName className){

            service = null;

        }

    };

}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest ...>

    <application  ... >

       ...

       <service android:name=".Service_1_1" android:enabled="true"

            android:exported="true" android:process=":myservice">

            <intent-filter>

                <action android:name="com.example.service.IService_1_1" />

            </intent-filter>

       </service>

       ...

    </application>

</manifest>

 

付録 Parcelableクラスの例のソースコード

IService_1_2.aidl

package com.example.service;

 

import com.example.service.MyDate2;

// Declare the interface.

interface IService_1_2 {

    MyDate2 tomorrow(inout MyDate2 date1, in MyDate2 date2);

}

Service

public class Service_1_2 extends Service {

    private String tag = this.getClass().getSimpleName();

    public IBinder onBind(Intent intent) {

          return service;

    }

 

    @Override

    public void onCreate() {

      super.onCreate();

    }

 

    @Override

    public void onDestroy() {

      super.onDestroy();

    }

 

    private final IService_1_2.Stub service = new IService_1_2.Stub(){

        public MyDate2 tomorrow(MyDate2 date1, MyDate2 date2){// RemoteException 不要

            long t = date1.getTime() + (24*60*60*1000);

            date1.setTime(t);

            date2.setTime(t);

            MyDate2 date0 = new MyDate2();

            date0.setTime(t);

            return date0;

        }

    };

 

}

MyDate2

public class MyDate2 implements Parcelable {

    /*

     * Parcelableにするための処理

     */

    public static final Parcelable.Creator<MyDate2> CREATOR = new Parcelable.Creator<MyDate2>() {

        public MyDate2 createFromParcel(Parcel in){

            return new MyDate2(in);

        }

        public MyDate2[] newArray(int size) {

            return new MyDate2[size];       

        }   

    };

    public int describeContents(){

        return 0;

       

    }

    public void writeToParcel(Parcel out, int k){

        out.writeLong(date.getTime());

    }

    public void readFromParcel(Parcel in){//ParcelableのメソッドではないがStubでつかっているので必要(仕様には記載していない)

        date = new Date(in.readLong());

    }

   

    private MyDate2(Parcel in){

        readFromParcel(in);

    }

    /*

     * 本来のMyDate2クラスの記述

     * class to wrap Date

     */

    private Date date;

    public MyDate2(){

        date = new Date();

    }

    public long getTime(){

        return date==null?(-1L):date.getTime();

    }

    public void setTime(long t){

        this.date = new Date(t);

    }

}

MyDate2.aidl

package com.example.service;

 

parcelable MyDate2;

Activity

package com.example.service;

 

import java.util.Date;

 

import android.app.Activity;

import android.content.ComponentName;

import android.content.Context;

import android.content.Intent;

import android.content.ServiceConnection;

import android.os.Bundle;

import android.os.IBinder;

import android.os.RemoteException;

import android.util.Log;

 

public class HelloService_1_2_date_parcelable extends Activity {

    private String tag = this.getClass().getSimpleName();

    private IService_1_2 service;

   

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        Intent intent = new Intent(IService_1_2.class.getName());

        bindService(intent,serviceConnection,Context.BIND_AUTO_CREATE);

    }

    public void onDestroy() {

        super.onDestroy();

        unbindService(serviceConnection);

    }

    private ServiceConnection serviceConnection = new ServiceConnection(){

        public void onServiceConnected(ComponentName className, IBinder binder){

            service = IService_1_2.Stub.asInterface(binder);

            /*

             * 実用のプログラムでは、具体的な処理をするメソッドをここから呼ぶ。

             * 時間が掛かる処理は別スレッドで実行する。

             */

            try{

                MyDate2 today1 = new MyDate2();

                MyDate2 today2 = new MyDate2();

                today2.setTime(today1.getTime());// today2の値をtoday1に等しく設定

                MyDate2 result = service.tomorrow(today1,today2);

                Log.d(tag,"tomorrow today1 from service = " + (new Date(today1.getTime())));

                Log.d(tag,"tomorrow today2 from service = " + (new Date(today2.getTime())));

                Log.d(tag,"tomorrow result from service = " + (new Date(result.getTime())));

            }catch(RemoteException e){

                Log.e(tag,"RemoteException, msg= " + e.getMessage());

            }

        }

        public void onServiceDisconnected(ComponentName className){

            service = null;

        }

    };

}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest ...>

    <application  ... >

       ...

       <service android:name=".Service_1_2" android:enabled="true"

            android:exported="true" android:process=":myservice">

            <intent-filter>

                <action android:name="com.example.service.IService_1_2" />

            </intent-filter>

       </service>

       ...

    </application>

</manifest>

 

以上