Android の四大コンポーネントのひとつである Service は、UI に依存せずにバックグラウンドで長時間動作する処理を実行するためのコンポーネントです。ただし、Service はメインスレッドで動作するため、その中で時間のかかる処理(ネットワークアクセスや大規模な計算など)を直接行うと、メインスレッドをブロックして ANR (Application Not Responding) を引き起こす可能性があります。そのため、Service 内で時間がかかる処理を行う必要がある場合は、必ず子スレッドを作成して実行してください。
本記事のサンプルコードは、各セクションの説明に沿って掲載しています。
1. Service の基本
Android Studio で開発する場合、Service の起動方法は主に2つあります。
- startService() / stopService() による起動と停止
- 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を呼ぶとonUnbind→onDestroyの順に呼ばれます。
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 を実装したクラスも利用できます。
手順
- Parcelable インターフェースを実装した Java クラスを作成(例:
Person,Pet)。 - 同名の
.aidlファイルにparcelable宣言を追加。 - インターフェース 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_STICKY、START_NOT_STICKY、START_REDELIVER_INTENT があります。システムによってサービスが強制終了された後の再作成時に、最後の Intent が渡されるかどうかが異なります。また、サービスが起動中に startService が呼ばれると、onStartCommand の Intent は null になる可能性があるため、注意が必要です。
AIDL の方向タグ(in, out, inout)についても理解しておくと便利です。in はクライアントからサーバーへ、out はサーバーからクライアントへ、inout は双方向です。デフォルトは in です。
サンプルコードは各説明中に含まれています。必要に応じて、実機やエミュレータで動作を確認してください。