Android Service 徹底解説:基本から AIDL によるプロセス間通信まで

Android の四大コンポーネントのひとつである Service は、UI に依存せずにバックグラウンドで長時間動作する処理を実行するためのコンポーネントです。ただし、Service はメインスレッドで動作するため、その中で時間のかかる処理(ネットワークアクセスや大規模な計算など)を直接行うと、メインスレッドをブロックして ANR (Application Not Responding) を引き起こす可能性があります。そのため、Service 内で時間がかかる処理を行う必要がある場合は、必ず子スレッドを作成して実行してください。

本記事のサンプルコードは、各セクションの説明に沿って掲載しています。

1. Service の基本

Android Studio で開発する場合、Service の起動方法は主に2つあります。

  1. startService() / stopService() による起動と停止
  2. bindService() / unbindService() によるバインドとアンバインド

1.1 startService 方式

最もシンプルな方法です。Activity から直接 Service を起動します。

AndroidManifest.xml への登録

<service android:name=".SimpleStartService">
    <intent-filter>
        <action android:name="com.example.service.START"/>
    </intent-filter>
</service>

MainActivity でのサービス起動と停止

Intent serviceIntent = new Intent();
serviceIntent.setAction("com.example.service.START");
// Android 5.0 以降、暗黙的な Intent で Service を起動する場合は setPackage が必要
serviceIntent.setPackage(getPackageName());

// サービスを開始
startService(serviceIntent);

// サービスを停止
stopService(serviceIntent);

注意: Android 5.0 以降では、Service の Intent は明示的でなければなりません。上記のように setPackage を追加するか、new Intent(this, SimpleStartService.class) のようにクラスを指定してください。

SimpleStartService.java

public class SimpleStartService extends Service {
    private boolean stopFlag = false;
    private static final String TAG = "SimpleService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate");
        // 子スレッドでカウント処理
        new Thread(() -> {
            int count = 0;
            while (!stopFlag) {
                Log.i(TAG, "count: " + count++);
                SystemClock.sleep(1000);
            }
        }).start();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand");
        // システムによって強制終了された場合、再作成する
        return Service.START_STICKY;
    }

    @Override
    public void onDestroy() {
        stopFlag = true;
        Log.i(TAG, "onDestroy");
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

1.2 bindService 方式

bindService は、サービスとバインドしたコンポーネント(通常は Activity)との間で通信を行いたい場合に使用します。バインドしたコンポーネントが破棄されるとサービスも自動的に停止します。

AndroidManifest.xml への登録

<service android:name=".BindService">
    <intent-filter>
        <action android:name="com.example.service.BIND"/>
    </intent-filter>
</service>

MainActivity でのバインドとアンバインド

private BindService.MyBinder myBinder;
private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i("tag", "connection success");
        myBinder = (BindService.MyBinder) service;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.e("tag", "異常終了時にのみ呼ばれます(自らアンバインドした場合は呼ばれません)");
    }
};

// バインド
bindService(bindIntent, connection, Service.BIND_AUTO_CREATE);

// アンバインド
unbindService(connection);

BindService.java

public class BindService extends Service {
    private boolean stopFlag = false;
    private int count = 0;
    private static final String TAG = "BindService";

    public class MyBinder extends Binder {
        public int getCount() {
            return count;
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind");
        return new MyBinder();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate");
        new Thread(() -> {
            while (!stopFlag) {
                Log.i(TAG, "count: " + count++);
                SystemClock.sleep(1000);
            }
        }).start();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, "unbind");
        return false;
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy");
        stopFlag = true;
        super.onDestroy();
    }
}

bindService 方式の注意点

  • onStartCommand は呼ばれません(バインドのみのサービスでは実装不要)。
  • クライアントが unbindService を呼ぶと onUnbindonDestroy の順に呼ばれます。

1.3 IntentService の利用

Service の制限(メインスレッドで動く)を解消するために、IntentService というサブクラスが用意されています。IntentService は内部でワーカースレッドを管理し、順次 Intent を処理します。処理が完了すると自動的に停止します。

AndroidManifest.xml

<service android:name=".SampleIntentService" />

MainActivity

Intent intentServiceIntent = new Intent(this, SampleIntentService.class);
startService(intentServiceIntent);

SampleIntentService.java

public class SampleIntentService extends IntentService {
    public SampleIntentService() {
        super("SampleIntentService"); // ワーカースレッド名を指定
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        // このメソッドはワーカースレッドで実行される
        long endTime = System.currentTimeMillis() + 20 * 1000;
        while (System.currentTimeMillis() < endTime) {
            Log.i("tag", "残り: " + ((endTime - System.currentTimeMillis()) / 1000) + "秒");
            SystemClock.sleep(1000);
        }
    }
}

IntentService の利点

  • ワーカースレッドで処理されるため、メインスレッドをブロックしない。
  • 複数の Intent はキューイングされ、順次処理される。
  • すべての Intent 処理後、自動的に停止する。

1.4 基本まとめ

項目 startService bindService
ライフサイクル 呼び出し元が終了してもサービスは継続 呼び出し元(クライアント)がなければサービスも終了
通信 不可 可(Binder を経由)
複数起動時 onCreate は1回、onStartCommand が毎回呼ばれる onCreate、onBind は1回のみ

2. プロセス間通信 (AIDL)

AIDL(Android Interface Definition Language)を使うと、異なるプロセス(別アプリ)の Service と通信できます。AIDL は内部で Binder を使用しており、bindService ベースで動作します。

2.1 AIDL ファイルの作成

プロジェクトに AIDL ファイルを追加します。Android Studio では、パッケージを右クリック → New → AIDL → AIDL File で作成できます。生成された .aidl ファイルにはインターフェースを定義します。

シンプルな AIDL の例

ICat.aidl

package com.example.service;

interface ICat {
    String getColor();
    double getWeight();
}

作成後、メニューから Build → Make Project を実行すると、build/generated/source/aidl に Java のスタブが生成されます。

2.2 サービス側の実装(サーバー)

AndroidManifest.xml への登録

<service android:name=".CatService">
    <intent-filter>
        <action android:name="com.example.service.CAT"/>
    </intent-filter>
</service>

CatService.java

public class CatService extends Service {
    private Timer timer = new Timer();
    private String color;
    private double weight;

    private final ICat.Stub binder = new ICat.Stub() {
        @Override
        public String getColor() throws RemoteException {
            return color;
        }

        @Override
        public double getWeight() throws RemoteException {
            return weight;
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                int rand = (int)(Math.random() * 4);
                String[] colors = {"black", "blue", "red", "yellow"};
                double[] weights = {18.2, 19.8, 10.0, 28.6};
                color = colors[rand];
                weight = weights[rand];
            }
        }, 0, 1000);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    @Override
    public void onDestroy() {
        timer.cancel();
        super.onDestroy();
    }
}

2.3 クライアント側の実装(別アプリ)

クライアントアプリには、サーバーと同じパッケージ構造で AIDL ファイルをコピーし、Make Project を行います。

MainActivity.java(クライアント)

private ICat iCat;
private ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        iCat = ICat.Stub.asInterface(service);
        Toast.makeText(MainActivity.this, "接続成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Toast.makeText(MainActivity.this, "切断されました", Toast.LENGTH_SHORT).show();
    }
};

private void bind() {
    Intent intent = new Intent();
    intent.setAction("com.example.service.CAT");
    // サーバーアプリのパッケージ名を指定
    intent.setPackage("com.example.server");
    bindService(intent, conn, Service.BIND_AUTO_CREATE);
}

private void fetchData() {
    if (iCat != null) {
        try {
            String color = iCat.getColor();
            double weight = iCat.getWeight();
            Toast.makeText(this, "color: " + color + ", weight: " + weight, Toast.LENGTH_LONG).show();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

2.4 カスタムデータ型を使用した AIDL

AIDL は String、int、List、Map などの基本型に加え、Parcelable を実装したクラスも利用できます。

手順

  1. Parcelable インターフェースを実装した Java クラスを作成(例:Person, Pet)。
  2. 同名の .aidl ファイルに parcelable 宣言を追加。
  3. インターフェース AIDL ファイルでカスタム型を使用。

Person.java の例

public class Person implements Parcelable {
    private int id;
    private String name;

    // コンストラクタ、getter/setter は省略

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(name);
    }

    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel source) {
            return new Person(source.readInt(), source.readString());
        }

        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };
}

Person.aidl

package com.example.service.aidl;
parcelable Person;

Pet.aidl も同様。

IPet.aidl

package com.example.service.aidl;
import com.example.service.aidl.Person;
import com.example.service.aidl.Pet;

interface IPet {
    List<Pet> getPets(in Person owner);
}

重要: カスタム型を使う場合は、各 Parcelable クラスに対応する .aidl ファイル(Person.aidl, Pet.aidl)を同じパッケージに配置し、parcelable と宣言してください。Make Project 前にこれらを用意しないとビルドエラーになります。

ComplexAidlService.java(サーバー)

public class ComplexAidlService extends Service {
    private static Map<Person, List<Pet>> petMap = new HashMap<>();
    static {
        petMap.put(new Person(1, "tanaka"), Arrays.asList(new Pet("dog", 5.0f)));
        petMap.put(new Person(2, "suzuki"), Arrays.asList(new Pet("cat", 3.2f)));
    }

    private IPet.Stub binder = new IPet.Stub() {
        @Override
        public List<Pet> getPets(Person owner) throws RemoteException {
            return petMap.get(owner);
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}

クライアント側

クライアントアプリにもサーバーと同じ AIDL ファイルと Parcelable クラス(同じパッケージ名で)を配置します。

private void fetchData() {
    if (iPet != null) {
        try {
            List<Pet> pets = iPet.getPets(new Person(1, "tanaka"));
            // pets を表示
        } catch (RemoteException e) { }
    }
}

2.5 応用:onStartCommand の戻り値と Intent の null

onStartCommand の戻り値として START_STICKYSTART_NOT_STICKYSTART_REDELIVER_INTENT があります。システムによってサービスが強制終了された後の再作成時に、最後の Intent が渡されるかどうかが異なります。また、サービスが起動中に startService が呼ばれると、onStartCommand の Intent は null になる可能性があるため、注意が必要です。

AIDL の方向タグ(in, out, inout)についても理解しておくと便利です。in はクライアントからサーバーへ、out はサーバーからクライアントへ、inout は双方向です。デフォルトは in です。


サンプルコードは各説明中に含まれています。必要に応じて、実機やエミュレータで動作を確認してください。

タグ: Android service intentservice aidl parcelable

5月19日 05:56 投稿