読者です 読者をやめる 読者になる 読者になる

リア充爆発日記

You don't even know what ria-ju really is.

Androidのバックグラウンド処理に使うServiceのまとめメモ

http://developer.android.com/guide/components/services.html
ここを読んでのメモ。

概要

  • ServiceはUIを伴わず、長時間走らせたい処理を行うコンポーネント
  • ユーザーがアプリを切り替えても、処理を走らせたままにできるし、例えばネットワークトランザクションなど、他のプロセスとのプロセス間通信もできる。※後述のとおり、アプリが死ねば(プロセスが落ちれば)、Serviceも死ぬ。
  • Serviceは基本的に2つの状態をとる
    • Started
      • ActivityなどからstartService()で起動されたら"started"状態になり無期限に動き続け、例え呼び出し元のActivityが破棄されても動き続ける。
      • 1つのオペレーションを行い、完了しても特に返り値などは返さない。なので例えばファイルのダウンロード処理などは、それが終わり次第、自分自身(Serviceプロセス自体)を終了させる必要がある。
    • Bound
  • これらは同時に使える。(あるServiceをstartServiceした上で、bindService()もできる)
  • Serviceどんなコンポーネントからも利用することができる。例え別のアプリのコンポーネントからも。(あるアプリからIntent発行で別のアプリを起動できるのと同じ)
  • とはいえ、Serviceをprivateにして他のアプリからの利用をブロックすることもできる。
  • 注意:Serviceは当該アプリケーションプロセスのメインスレッド上で動きます。Serviceは自身のスレッドを生成しませんし、特別に何かしない限り、別プロセスで動くこともありません。なので、Serviceで重い処理などを行う場合は、ちゃんと別スレッドを生成してそこで実行しましょう。

基本

  • Serviceを創るにはServiceクラス(もしくはServiceクラスを継承したクラス)を継承したサブクラスを作り、いくつかの重要なメソッドをオーバーライドする。
    • onStartCommand()
      • 上述のstartService()が呼び出された時に発火。前述のとおり、 stopSelf()で自己終了するか、stopService()を呼ぶかしない限り止まらない。バインドのみ利用する場合はオーバーライドしなくていい。
    • onBind()
      • 上述のbindService()が呼び出された時に発火。クライアント(このServiceがサーバだとして)がServiceを利用できるよう、IBinderを継承したインターフェースを返してやる必要がある。このメソッドはオーバーライド必須だけど、バインドの必要がない場合はnullを返しておけばよい。
    • onCreate()
      • Serviceが最初に作られたときに一回だけ呼ばれる。上述の2メソッドより先に呼ばれる。
    • onDestroy()
      • Serviceが終了するときに呼ばれる。スレッドやリスナーなどのゴミが残らないよう、ここできっちり処理すること。
  • Serviceはメモリ使用率が逼迫した場合にシステムによって強制終了される場合がある。ユーザーが表示しているアクティビティにバインドされたServiceは通常より強制終了されにくい。また後述するフォアグラウンド実行されているServiceはほとんど強制終了されることはない。
    • 起動時間が長いServiceほど、強制終了されやすくなる。
    • 強制終了されてもリソース状況に余裕がでてくればシステムによって再起動されるので、それに応じた実装をしておくこと。
Manifestに記述
  • Acitivityと同じうように、タグの子要素としてタグを追加する
<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>
  • name以外にも設定できる要素はあるけど、必須なのはnameだけ。この名前は一度アプリを公開したら変えるべきではない。※privateにしてあれば問題ないのかな。詳しくはこちらを。
  • ActivityのようにIntentフィルタも設定できるが、他のアプリに利用させる予定がなければ設定不要。
  • さらにandroid:exportedをfalseにしておけば、確実に他アプリからアクセスできなくなる。これはIntentフィルタを設定していても有効。

Started Serviceを作る

  • 前述のとおりActivityなどからstartService()を呼び出して起動する。呼び出し時にはIntentを渡せる(onStartCommand()で受け取る)
  • 歴史的経緯で、"started"なServiceを作るのに継承できるクラスは、ServiceクラスとIntentServiceクラスの2つがある。IntentServiceクラスはServiceクラスを継承しており、マルチスレッドな対応が必要ない場合にオススメ。IntentServiceの利用にあたっては onHandleIntent()をオーバーライドするだけ。
IntentServiceを継承する
  • マルチスレッドな対応が必要なケースはあまりないので、だいたいの場合においてIntentServiceを使うのが吉。
  • IntentServiceは以下のことをしてくれる
    • メインスレッドから分離して、onStartCommand()で渡されたIntentを処理してくれるワーカスレッドを作る
    • onHandleIntent()にIntentをひとつずつ渡してくれるキューを作る。同時実行はできないけど、これでマルチスレッド処理に困らくてよい。
    • すべての処理が終ったら自動的にServiceを終了してくれる。stopSelf()を呼ぶ必要なし。
    • nullを返すだけのonBind()実装を提供。
    • きたIntentをonHandleIntent()に渡してくれるonStartCommand()のデフォルト実装を提供
  • というわけで、だいたいの場合においてonHandleIntent()を実装するだけでよい。サンプルは以下。
public class HelloIntentService extends IntentService {

  /** 
   * A constructor is required, and must call the super IntentService(String)
   * constructor with a name for the worker thread.
   */
  public HelloIntentService() {
      super("HelloIntentService");
  }

  /**
   * The IntentService calls this method from the default worker thread with
   * the intent that started the service. When this method returns, IntentService
   * stops the service, as appropriate.
   */
  @Override
  protected void onHandleIntent(Intent intent) {
      // Normally we would do some work here, like download a file.
      // For our sample, we just sleep for 5 seconds.
      long endTime = System.currentTimeMillis() + 5*1000;
      while (System.currentTimeMillis() < endTime) {
          synchronized (this) {
              try {
                  wait(endTime - System.currentTimeMillis());
              } catch (Exception e) {
              }
          }
      }
  }
}
  • onHandleIntent()以外( onCreate(), onStartCommand(), or onDestroy())のメソッドをオーバーライドするときは、super側の実装を呼び出してreturnすることを忘れないようにする。でないとIntentServiceがワーカースレッドの管理ができなくなる。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
    return super.onStartCommand(intent,flags,startId);
}
Serviceを継承する
  • Service内で一度に複数の処理を実行したければ、コード量は多くなるがServiceクラスを継承する必要がある。
  • 以下のコードは前項で紹介したIntentServiceのサンプルコードとまったく同じ動きをする。つまり、ワーカスレッドを作り、そこに1つずつ処理を渡していく(IntentServiceとの比較のためのコードなのでServiceを継承するメリットはあえて殺してある)。コード量を比較してみよう。
public class HelloService extends Service {
  private Looper mServiceLooper;
  private ServiceHandler mServiceHandler;

  // Handler that receives messages from the thread
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          long endTime = System.currentTimeMillis() + 5*1000;
          while (System.currentTimeMillis() < endTime) {
              synchronized (this) {
                  try {
                      wait(endTime - System.currentTimeMillis());
                  } catch (Exception e) {
                  }
              }
          }
          // Stop the service using the startId, so that we don't stop
          // the service in the middle of handling another job
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // Start up the thread running the service.  Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block.  We also make it
    // background priority so CPU-intensive work will not disrupt our UI.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();
    
    // Get the HandlerThread's Looper and use it for our Handler 
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the
      // start ID so we know which request we're stopping when we finish the job
      Message msg = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      mServiceHandler.sendMessage(msg);
      
      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }
  
  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); 
  }
}
  • onStartCommand()ではintをreturnする必要がある(IntentServiceはデフォルト実装がされている。変更はできる)。その値によってシステムが(メモリ不足などで)Serviceをkillしたときのあとの挙動が変わる。
  • returnすべきintは以下の定数で提供されている。
    • START_NOT_STICKY
      • onStartCommand()がreturnされたあとにシステムにkillされても、未処理のIntentがなければServiceを再生成しない。不要なServiceを走らせず、未処理のジョブを再実行させるのにもっとも安全なオプション。
    • START_STICKY
      • onStartCommand()がreturnされたあとにシステムにkillされると、Serviceを再生成しonStartCommand()を呼ぶ。が、最後のIntent(killされたときに処理中のIntent)は処理されない。nullのIntentとともにonStartCommand()が呼ばれるが、Serviceを実行前のIntentがあればそれは処理される。このオプションはメディアプレイヤーなどの、起動後、(曲再生などの)命令待ちになるような処理に向いている。(僕はここの説明があまり理解できてないが、たぶん「曲の再生命令」をここの説明のIntentに当てはめるとしっくりくるのでそういうことだと思う)
    • START_REDELIVER_INTENT
      • onStartCommand()がreturnされたあとにシステムにkillされると、Serviceを再生成し、最後に処理されたIntentとともにonStartCommand()が呼ばれる。ファイルダウンロードのようにレジュームが必要な処理に向いている。
Serviceの実行
  • Serviceの実行はIntentを投げることによって行う。Intentが投げられるとonStartCommand()が呼ばれる。(onStartCommand()はゼッタイ直接呼ぶなよ!)
  • さっきのサンプルServiceを呼ぶならこんな感じ
Intent intent = new Intent(this, HelloService.class);
startService(intent);
  • startService()はすぐreturnされ(ブロックされない)、システムはonStartCommand()を即座に呼び出す。Serviceがまだ生成されてない場合はonCreate()が先に呼び出されてからonStartCommand()が呼ばれる。
  • バインドを利用しない場合は、ServiceとのコミュニケーションはstartService()でのみ行われるが、もしServiceから返り値を受け取りたい場合は、PendingIntentを発行し、Serviceからのブロードキャストを受け取るという方法もある。
  • 起動したServiceはいくらでも処理を受け付けるが、Serviceを停止するのはstopSelf()なりstopService()なりを1回呼べばいい。
Serviceを停止する
  • (メモリ逼迫などでやむを得ない場合以外は)システムはServiceを停止したりしないので、起動されたServiceは自分自身でライフサイクル管理をしなければいけない(ちゃんとService停止する処理を入れろよ)
  • 並行処理をしているときは処理が完了してないスレッドも停止してしまわないようにする必要がある。
    • そのためにはstopSelf()を使う。処理要求ごとにIDが発行されるので、そのIDでもって正しくServiceが停止されるようにする。
  • Serviceの停止処理は適切に行わないと無駄な電力消費などを招くので注意すること。
    • 必要に応じて別のコンポーネントからstopService()でService停止することも考慮する。
  • 詳しくはManaging the Lifecycle of a Service.を参照

Bound Serviceを作る

  • 前述のとおり"bound"なService(以下Bound Service)は別アプリケーションを含む別コンポーネントと連携するときに利用する。
  • これも前述のとおり、Bound ServiceはonBind()をオーバライドしIBinderを継承したクラスを返しておく必要がある。
  • 他のコンポーネントはbindService() によってServiceのインターフェースを取得し、連携することができる。
  • またまた前述のとおり、Bound Serviceは複数の他コンポーネントから接続(バインド)ができ、バインドされたコンポーネントが1つもなくなると終了する。
  • Bound Serviceは奥が深いのでさらに詳細はBound Services.を参照。

ユーザーに通知する

  • ServiceはToast NotificationsStatus Bar Notificationsでユーザーに通知ができる。
  • 普通はStatus Bar Notificationsを使って、ユーザーに処理が続いていること、終わったことなどを適切に通知するのがベスト。(例:ファイルダウンロード中、ダウンロード完了、など)

Serviceのフォアグラウンド実行

  • Serviceのフォアグラウンド実行(以下 フォアグラウンドService)とは、ユーザーに起動状態であることを意識させ、(メモリ逼迫時など)システムのkill対象になりにくくするService。
  • フォアグラウンドServiceではStatus Bar Notificationsを"ongoing"で使う必要がある(詳しくはこちら)。これはすなわちServiceが停止したり、フォアグラウンドから削除されても、Notificationは消えないことを意味する。
    • ミュージックプレイヤーをイメージするとわかりやすい。ホームボタンを押してホーム画面にいったり、音楽再生が終ったからといってNotificationが消えたら、プレイヤーが起動していることがわからなくなる。
  • フォアグラウンド実行するにはstartForeground()を呼び出す。
    • startForeground()の引数をはNotificationをユニークに指定するintとNotificationそのものの2つ。こんな感じ。
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
        System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
        getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION, notification);
  • Serviceをフォアグラウンドから削除するにはstopForeground()を呼ぶ。
    • stopForeground()はStatus barからNotificationを消すかどうかのbooleanの引数をとるが、いずれにしてもServiceは停止しない。
    • フォアグラウンド実行中にServiceが停止されれば、Notificationは自動的に消える。

Serviceのライフサイクルを管理する

  • 基本はこれまで説明したとおり、"started"と"bound"なServiceの2通りのライフサイクルがある。
    • stopSelf()で止めるやつと、バウンドされたコンポーネントがなくなると終了するやつね。
  • が、"started"かつ"bound"なServiceもあるので、その場合は不慮の停止などが起きないように考慮すること。
ライフサイクルコールバックの実装
  • ActivityのようにServiceにもライフサイクルを管理するコールバックメソッドがある。以下のスケルトンを参照。
public class ExampleService extends Service {
    int mStartMode;       // indicates how to behave if the service is killed
    IBinder mBinder;      // interface for clients that bind
    boolean mAllowRebind; // indicates whether onRebind should be used

    @Override
    public void onCreate() {
        // The service is being created
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        return mStartMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return mBinder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return mAllowRebind;
    }
    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }
    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
    }
}
  • Acitivityと違って不要なコールバックはわざわざsuperを呼ぶ必要はない。

  • これらのコールバックを実装することによって、以下の2つのネストしたライフサイクルを監視できる。
    • Serviceの全体のライフタイムはonCreate()が呼ばれた時からonDestroy()が処理をreturnするまで。
    • ServiceのアクティブライフタイムはonStartCommand()かonBind()が呼ばれた時に始まり、"started"なServiceなら全体のライフタイム終了まで。"bound"なサービスならonUnbind()が呼ばれるまでとなる。
  • 前掲のライフサイクルを表した図は、"started"と"bound"を別々に表しているが、実際はonStartCommand()から始まってもonBind()が呼ばれる(つまり両方の特性を持ちうる)可能性があることを留意すること。

ふぅ。。。

スマートにプログラミング Android入門編 第3版 SDK4.x対応

スマートにプログラミング Android入門編 第3版 SDK4.x対応