Android - AIDLを利用して、Remote Serviceの実装

AndroidでRemote Serviceを実装する方法はAIDL(Android Interface Definition Language)またはMessengerを利用する方法があります。 Messengerは、内部的にAIDLを利用して、IPC(Inter-process communication)を実行します。

AIDLとMessengerの違いは次のとおりです。

  • AIDL:AndroidはBinderを使用して、IPCを実行します。 AIDLは、インターフェイスの定義であり、定義されたインタフェースに必要なBinderコードをコンパイル過程で自動的に生成されます

AIDLでRemote serviceを実装すると、マルチスレッドで複数の要求を同時に要求したり、処理することができます。もちろんスレッドに安全に実装する必要があります。

  • Messenger:内部的にAIDLを利用して実装されています。 AIDLを使用するのが面倒なので簡単に使用することができるMessengerクラスを提供しています

外部から伝達されたデータは、単一のスレッドで処理されるため、スレッドの安全に実装する必要はありません。ただしHandlerで順次Taskが処理されるため、同時に処理することはできません。

この記事では、AIDLを利用して、Remote Serviceを実装する方法を紹介します。

この記事で使用されたサンプルは、GitHubにあります。

Remote Serviceの定義

Serviceを実装して実行するには、まず、次のように AndroidManifest.xmlに宣言する必要があります。 このように宣言すると、Local Serviceとして実行されます。つまり、このServiceは、Applicationのプロセスと同じプロセスで実行されます。

<application>
    ...
    <service android:name=".LocalService" />

</application>

Remote Serviceは、Applicationと、他のプロセスで実行され、次のように android:process属性にプロセス名を入力すると、その名前でプロセスが実行され、サービスは、このプロセスで動作します。

<application>
    ...
    <service android:name=".RemoteService" android:process=":remote" />

</application>

サービスを実行した後に adb shellコマンドでプロセスのリストを確認してみると次のように二つのプロセスを見つけることができます。 Remote Serviceは com.example.remoteservice:remoteプロセスで動作します。

$ adb shell ps -ef | grep remoteservice
u0_a140       3560  1776 4 22:32:12 ?     00:00:00 com.example.remoteservice
u0_a140       3658  1776 2 22:32:13 ?     00:00:00 com.example.remoteservice:remote

サービスの実装

サービスは、次のように実装することができます。コードから見える IRemoteServiceIRemoteServiceCallbackはAIDLで自動生成されたクラスです。 AIDLでインターフェイスを定義する方法は、後ろから見ていきます。

class RemoteService : Service() {

    private val binder = object : IRemoteService.Stub() {
        override fun addCallback(callback: IRemoteServiceCallback): Boolean {
            return true;
        }

        override fun removeCallback(callback: IRemoteServiceCallback?): Boolean {
            return true;
        }
    }

    override fun onBind(p0: Intent?): IBinder? {
        return binder
    }

}

AIDLインターフェイスの定義

AIDLは、プロセス間通信のためのインタフェースが定義するときに使用します。 AIDLファイルを作成すると、定義されたインタフェースに基づいてバインダーのコードが自動生成されます。

アンドロイドスタジオで [File] -> [New] -> [AIDL] -> [AIDL File]を押すと、次のようにAIDLファイルを作成することができます。 android studio aidl

IRemoteServiceIRemoteServiceCallbackを作成すると、次のようにaidlフォルダにファイルが作成されます。 android studio aidl file

ファイルを開いてみると、Javaで実装されたサンプルコードが入力されています。 AIDLは、Javaを使用してインターフェイスを定義する必要があります。

これで、サンプルコードを消し、インタフェースを定義してみましょう。

IRemoteService.aidlは次のようにコードを変更しています。

package com.example.remoteservice;

import com.example.remoteservice.IRemoteServiceCallback;

interface IRemoteService {
    boolean addCallback(IRemoteServiceCallback callback);
    boolean removeCallback(IRemoteServiceCallback callbac);
}

IRemoteServiceCallback.aidlは次のようにコードを変更しています。

package com.example.remoteservice;

interface IRemoteServiceCallback {
    void onItemAdded(String name);
    void onItemRemoved(String name);
}

AIDLファイルを実装した、すぐコードが自動生成されません。 [Build] -> [Make Project]を押すと、AIDLがコンパイルされ、コードが自動生成されます。

自動生成されたコードは、次のパスに作成されます。

app/build/generated/aidl_source_output_dir/debug/out/com/example/remoteservice$ ls
IRemoteServiceCallback.java  IRemoteService.java

次のように自動生成されたコードをGitHubに上げておきました。デバッグ目的のために、ログにも追加しました。気とここで簡単にコードを確認することができます。

Remote Serviceの実装

以下は、私の実装したRemoteServiceの完全なコードです。 ここで重要なことは、binderです。 AppこのServiceにバインドするときbinderを返します。 Appはバインダーに定義されたインタフェースを使用して、Serviceと通信することができます。

class RemoteService : Service() {
    companion object {
        const val TAG = "RemoteService"
    }

    val listeners = arrayListOf<IRemoteServiceCallback>()
    private val binder = object : IRemoteService.Stub() {
        override fun addCallback(callback: IRemoteServiceCallback): Boolean {
            Log.d(TAG, "Add callback : $callback")
            listeners.add(callback)
            return true;
        }

        override fun removeCallback(callback: IRemoteServiceCallback?): Boolean {
            Log.d(TAG, "Remove callback : $callback")
            listeners.remove(callback)
            return true;
        }
    }

    override fun onBind(p0: Intent?): IBinder? {
        handler.sendEmptyMessageDelayed(0, 5000)
        return binder
    }

    private val handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            listeners.forEach { listener ->
                listener.onItemAdded("new item");
                listener.onItemRemoved("old item");
            }
            sendEmptyMessageDelayed(0, 5000)
        }
    }
}

Binderの実装

上記のコードでBinderの実装コードをもう少し詳しく見ると、Binderは IRemoteService.Stubを継承します。 IRemoteService.StubIRemoteService.aidlファイルがコンパイルされるときに自動的に生成されるクラスです。

次のコードは、匿名クラスで実装したが、クラスを定義し、それを生成してもされます。

private val binder = object : IRemoteService.Stub() {
    override fun addCallback(callback: IRemoteServiceCallback): Boolean {
        Log.d(TAG, "Add callback : $callback")
        listeners.add(callback)
        return true;
    }

    override fun removeCallback(callback: IRemoteServiceCallback?): Boolean {
        Log.d(TAG, "Remove callback : $callback")
        listeners.remove(callback)
        return true;
    }
}

ServiceでAppでイベント配信

IRemoteServiceのバインダーをAppに渡すと、Appのみサービスを呼び出すことができます。 つまり、サービスはAppにどのイベントも渡すことができません。

もしAppがServiceにバインダーを伝達なら、ServiceはAppにイベントを渡すことができます。 上記で定義された IRemoteService.StubaddCallback()はAppがServiceにIRemoteServiceCallbackオブジェクトを提供するために作成されたメソッドです。

もしバインディングがされると、サービスがAppに向かって5秒間隔で onItemAdded()onItemRemoved()を呼び出すようにしました。

override fun onBind(p0: Intent?): IBinder? {
    handler.sendEmptyMessageDelayed(0, 5000)
    return binder
}

private val handler = object : Handler(Looper.getMainLooper()) {
    override fun handleMessage(msg: Message) {
        listeners.forEach { listener ->
            listener.onItemAdded("new item");
            listener.onItemRemoved("old item");
        }
        sendEmptyMessageDelayed(0, 5000)
    }
}

上記の例は、単にServiceからAppのバインダーを用いてインターフェースを呼び出すことを表示するためのものです。

実際には、このようなパターンで使用されていないことがあります。単に参考のみください。

Appでサービスバインダー得る

次のコードは、Appのサービスにバインドして、 IRemoteServiceバインダーを取得します。 そしてAppは、このバインダーを介して IRemoteServiceCallbackバインダーをサービスに転送します。

class MainActivity : AppCompatActivity() {

    companion object {
        const val TAG = "MainActivity"
    }

    private var bound = false;
    private var iRemoteService: IRemoteService? = null

    private var callback = object : IRemoteServiceCallback.Stub() {
        override fun onItemAdded(name: String?) {
            Log.d(TAG, "onItemAdded: $name")
            bound = true
        }

        override fun onItemRemoved(name: String?) {
            Log.d(TAG, "onItemRemoved: $name")
            bound = false
        }
    }

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            Log.d(TAG, "onServiceConnected: $className")
            iRemoteService = IRemoteService.Stub.asInterface(service)
            iRemoteService!!.addCallback(callback)
        }

        override fun onServiceDisconnected(className: ComponentName) {
            Log.d(TAG, "onServiceDisconnected: $className")
            iRemoteService = null
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onResume() {
        super.onResume()
        if (!bound) {
            val intent = Intent(this, RemoteService::class.java)
            bindService(intent, connection, Context.BIND_AUTO_CREATE);
        }
    }

    override fun onStop() {
        super.onStop()
        if (bound) {
            iRemoteService!!.removeCallback(callback)
            unbindService(connection)
        }
    }
}

バインディング、バインダー変換

サービスにバインドするコードをさらに詳しく説明します。

次のようなコードを実行すると、サービスにバインドしようとします。

val intent = Intent(this, RemoteService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE);

サービスに接続すると onServiceConnected()がcallbackされIRemoteServiceが引数として渡されます。

private var iRemoteService: IRemoteService? = null

private val connection = object : ServiceConnection {
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        Log.d(TAG, "onServiceConnected: $className")
        iRemoteService = IRemoteService.Stub.asInterface(service)
        iRemoteService!!.addCallback(callback)
    }

    override fun onServiceDisconnected(className: ComponentName) {
        Log.d(TAG, "onServiceDisconnected: $className")
        iRemoteService = null
    }
}

引数を見ると、IBinderに渡されます。 IRemoteServiceはIBinderを継承する構造のため、IBinderをIRemoteServiceに変換することができます。

IRemoteService.aidlはコンパイル時にIRemoteService.Stub.asInterface()メソッドを作成します。 次のように、このメソッドを使用してIBinderをIRemoteServiceに変換することができます。

iRemoteService: IRemoteService? = IRemoteService.Stub.asInterface(service)

確認

アプリを起動して、意図したとおりにうまく動作することをログで確認してみました。 PIDを見ればMainActivityとRemoteServiceが、他のプロセスで実行されていることを見ることができます。 そしてバインダーを介してお互いにイベントを送出することを見ることができます。

08-22 12:32:58.310  5905  5905 D MainActivity: onServiceConnected: ComponentInfo{com.example.remoteservice/com.example.remoteservice.RemoteService}
08-22 12:32:58.311  5940  5959 D RemoteService: Add callback : com.example.remoteservice.IRemoteServiceCallback$Stub$Proxy@ea9718b
08-22 12:33:03.306  5905  5924 D MainActivity: onItemAdded: new item
08-22 12:33:03.307  5905  5924 D MainActivity: onItemRemoved: old item
08-22 12:33:08.313  5905  5924 D MainActivity: onItemAdded: new item
08-22 12:33:08.313  5905  5924 D MainActivity: onItemRemoved: old item

他のアプリからのRemoteServiceにバインド

今まで自分のアプリで実装したRemote Serviceにバインドする方法を説明しました。

他のアプリで実装したサービスにバインドする方法について、より知りたい場合は、他のアプリのServiceにバインドを参照してください。

参考

Related Posts

codechachaCopyright ©2019 codechacha