Android開発において、Serviceとの通信は避けて通れない道です。「同一アプリ内ならどうする?」「別アプリと連携するなら?」という疑問に対し、現代のAndroid開発におけるベストプラクティスをまとめました。
特に、**「とりあえずAIDLを使えばいいの?」**という疑問を持っている方に向けて、判断基準と実装パターンを解説します。
まず結論から言うと、同一アプリ・同一プロセス内でAIDLを使うことは推奨されません。
理由はシンプルで、**オーバーキル(過剰装備)**だからです。
AIDLは本来、異なるプロセス間でデータを転送するための仕組み(マーシャリング・アンマーシャリング)です。メモリ空間を共有している同一プロセス内でこれを行うと、無駄なオーバーヘッドが発生し、コードも複雑になります。
同一プロセスであれば、以下の手法が一般的です。
Binderの拡張: onBind でBinderを返し、Activityから直接Serviceのメソッドを叩く。
ViewModel / LiveData / Flow: Serviceの状態を監視する現代的なアーキテクチャ。
EventBus / LocalBroadcastManager: イベント通知。
AndroidManifest.xml で android:process=":remote" を指定して、意図的にプロセスを分けている場合。
将来的にService部分を別アプリとして切り出す計画がある場合。
では、**「アプリA(Activity)」と「アプリB(Service)」**のように、明確にプロセスが分かれている場合はどうでしょうか。
ここでは主に以下の4つの選択肢があります。
| 手法 | 向いているケース | 難易度 |
|---|---|---|
| Intent / Broadcast | 単発の命令、イベント通知(投げっぱなし) | 低 |
| Messenger | コマンド送信、小規模な双方向通信 | 中 |
| AIDL | 高速な通信、複雑なメソッド呼び出し、マルチスレッド | 高 |
| Content Provider | DBやファイルなどのデータ共有 | 中〜高 |
多くのケースでは、Messenger か AIDL の二択になります。
「再生・停止」の命令を送る、進捗状況を受け取る程度であれば、AIDLよりもMessengerが圧倒的に楽です。
Handlerベースで動作するため、スレッドセーフ(順番に処理される)である点もメリットです。
Messengerは「手紙(Message)を受け取る窓口(Handler)」を相手に渡す仕組みです。
受信側:アプリB(Service)
replyTo を使って、相手に返信を送ります。
Kotlin
class MyMessengerService : Service() {
private val incomingHandler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
1 -> { // リクエスト受信
val data = msg.data.getString("key")
// 返信
val replyTo = msg.replyTo
val replyMsg = Message.obtain(null, 2)
replyMsg.data = Bundle().apply { putString("res", "受信しました: $data") }
replyTo.send(replyMsg)
}
}
}
}
private val messenger = Messenger(incomingHandler)
override fun onBind(intent: Intent?): IBinder? = messenger.binder
}
送信側:アプリA(Activity)
自分のMessenger(返信用)を作成し、msg.replyTo にセットして送信します。
Kotlin
// 返受信用
private val clientMessenger = Messenger(object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
// アプリBからの返信を処理
}
})
private fun sendToService() {
val msg = Message.obtain(null, 1)
msg.replyTo = clientMessenger // ★ここがポイント
serviceMessenger?.send(msg)
}
Messengerでは対応できない**「大量データの高速転送」や「並列処理(マルチスレッド)」**が必要な場合は、AIDLの出番です。
AIDLにおいて最も重要なのはファイルの配置です。
提供側(アプリB)と利用側(アプリA)の両方に、全く同じパッケージ構成・ファイル名で .aidl ファイルを置く必要があります。
src/main/aidl/com/example/common/IMyService.aidlJava
// IMyService.aidl
package com.example.common;
interface IMyService {
String toUpperCase(String text);
int add(int a, int b);
}
受信側:アプリB(Service)
自動生成された Stub クラスを継承して実装します。
Kotlin
class MyAidlService : Service() {
private val binder = object : IMyService.Stub() {
override fun toUpperCase(text: String?): String {
return text?.uppercase() ?: ""
}
override fun add(a: Int, b: Int): Int = a + b
}
override fun onBind(intent: Intent?): IBinder = binder
}
送信側:アプリA(Activity)
asInterface を使ってBinderを変換し、メソッドとして呼び出します。
Kotlin
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val myService = IMyService.Stub.asInterface(service)
// 普通のメソッドのように呼べる
val result = myService.toUpperCase("hello")
}
}
連携アプリを作る際、自動生成されたJavaファイルを渡してはいけません。
**「生成前の .aidl ファイルそのもの」**を共有し、利用側(アプリA)の環境でビルド(Rebuild)してもらうのが正しい手順です。
Android 11 (API 30) 以降では、アプリAからアプリBのサービスを見つけるために、AndroidManifest.xml に <queries> の記述が必須になりました。
XML
<queries>
<package name="com.example.appB" />
</queries>
これがないと、bindServiceが失敗したり、Serviceが見つからない扱いになります。
同一アプリ内 → Binder継承 または ViewModel(AIDLは不要)
別アプリ:簡単なコマンド → Messenger(実装が楽、スレッドセーフ)
別アプリ:複雑・高速 → AIDL(パフォーマンス重視、ファイル共有が必要)
要件に合わせて、適切な通信手段を選定しましょう。まずはMessengerで要件を満たせないか検討し、不足があればAIDLにステップアップするのがおすすめです。