package com.android_mvc.framework.bat; import com.android_mvc.framework.common.FWUtil; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; /** * 常駐型サービスの基底クラス。 * @author id:language_and_engineering * */ public abstract class BasePeriodicService extends Service { // 常駐不可フラグ private boolean mustStopResident = false; // メインスレッドで生成されたハンドラ private static Handler handlerMainThread = null; /** * サービスの定期実行の間隔をミリ秒で指定。 * 処理が終了してから次に呼ばれるまでの時間。 */ protected abstract long getIntervalMS(); /** * 定期実行したいタスクの中身(1回分)。 * タスクの実行が完了したら,次回の実行計画を立てること。 */ protected abstract void execTask(); /** * 次回の実行計画を立てる。 */ protected abstract void makeNextPlan(); /** * ServiceのContextを返す。 */ protected Context getContext() { return this; } /** * メインスレッドで生成されたハンドラを返す。 */ protected Handler getHandler() { return handlerMainThread; } // ---------- 必須メンバ ----------- protected final IBinder binder = new Binder() { @Override protected boolean onTransact( int code, Parcel data, Parcel reply, int flags ) throws RemoteException { return super.onTransact(code, data, reply, flags); } }; @Override public IBinder onBind(Intent intent) { return binder; } // ---------- サービスのライフサイクル ----------- /** * 常駐を開始 */ public BasePeriodicService startResident(Context context) { if( handlerMainThread == null ) { FWUtil.d("ハンドラがないため,生成します。"); handlerMainThread = new Handler(); } FWUtil.d("サービスの常駐を開始します。"); Intent intent = new Intent(context, this.getClass()); intent.putExtra("type", "start"); context.startService(intent); // NOTE: ここから先は,違うインスタンスとしてサービスが生成される。 // 何かを渡したい場合はintentに含めるか,staticメンバを経由する。 return this; } /** * 常駐を開始。別スレッドから呼び出す場合。 */ public BasePeriodicService startResident(Context context, Handler handler) { // ハンドラを保持 handlerMainThread = handler; this.startResident(context); return this; } @Override public void onStart(Intent intent, int startId) { // サービス起動時の処理。 // サービス起動中に呼ぶと複数回コールされ得る。しかし二重起動はしない // @see http://d.hatena.ne.jp/rso/20110911 super.onStart(intent, startId); mustStopResident = false; FWUtil.d("サービスからタスクを起動"); execTask(); // NOTE: 重いタスクかもしれないが,同一スレッド内でコールしている。 // 理由は,もし重いタスクであれば,メソッド内部で逐次非同期にタスクを走らせればよいから。 // ここの時点で別スレッドに分けてしまうと,逐次非同期のタスクランナーが実行できなくなってしまう。 // NOTE: ここで次回の実行計画を逐次的にコールしていない理由は, // タスクが非同期の場合があるから。 } /** * サービスの次回の起動を予約 */ public void scheduleNextTime() { // サービス終了の指示が出ていたら,次回の予約はしない。 if( mustStopResident ) return; long now = System.currentTimeMillis(); // アラームをセット PendingIntent alarmSender = PendingIntent.getService( this, 0, new Intent(this, this.getClass()), 0 ); AlarmManager am = (AlarmManager)this.getSystemService(Context.ALARM_SERVICE); am.set( AlarmManager.RTC, now + getIntervalMS(), alarmSender ); FWUtil.d("次回登録が完了"); } /** * サービスの定期実行を解除し,サービスを停止 */ public void stopResident(Context context) { // TODO: 既存の実行タスクも終了させる?今は生かしてある。 FWUtil.d("サービスの常駐を終了します。"); // 次回予約をさせない mustStopResident = true; // サービス名を指定 Intent intent = new Intent(context, this.getClass()); // アラームを解除 PendingIntent pendingIntent = PendingIntent.getService( context, 0, // ここを-1にすると解除に成功しない intent, PendingIntent.FLAG_UPDATE_CURRENT ); AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); alarmManager.cancel(pendingIntent); // @see http://creadorgranoeste.blogspot.com/2011/06/alarmmanager.html // サービス自体を停止 stopSelf(); } }