最近このBLOGがIMoNiのサポートサイトと化しているので、たまには開発者向けの投稿でも書いてみようと思います。
何らかを定期的に監視し続けるときのように、ずっと動き続けるService(システムが提供する機能というわけではなく、AndroidのServiceコンポーネントです。)を作ることがあると思いますが、私が初めて取り組んだときは、意外とハマりどころがあるなと思いました。このことは、いつの間にか落ちていたり、停止できなかったりするアプリを目にすることが意外と多いことからも頷けると思います。
多分いろいろな実装方法があると思いますし、処理の特性によって最適な方法は異なるかもしれませんが、私が一番簡単じゃないかなと思っているやり方を簡単に述べます。他にもアイディアや観点があれば是非教えて貰えるとうれしいです。
・息の長いThreadよりonStartとAlarmManagerを使う
どちらも試しましたが、後者の方がコードが簡潔になりますし、バグが混入しにくいような気がします
・onStartから作業用Thread(Task)を起こす
onStartで時間をかけすぎると(多分5秒以上)怒られるので、時間がかかる場合は別スレッドで行い、onStartは直ちに終わるようにします。ちなみに生Threadよりもjava.util.concurrent.ExecutorServiceなどを使ったほうが簡単かもしれません。例外の取り方に癖があるので気をつけます。
(http://java.sun.com/javase/ja/6/docs/ja/api/java/util/concurrent/Callable.html)
・定期的に処理を行う場合は、作業用Threadの処理終了時にAlarmをset
作業用Threadは、一回処理をして終わるようにします。定期的に処理したい場合はThreadが終わる時にAlarmに登録します。
・止める処理もonStart
止めるときの処理もonStartでやるのがいいような気がします。intentのActionで振舞いを分けるイメージです。以下のような感じです。止める時も呼び側がstartServiceなのか、と少し誤解を招きそうですが、僕はこうしています。
public void onStart(final Intent intent, int startId) {
super.onStart(intent, startId);
if (ACTION_EXECUTE.equals(intent.getAction())) {
// スレッド起こして処理実行
}
else if (ACTION_STOP.equals(intent.getAction()) ){
// 終了処理(Alarmのキャンセルなど)
}
・・・
}
もちろんバインドして終了処理を呼ぶ方法もありだと思います。
・必要に応じてwakelockを使う
Alarmはpartial wakelockを取得してないとスリープ時に呼ばれないことがあるようです。またwifiなどもスリープする可能性があるので、処理の特性に応じて適宜最小限のwakelockをかけます。
・・・ところで、Androidにはちょっと厄介な特性があります。それは、メモリが足りないなどの諸々の理由でServiceが勝手に殺されることがあるということです。
・onLowMemoryは期待しない
メモリが少なくなるとonLowMemoryメソッドが呼ばれます。このメソッドをうまく活用すればよいのですが、あまり役に立ちません。なぜなら、このメソッドはちょっとしたことですぐ呼ばれるからです。
・onDestroyも期待しない
じゃ、死ぬときにonDestroyが呼ばれると思いきや、呼ばれずに死ぬことの方が多いです。なので、ここにコードを書いてもあまり意味はありません。
・各処理の終了時に現在の状況を保存しておき、onCreateで再startする
つまり死ぬことを予め予期できないし検知できないので、いつ死んでもいいように、なんらかの処理を終えたときに最新の状況を逐次記録(永続化)しておきます。Serviceのプロセスは殺されたとしても、しばらくするとまた別のプロセスが勝手に生成されます。しかしこの時点ではServiceはまっさらな状態なので、以前の最新の状況を読み込んで、何をやっていたかを思い出してから再度動き続けるようにします。
以上です。
繰り返しますが、他にも「おれはこうやってる」などのアイディアや観点があれば是非教えて貰えるとうれしいです。