Android: Serviceの起動と停止のサンプル

AndroidのServiceを作成し、ボタンを押して起動、停止するサンプルです。

[Serviceについて]
http://developer.android.com/guide/topics/fundamentals/services.html
- A Service is an application component that can perform long-running operations in the background and does not provide a user interface. -

http://developer.android.com/reference/android/app/Service.html
- Note that services, like other application objects, run in the main thread of their hosting process. This means that, if your service is going to do any CPU intensive (such as MP3 playback) or blocking (such as networking) operations, it should spawn its own thread in which to do that work. -



[ サンプルプログラム ]
http://developer.android.com/guide/topics/fundamentals/services.html を見たら、IntentServiceというクラスを使う方がServiceクラスを使うより簡単そうだったので、そこに掲載されているサンプルを参考に書いたら、確かに少ないコードですぐにできた。
(これについては次回のブログ記事に書きます)

ところがそれを実行してみたところ、サービスの処理が終わるまでは、アプリケーション本体を終了する以外の方法ではWorkerスレッドを終了させることができない。色々と調べてみたところ、どうもそれが本来の動作のようなので、IntentServiceが利用するMessageQueueクラスとLooperクラスを使わずに、キューを持たない単なるスレッドでサービスを処理するサンプルを書いてみた。これだとサービスの処理の途中でも終了させることができる。


      

アプリの内容:
・起動ボタンが押されるとサービスを開始
・サービス => 一定間隔でカウンタをカウントアップする
・テキストボックスに入力された上限値に達するか、停止ボタンが押されたらサービスを停止する

  UIスレッド(ControlServiceSample1Activityクラス)
           ↓
         Intentの発行
           ↓
      サービス(ServiceSample1クラス)
           ↓
  Workerスレッド(WorkerThreadクラス)によるサービスの処理

(私はJavaについてはよく知らない状態で、昔少しやったC++を思い出しながら、主にhttp://developer.android.comにあるドキュメントを参照しつつ、かなり勘で書いてます。たぶん中には"変な書き方"があったり、あるいはもっと良い書き方があったりするかも知れません。)



[ デバッガとDDMS(Dalvik Debug Monitor Server)でServiceの状態を確認 ]



[ このサンプルについて ]

ControlServiceSample1Activity.java

ControlServiceSample1Activityクラスの定義

 13行目  public class ControlServiceSample1Activity extends Activity {

Intentによるサービスの起動

 57行目  Intent intent = new Intent(ControlServiceSample1Activity.this, ServiceSample1.class);
 58行目  startService(intent);

Intentによるサービスの停止

 64行目  Intent intent = new Intent(ControlServiceSample1Activity.this, ServiceSample1.class);
 65行目  stopService(intent);

ServiceSample1.java

ServiceSample1クラスの定義

 10行目 public class ServiceSample1 extends Service {

WorkerThreadの生成と起動

 25行目                 thread = new WorkerThread(this, "Service Sample1 [WorkerThread]");
 26行目                 thread.start();

WorkerThread.java

WorkerThreadクラスの定義

  9行目 public class WorkerThread extends Thread {

サービスの処理(例:一定間隔でカウントアップする)

 40行目                         int count = 0;
 41行目                         do{
 42行目                                 count += 1;
 43行目                         String text = String.valueOf(count);
 44行目                         mHandler.post(new ShowToast(mContext,count));
 45行目                         Log.v("ServiceStatus", "count="+text);
 46行目                                 Thread.sleep(5000); // interval is 5 sec.
 47行目                         } while (count != ServiceSample1.UpperLimit);

サービスの処理が終了したら、サービスを停止する

 53行目                 Intent intent = new Intent(mContext, ServiceSample1.class);
 54行目                 mContext.stopService(intent);

AndroidManifest.xml

このファイルに次のようにサービスを登録します。

<service android:name=".ServiceSample1" />

ここにサービスを登録しないと、サービスは起動されません。

http://developer.android.com/guide/topics/manifest/manifest-intro.html

Every application must have an AndroidManifest.xml file (with precisely that name) in its root directory. The manifest presents essential information about the application to the Android system, information the system must have before it can run any of the application's code.

http://developer.android.com/guide/topics/manifest/service-element.html

android:name
The name of the Service subclass that implements the service. This should be a fully qualified class name (such as, "com.example.project.RoomService"). However, as a shorthand, if the first character of the name is a period (for example, ".RoomService"), it is appended to the package name specified in the <manifest> element.



[ サンプルのコード ]
ControlServiceSample1Activity.java

package jp.knowd.ControlServiceSample1;

import android.app.Activity;
import android.os.Bundle;
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;

public class ControlServiceSample1Activity extends Activity {
    /** Called when the activity is first created. */
    //  利用する部品 
    public TextView txtInfo;
    public EditText edtInput;
    public Button btn1, btn2;
    public TextView txtResult;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // レイアウトを作成
        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        setContentView(layout);

        // 以下、部品を作成していく
        
        // ラベル
       txtInfo = new TextView(this);
       txtInfo.setText("Upper Count Limit: Value 0 or null means an infinit loop.");
       layout.addView(txtInfo);
        
        // エディタ
       edtInput = new EditText(this);
       layout.addView(edtInput);
        
        // ボタン1
        btn1 = new Button(this);
        btn1.setText("ServiceSample1を[起動]する");
        layout.addView(btn1);
        
        // ボタン2
        btn2 = new Button(this);
        btn2.setText("ServiceSample1を[停止]する");
        layout.addView(btn2);
        
        // ボタン1のイベントを設定
        btn1.setOnClickListener(new OnClickListener(){
            public void onClick(View v) {
            	String strInput = edtInput.getText().toString();
            	try { ServiceSample1.UpperLimit = Integer.parseInt(strInput);}
            	catch(Exception e) {ServiceSample1.UpperLimit = 0;}     	
        		Intent intent = new Intent(ControlServiceSample1Activity.this, ServiceSample1.class);
        		startService(intent);
             }
        });
        // ボタン2のイベントを設定
        btn2.setOnClickListener(new OnClickListener(){
            public void onClick(View v) {
        		Intent intent = new Intent(ControlServiceSample1Activity.this, ServiceSample1.class);
        		stopService(intent);
             }
        });      
    }
}

ServiceSample1.java

package jp.knowd.ControlServiceSample1;

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

public class ServiceSample1 extends Service {

	public static WorkerThread thread;
	public static int UpperLimit;
	public static Context context;

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

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Toast.makeText(this, "ServiceSample1 started.", Toast.LENGTH_SHORT).show();
		Log.i("ServiceStatus", "ServiceSample1 started!!");	
		thread = new WorkerThread(this, "Service Sample1 [WorkerThread]");
		thread.start();
		return START_STICKY;
	}
	
	@Override
	public void onDestroy() {
		thread.interrupt();
		Toast.makeText(this, "ServiceSample1 terminated.", Toast.LENGTH_SHORT).show();
		Log.w("ServiceStatus", "ServiceSample1 terminated!!");
	}
	
}

WorkerThread.java

package jp.knowd.ControlServiceSample1;

import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.util.Log;
import android.widget.Toast;

public class WorkerThread extends Thread {
	
	public class ShowToast implements Runnable {

		private Context mContext;
		private int mCount;

		public ShowToast(Context context, int count) {
			mContext = context;
			mCount = count;
		}

		@Override
		public void run() {
			String text = String.valueOf(mCount);
			Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show();
		}
	}
	
	private Context mContext;
	private Handler mHandler;

	public WorkerThread(Context context, String name) {
		super(name);
		mContext = context;
		mHandler = new Handler();
	}

	@Override
	public void run() {
		try {
			int count = 0;
			do{
				count += 1;
		    	String text = String.valueOf(count);
		    	mHandler.post(new ShowToast(mContext,count));
		    	Log.v("ServiceStatus", "count="+text);
				Thread.sleep(5000); // interval is 5 sec.
			} while (count != ServiceSample1.UpperLimit);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		// Stopping Service by Intent.
		Intent intent = new Intent(mContext, ServiceSample1.class);
		mContext.stopService(intent);		
	}

}

AndroidManifest.xml