AndroidのServiceについて

AndroidのServiceの実装方法についてまとめます。

 

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のインスタンスが再生成されます。

0001_Swipe.png

 

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_CLIENTBIND_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は以下のとおりとなります。

0002_Notification.png