AndroidのServiceの実装方法についてまとめます。
Table of Contents
1 Serviceとは
ServiceはLinux/Unixの子プロセスやdaemonに近い概念で特定のジョブを実行するコンポーネントです。
Androidではファイルのダウンロードや音楽の再生などはUI操作を阻害しないように、Activityとは別のServiceで動作させます。
2 startServiceメソッドとbindServiceメソッド
Serviceの起動方法はstartServiceメソッドを使うのとbindServiceメソッドを使う二つの方法あります。
2.1 ライフサイクルが異なる
startServiceで起動したServiceはstopServiceが呼び出されるまで、あるいはServiceでstopSelfを実行するまで動き続けます。
startService -> Service.onCreate -> Service.onStartCommand stopService (stopSelf) -> Service.onDestroy
bindServiceで起動したServiceは初回のbindServiceで生成され、バインドしているActivityがなくなると破棄されます。
(First) bindService -> Service.onCreate -> Service.onBind (Last) unbindService -> Service.onUnbind -> Service.onDestroy
startServiceで起動したServiceに対してbindServiceすることも可能です。
その場合はstopServiceかstopSelfまでServiceは動き続けます。
2.2 bindServiceはBinderクラスとIBinderインターフェースを利用できる
bindServiceを用いた場合はBinderクラスとIBinderインターフェースでActivityとServiceでデータのやり取りが可能です。
Binderクラスを拡張した独自クラスを実装し、IBinderインターフェース経由で独自クラスのデータにアクセスします。
2.3 ActivityとServiceが同一のアプリケーションでstartServiceするとServiceが破棄/再生成される
ActivityとServiceが異なるアプリケーションの場合は問題にならないのですが、同一のアプリケーションの場合はActivity終了後にServiceのインスタンスは一度破棄され、ActivityManagerによって新たなServiceのインスタンスが再生成されます。
ActivityとServiceが同一のアプリケーションにて、Serviceのインスタンスの破棄と再生成を防ぐにはServiceをstartForegroundメソッドで、Foreground Serviceとして動作させる必要が有ります。
ただし、Foreground ServiceはNotificationが表示される点に留意してください。
3 Serviceを動かすコード
ActivityとServiceが同一のアプリケーションのコードを記載します。
3.1 AndroidManifest.xml
AndroidManifest.xmlにserviceの項目を追加します。
nameに".[Serviceを継承したクラス名]"を設定します。
異なるアプリケーションのサービスを利用する場合は[パッケージ名].[Serviceを継承したクラス]を設定します。
<?xml version="1.0" encoding="utf-8"?> <manifest package="com.hiroom2.sample" xmlns:android="http://schemas.android.com/apk/res/android"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <service android:name=".SampleService" /> </application> </manifest>
3.2 startServiceメソッドを使ったコード
ActivityからstartServiceメソッドを呼ぶとServiceの onStartCommandメソッドが実行されます。
startServiceの引数にActivityのオブジェクトからServiceのクラスへの Intentを設定します。
Activity終了後にServiceのインスタンスが再生成されるのを見せる為、stopServiceはコメントアウトしています。
package com.hiroom2.sample; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; public class SampleActivity extends AppCompatActivity { private static final String gTag = "SampleActivity"; private Intent mIntent; @Override protected void onCreate(Bundle savedInstanceState) { Log.i(gTag, "onCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_sample); mIntent = new Intent(this, SampleService.class); startService(mIntent); } @Override protected void onDestroy() { Log.i(gTag, "onDestroy"); super.onDestroy(); //stopService(mIntent); } }
onStartCommandの戻り値のSTART_STICKYはServiceが停止した場合にすぐさまServiceが再起動されるようにするフラグです。再起動が不要な場合は START_NOT_STICKYを用います。
package com.hiroom2.sample; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.support.annotation.Nullable; import android.util.Log; public class SampleService extends Service { private static final String gTag = "SampleService"; @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(gTag, "onStartCommand"); return START_STICKY; } @Override public void onCreate() { Log.i(gTag, "onCreate"); super.onCreate(); } @Override public void onDestroy() { Log.i(gTag, "onDestroy"); super.onDestroy(); } @Nullable @Override public IBinder onBind(Intent intent) { Log.i(gTag, "onBind"); return null; } }
ログは以下のようになりました。
I/SampleActivity: onCreate I/SampleService: onCreate I/SampleService: onStartCommand /** Kill activity with finger swipe from recent task list. */ I/SampleActivity: onDestroy W/ActivityManager: Scheduling restart of crashed service com.hiroom2.sample/.SampleService in 1000ms I/ActivityManager: Start proc 5493:com.hiroom2.sample/u0a43 for service com.hiroom2.sample/.SampleService I/SampleService: onCreate I/SampleService: onStartCommand
途中でアプリケーションをタスクリストからスワイプで終了させると、ActivityManagerによってServiceのインスタンスが再生成されます。
ActivityManagerから呼ばれた場合、onStartComanndメソッドの引数intentはnullになります。
3.3 bindServiceメソッドを使ったコード
ActivityからbindServiceメソッドを呼ぶとServiceのonBindメソッドが実行され、Binderオブジェクトを返します。
bindServiceの引数にはIntentとServiceConnectionを設定します。
ServiceConnectionのonServiceConnectedメソッドはbindService契機で呼ばれ、引数のIBinderにはServiceのonBindメソッドの戻り値が設定されます。
このIBinderでActivityとService間の通信が可能になります。
BIND_AUTO_CREATEを使用するとバインドと同時にServiceを開始します。
他にもBIND_ABOVE_CLIENTやBIND_ALLOW_OOM_MANAGEMENT等があり、Activityに比べた実行優先度やOOMによる扱いを設定できます。
すべてのActivityからunbindServiceメソッドが呼ばれると、最後の呼び出しでServiceのonUnbindが実行され、onServiceDisconnectedメソッドが呼び出されます。
package com.hiroom2.sample; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.support.v7.app.AppCompatActivity; import android.util.Log; public class SampleActivity extends AppCompatActivity { private static final String gTag = "SampleActivity"; private Intent mIntent; private ServiceConnection mServiceConnection; @Override protected void onCreate(Bundle savedInstanceState) { Log.i(gTag, "onCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_sample); mIntent = new Intent(this, SampleService.class); mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i(gTag, "onServiceConnected"); SampleBinder sampleBinder = (SampleBinder) service; /* Communication with Service. */ Log.i(gTag, "SampleBinder.getValue() = " + sampleBinder.getValue()); } @Override public void onServiceDisconnected(ComponentName name) { Log.i(gTag, "onServiceDisconnected"); } }; bindService(mIntent, mServiceConnection, BIND_AUTO_CREATE); } @Override protected void onDestroy() { Log.i(gTag, "onDestroy"); super.onDestroy(); } }
onBindメソッドでBinderクラスを拡張したクラスのインスタンスを返します。ここでは単純にServiceが持つ整数を返しています。
package com.hiroom2.sample; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.support.annotation.Nullable; import android.util.Log; public class SampleService extends Service { private static final String gTag = "SampleService"; private int mCurrValue = 0; @Override public void onCreate() { Log.i(gTag, "onCreate"); super.onCreate(); } @Override public void onDestroy() { Log.i(gTag, "onDestroy"); super.onDestroy(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(gTag, "onStartCommand"); return START_STICKY; } @Nullable @Override public IBinder onBind(Intent intent) { Log.i(gTag, "onBind"); /* Communication with Activity. */ return new SampleBinder(mCurrValue++); } }
アプリケーションで使用する独自のデータをBinderクラスを拡張したクラスで扱い、IBinderインターフェースでアクセスします。
package com.hiroom2.samplebindservice; import android.os.Binder; public class SampleBinder extends Binder { private static final String gTag = "SampleBinder"; private int mValue; SampleBinder(int value) { mValue = value; } public int getValue() { return mValue; } }
ログは以下のとおりになります。
ActivityからServiceが起動し、Activity側のonServiceConnectedが呼ばれることがわかります。
I/SampleActivity: onCreate I/SampleService: onCreate I/SampleService: onBind I/SampleActivity: onServiceConnected I/SampleActivity: SampleBinder.getValue() = 0
3.4 startForegroundを使ったコード
ここではstartForegroundを用いて、Activityが停止しても再生成されずに動作するServiceを作成します。
ServiceのNotificationをクリックすることで、Activityを起動できるようにもします。
ActivityのonCreateでstartServiceを実行する単純なコードなので、すでにstartServiceが実行されているかを確認します。確認にはServiceのstaticなメンバを用いています。
package com.hiroom2.sample; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; public class SampleActivity extends AppCompatActivity { private static final String gTag = "SampleActivity"; @Override protected void onCreate(Bundle savedInstanceState) { Log.i(gTag, "onCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_sample); if (!SampleService.isStarted()) startService(new Intent(this, SampleService.class)); } @Override protected void onDestroy() { Log.i(gTag, "onDestroy"); super.onDestroy(); } }
NotificationをクリックするとActivityを起動できるようにするため、onStartCommandにてActivityへのIntentを作成します。
IntentをNotification必要とするPendingIntentに変更します。
NotificationはNotification.Builderで作成し、タイトル、テキスト、アイコン等の必要最低限のプロパティを設定します。
setContentIntentでクリック時に動作するPendingIntentを指定します。
必要最低限のプロパティを設定しない場合、設定したタイトルやテキストが反映されず、デフォルトのNotificationになる点に注意してください。
NotificationをstartForegroundに設定します。
package com.hiroom2.sample; import android.app.Notification; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.support.annotation.Nullable; import android.util.Log; public class SampleService extends Service { private static final String gTag = "SampleService"; private static boolean gStarted = false; public static boolean isStarted() { return gStarted; } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(gTag, "onStartCommand"); gStarted = true; Intent activityIntent = new Intent(this, SampleActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, activityIntent, 0); Notification notification = new Notification.Builder(this) .setContentTitle("Title") .setContentText("Text") .setContentIntent(pendingIntent) .setSmallIcon(R.mipmap.ic_launcher) .build(); startForeground(startId, notification); return START_STICKY; } @Override public void onCreate() { Log.i(gTag, "onCreate"); super.onCreate(); } @Override public void onDestroy() { Log.i(gTag, "onDestroy"); super.onDestroy(); gStarted = false; } @Nullable @Override public IBinder onBind(Intent intent) { Log.i(gTag, "onBind"); return null; } }
ログは以下のとおりとなります。startForegroundがない場合と比べて、Activityをスワイプで終了させてもServiceの破棄と再生成が実行されません。
I/SampleActivity: onCreate I/SampleService: onCreate I/SampleService: onStartCommand /** Kill activity with finger swipe from recent task list. */ I/SampleActivity: onDestroy /** Touch notification. */ I/ActivityManager: START u0 {cmp=com.hiroom2.sample/.SampleActivity} from uid 10046 on display 0 I/SampleActivity: onCreate
Notificationは以下のとおりとなります。