Androidのプロセス間通信(IPC)再入門:AIDLはいつ使うべき?Messengerとの使い分けと実装パターン

Android開発において、Serviceとの通信は避けて通れない道です。「同一アプリ内ならどうする?」「別アプリと連携するなら?」という疑問に対し、現代のAndroid開発におけるベストプラクティスをまとめました。

特に、**「とりあえずAIDLを使えばいいの?」**という疑問を持っている方に向けて、判断基準と実装パターンを解説します。

1. 同一アプリ内(同一プロセス)の場合

まず結論から言うと、同一アプリ・同一プロセス内でAIDLを使うことは推奨されません。

理由はシンプルで、**オーバーキル(過剰装備)**だからです。

AIDLは本来、異なるプロセス間でデータを転送するための仕組み(マーシャリング・アンマーシャリング)です。メモリ空間を共有している同一プロセス内でこれを行うと、無駄なオーバーヘッドが発生し、コードも複雑になります。

推奨される手法

同一プロセスであれば、以下の手法が一般的です。

例外:同一アプリでもAIDLを使うケース


2. 別アプリ(別プロセス)との通信手法

では、**「アプリA(Activity)」と「アプリB(Service)」**のように、明確にプロセスが分かれている場合はどうでしょうか。

ここでは主に以下の4つの選択肢があります。

手法 向いているケース 難易度
Intent / Broadcast 単発の命令、イベント通知(投げっぱなし)
Messenger コマンド送信、小規模な双方向通信
AIDL 高速な通信、複雑なメソッド呼び出し、マルチスレッド
Content Provider DBやファイルなどのデータ共有 中〜高

多くのケースでは、MessengerAIDL の二択になります。


3. 「Messenger」を使った実装パターン

「再生・停止」の命令を送る、進捗状況を受け取る程度であれば、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)
}

4. 「AIDL」を使った実装パターン

Messengerでは対応できない**「大量データの高速転送」「並列処理(マルチスレッド)」**が必要な場合は、AIDLの出番です。

ファイル配置のルール

AIDLにおいて最も重要なのはファイルの配置です。

提供側(アプリB)と利用側(アプリA)の両方に、全く同じパッケージ構成・ファイル名で .aidl ファイルを置く必要があります。

Java

// 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")
    }
}

注意点:AIDLファイルの共有方法

連携アプリを作る際、自動生成されたJavaファイルを渡してはいけません。

**「生成前の .aidl ファイルそのもの」**を共有し、利用側(アプリA)の環境でビルド(Rebuild)してもらうのが正しい手順です。


5. ハマりポイント:パッケージの可視性(Android 11+)

Android 11 (API 30) 以降では、アプリAからアプリBのサービスを見つけるために、AndroidManifest.xml<queries> の記述が必須になりました。

XML

<queries>
    <package name="com.example.appB" />
</queries>

これがないと、bindServiceが失敗したり、Serviceが見つからない扱いになります。

まとめ

要件に合わせて、適切な通信手段を選定しましょう。まずはMessengerで要件を満たせないか検討し、不足があればAIDLにステップアップするのがおすすめです。