My Photo

« 第30節、新潟は福岡に引導を渡す、磐田は知らん | Main | Androidアプリ開発メモ031:サービス その3:IntentService »

October 23, 2011

Androidアプリ開発メモ030:サービス その2

前のサービスに関する記事は後から読んでもぜんぜんわからないので、あらためサービスについて末尾に書いた参考サイトを読んで、自分なりにまとめてみた。

サービスとは

サービスはユーザインタフェースを持たずバックグラウンドで動作するコンポーネント。
サービスは標準ではホストプロセスのメインスレッドで実行される。別スレッドでも別プロセスでもない。サービスで重い処理をして他の操作をブロックしてしまう可能性があれば、サービス内で別スレッドを生成してそちらで処理をさせる方がいい。

サービスを使う場面

たとえばタイマーのアプリを考えると、アクティビティで別スレッドを生成して一定時間後に音が鳴るようにした場合、スレッドを生成したアクティビティ(メインスレッド)が終了すると、別スレッドを変更できないのでタイマーを中止したり設定を変更したりできない。
サービスを使えば、後からタイマーの中止・変更が可能。

サービスの種類

サービスは2つの形態を取る。両方の形態を兼ね備える場合もある。

開始された(Started)
アプリケーションコンポーネント ( アクティビティなど ) が startService() を呼び出してサービスを開始したとき、サービスが "開始された" 状態になる。startService()の引数のIntentでどのサービスを開始するか指定する。
サービスを開始したコンポーネントが終了しても、サービスはバックグラウンドで動く。
サービスは開始したコンポーネントに何か返したりしない。
サービスは処理が終わったら自身で stopSelf() または stopSelfResult() を呼ぶか、他のコンポーネントがContext#stopService()を呼ぶかして終了しなければならない。
バインドされた(bound)
アプリケーションコンポーネントが bindService() を呼び出してサービスにバインドしたとき、サービスが "バインドされた" 状態になる。
バインドされたサービスは、コンポーネントとサービスとのやり取りのためのインターフェイスを提供する。
複数のコンポーネントがサービスにバインドすることができる。すべてのコンポーネントがアンバインドされるとそのサービスは、(startService()で開始されていないならば)破棄される。
また、サービスがクライアントコンポーネントと同じプロセスで動く場合はをローカルサービス、異なるプロセスで動く場合をリモートサービスという。

「開始された」サービスのライフサイクル

startService()でサービスが開始されると、サービスのonCreate(),onStartCommand()がシステムに呼ばれる。サービスがすでに存在する場合はonStartCommand()だけが呼ばれる。
stopService()が実行された場合またはサービス自身がstopSelf()を呼んだ場合、onDestroy()がシステムに呼ばれる。

参考:Intent起動のServiceのライフサイクル <<えふログ

onStartCommand()の戻り値は下記の定数を使わなければならない。どれを返すかによりonStartCommand()から戻った後でシステムがサービスを強制終了した場合の動作が変わる。

START_NOT_STICKTY 配信されるペンディングインテントが存在しない限りサービスは再作成されない。
START_STICKY サービスを再作成し、onStartCommand() を呼び出すが、最後のインテントは再配信さない。その代わり、サービスを開始するペンディングインテントがない場合はシステムがインテントをnullにしてonStartCommand() 呼び出す。
START_REDELIVER_INTENT サービスを作成し、最後にサービスに配信された最後のインテントで onStartCommand() を呼び出す。
START_STICKY_COMPATIBILITY この定数の説明には「強制終了された後にonStartCommand()が呼ばれることは保障されない。」とある。またonStartCommand()の引数intentの説明に「以前にSTART_STICKY_COMPATIBILITY以外のものが返された場合、これはnullになることがある。」とある。よって、onStartCommand()がSTART_STICKY_COMPATIBILITYを返した後にシステムがサービスを強制終了すると、intentがあればonStartCommand()を呼び出すが、intentがなければonStartCommand()は呼ばれないものと思われる(呼ばれた場合はintentはnullではない)。
START_STICKYを返した場合は、onStartCommand()でintentがnullになっている場合がある。
START_STICKY_COMPATIBILITYを返しておくのが無難?

サンプルコード

ApiDemoの
com.example.android.apis.app.LocalServiceActivities.java の静的ネストクラスController
com.example.android.apis.app.LocalService.java(バインドされたサービスも一緒に書かれている)

単純なサービス - Android 開発入門

繰り返し処理を行うとき - Android 開発入門

mp3を再生するサンプルアプリ。
サービスを利用するアクティビティのコード。

package com.example.localserviceexample;

import java.util.List;

import android.app.Activity;
import android.app.ActivityManager;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class LocalServiceExample extends Activity 
  implements View.OnClickListener {
  
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // ボタンの設定
    Button startButton  = (Button)findViewById(R.id.startButton);
    startButton.setOnClickListener(this);
    Button stopButton  = (Button)findViewById(R.id.stopButton);
    stopButton.setOnClickListener(this);
  }
  
  /**
   * ボタン押下時の処理
   */
  @Override
  public void onClick(View v) {
    String tag = (String)v.getTag();
    if (tag.equals("start")) {
      if (isServiceRunning("com.example.serviceex.PlayerService")) {
        return;
      }
      
      // プレイヤーサービスの開始
      Intent intent = new Intent(this, com.example.localserviceexample.PlayerService.class);
      startService(intent);
      
    } else if (tag.equals("stop")) {
      // プレイヤーサービスの停止
      Intent intent = new Intent(this, com.example.localserviceexample.PlayerService.class);
      stopService(intent);
    }
  }
    
  /**
   * サービス起動中かどうかを返す。
   * @param className
   * @return
   */
  private boolean isServiceRunning(String className) {
    ActivityManager am
      = (ActivityManager)getSystemService(ACTIVITY_SERVICE);
    List serviceInfos
      = am.getRunningServices(Integer.MAX_VALUE);
    for (int i = 0; i < serviceInfos.size(); i++) {
      if (serviceInfos.get(i).service.getClassName().equals(className)) {
        return true;
      }
    }
    return false;
  }
}

サービスのコード。
package com.example.localserviceexample;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

import android.media.MediaPlayer;

public class PlayerService extends Service
  implements MediaPlayer.OnCompletionListener {
  
  private static final int NOTIFICATION_ID = 0;

  private MediaPlayer mediaPlayer;
  private NotificationManager notificationManager;

  @Override
  public void onCreate() {
    super.onCreate();
    Log.v("SERVICE", "onCreate");
    
    notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    super.onStartCommand(intent, flags, startId);
      Log.v("SERVICE", "onStartCommand");
    
    // ノーティフィケーションの表示
    showNotification(
        this, R.drawable.icon,
        "サウンドを再生します。",
        "プレイヤーサービス",
        "プレイヤーサービスを開始しました。");
    
    // サウンドの再生
    playSound();
    
    return Service.START_STICKY_COMPATIBILITY;
  }

  @Override
  public void onDestroy() {
      Log.v("SERVICE", "onDestroy");
      
    // ノーティフィケーションの取り消し
    notificationManager.cancel(NOTIFICATION_ID);
    
    // サウンド再生の停止
    stopSound();
    
    // トーストの表示
    Toast.makeText(
        this,
        "プレイヤーサービスを停止しました。",
        Toast.LENGTH_LONG).show();
  }

  @Override
  public void onCompletion(MediaPlayer mp) {
      Log.v("SERVICE", "onCompletion");
    stopSound();
  }

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

  /**
   * サウンドを生成する。
   */
  private void playSound() {
    try {
      if (mediaPlayer == null) {
        mediaPlayer = MediaPlayer.create(this, R.raw.sample);
        mediaPlayer.start();
        mediaPlayer.setOnCompletionListener(this);
      }
    } catch (IllegalStateException e) {
      e.printStackTrace();
    }
  }

  /**
   * サウンドを停止する。
   */
  private void stopSound() {
    try {
      if (mediaPlayer != null) {
        mediaPlayer.stop();
        mediaPlayer.setOnCompletionListener(null);
        mediaPlayer.release();
        mediaPlayer = null;
      }
    } catch (IllegalStateException e) {
      e.printStackTrace();
    }
  }
  
  /**
   * ノーティフィケーションを表示する。
   */
  private  void showNotification(
      Context context, int icon, String tickerText,
      String contentTitle, String contentText) {
    
    // ノーティフィケーションの生成
    Notification notification
      = new Notification(icon, tickerText, System.currentTimeMillis());
    PendingIntent pendingIntent
      = PendingIntent.getActivity(
          context, 0,
          new Intent(context, com.example.localserviceexample.LocalServiceExample.class),
          0);
    notification.setLatestEventInfo(context, contentTitle, contentText, pendingIntent);
    
    // ノーティフィケーションの表示
    notificationManager.notify(NOTIFICATION_ID, notification);
  }
}

AndroidManifest.xmlにはapplication要素の子要素として(activity要素と同じレベル)、service要素を追加する。
<service android:name=".PlayerService" />

参考:
2. サービス - ソフトウェア技術ドキュメントを勝手に翻訳
サービス - Android 開発入門
AndroidのServiceについて - adsaria mood
Android: Serviceのノウハウ << えふログ

« 第30節、新潟は福岡に引導を渡す、磐田は知らん | Main | Androidアプリ開発メモ031:サービス その3:IntentService »

Androidアプリ開発」カテゴリの記事

Comments

Post a comment

(Not displayed with comment.)

TrackBack

TrackBack URL for this entry:
http://app.cocolog-nifty.com/t/trackback/26461/53068924

Listed below are links to weblogs that reference Androidアプリ開発メモ030:サービス その2:

« 第30節、新潟は福岡に引導を渡す、磐田は知らん | Main | Androidアプリ開発メモ031:サービス その3:IntentService »

May 2017
Sun Mon Tue Wed Thu Fri Sat
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
無料ブログはココログ

日本blog村

  • にほんブログ村 IT技術ブログへ
  • にほんブログ村 アニメブログへ
  • にほんブログ村 サッカーブログ アルビレックス新潟へ

好きな音楽家

メモ

XI-Prof