サイト移転のお知らせ

しばらくブログお休みしていましたが、こちらに移転しました。よろしければこちらへどうぞ。

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Androidで死なないServiceを実装してみた

Androidでサービスを使ったアプリを作った事がある人なら分かるかと思いますが、「サービスを死なないようにする」、これがなかなか難しいです。
Developerサイトに説明されているサービスにライフサイクルによれば、サービスが死ぬ前にonDestroy()がコールされるのですが、これがコールされずに突然プロセスごと消滅することが多々あります。

例えば、フォアグランドで動いているアプリが大量にメモりを消費するとき。システムはメモリを確保するためにバックグラウンドのサービスを瞬殺します。悪・即・斬です(笑)

また、軽い気持ちでインストールされてしまっているタスクキラーやメモリクリーン系のアプリが動いたとき。この時もサービスは悪・即・斬です(いや、悪ではないんですけどね)

その他、サービスがなにも仕事をしない状態で放置しながらlogcatのログを眺めていると、以下のようなログがでることがあります(jp.skr.imxs.servicetestがサービスのプロセスです) 。

I/ActivityManager( 1959): No longer want jp.skr.imxs.servicetest (pid 5354): hidden #16
W/ActivityManager( 1959): Scheduling restart of crashed service jp.skr.imxs.servicetest/.MainService in 5000ms

要は、「このサービスは必要ないから止めちゃうよ。で、5秒後に再度起動してあげるよ」とActivityManagerが気を利かせてくれているわけですね。つまり、この5秒の間はサービスが機能しなくなるわけです。

という状況なのでサービスを死なないようにする策はいろいろと考えられていますが、そのいくつかを実験してみました。
実験は、いくつかの実装形態に対して
  • 長時間(1時間)放置して死なないか
  • メモリクリーン系アプリを動かして死なないか
  • Androidの「実行中のサービス」の画面から「停止」して死なないか
  • を検証してみました。
    なお、メモリクリーン系アプリには、ダウンロードランキングでも常に上位にいる「FMR Memory Cleaner」を使いました。

    と、実験の前に注意点です。

    サービスを使ったアプリの大半は、なんらかのイベントをトリガーとして動作し、それ以外は待機しているものだと思います(イベントドリブンってことです)。イベントドリブンな実装で問題ない場合は、死なないサービスにする必要はありません。ブロードキャストレシーバなどでイベントを受けられないかを検討しましょう。
    また、一定周期毎に処理をするサービスを作るためにHandlerやThreadを使っていて、サービスが死ぬのが困るという場合はAlarmManagerを使えば回避できます(AlarmManagerが必要なタイミングで起こしてくれます)。


    では、実験です。改めて目的は、どうしたら「死なないサービス」を実現できるか、です。

    1. 普通のサービス
    普通って何やねん!と言われかねないですが、最低限のメソッド(onBindだけかな?)のみを実装し、同一プロセス内のActivityから起動(startService())されるサービスです。まずは比較のためということで。
    長時間経過

    30分程度したところで、前述したようなログがでてプロセスは死にました。
    onDestroy()は呼ばれません。

    FMR Memory Cleaer

    見事にプロセスが死にました。(プロセスの生き死にはpsコマンドで確認しています)。
    onDestroy()は呼ばれません。
    さらにやっかいなことに、ActivityManagerに停止させられた場合と比べて、再度起動するまでの時間が10倍以上になっています。なんども実施するとさらに再度起動するまでの時間が延びていくようです。
    W/ActivityManager( 1959): Scheduling restart of crashed service jp.skr.imxs.servicetest/.MainService in 64938ms

    実行中のサービスを停止

    意外なことにプロセスは残ったままです。
    onDestroy()はちゃんと呼ばれます。
    実行中のサービスの一覧からは消えていますが、「メニュー」→「キャッシュしたプロセスを表示」とすると、プロセスがちゃんといます。

    長時間経過やメモリクリーン系アプリで殺されてしまうので、やはりこの実装形態はダメですね。

    2. フォアグランドサービス
    サービス自身でstartForeground()をコールしたものです。
    Android Referenceにも、Foregroundのサービスは死なない(死ににくい)と書いてあります。
    http://developer.android.com/reference/android/app/Service.html#startForeground(int, android.app.Notification)
    長時間経過

    1時間程度確認しましたが、プロセスが死ぬ現象は見られませんでした。さすがForegroundです。

    FMR Memory Cleaer

    これでもプロセスは生きたままです。

    実行中のサービスを停止

    1.の実装形態と同様にonDestroy()が呼ばれました。プロセスは残っています。

    意図的に「停止」しない限りサービスはずっと生きているようなので、Foregroundにすれば目的は達成できそうです。ただ、Foregroundにすると、通知領域(ステータスバーのアイコンとプルダウンしたときの表示領域)の表示を伴わなければならないという制約があります。startForeground()の引数にnotificationがあるのはこのためです。死なないサービスはできますが、よほど納得できる理由がなければ通知領域の表示はユーザに嫌がられるかもしれませんね。

    3. 2つのサービス同士でbindしあう
    Context#stopService()の記述を見ると、bindされている間は死なないと書いてあります(本当はもっと細かい条件がありますが詳細は読んでみてください)。
    http://developer.android.com/reference/android/content/Context.html#stopService(android.content.Intent)

    ということで、同一プロセス内にメインのサービスとは別のサービス(HelperServiceとします)を作って、こいつからBindさせます。
    さらに、Bindしておけば、ServiceConnection#onServiceDisconected()で相手方のサービスが死んだ事が分かるので、そのタイミングで再度サービスを起動してやれば、たとえ一方のサービスが死んでもするに起動させられるだろうという作戦です。
    http://developer.android.com/reference/android/content/ServiceConnection.html#onServiceDisconnected(android.content.ComponentName)

    関係ありそうなところだけ絵で描けばこんな感じでしょうか。
    service3.png


    さて、結果はどうかというと。
    長時間経過

    30分程度したところで、前述と同様にプロセスは死にました。
    onDestroy()は呼ばれません。

    FMR Memory Cleaer

    これも同様にプロセスごと死んでしまいます。
    onDestroy()は呼ばれません。

    実行中のサービスを停止

    これまでの実装形態と同じくpsコマンドでみるプロセスは残っています。前述の2形態と異なるのは、おそらくbindしているためと思われますがonDestory()が呼ばれませんでした。

    結局、2つのサービスでbindし合ったところでプロセスごと殺されてしまっては意味が無いという結果でした。

    では、最後の実装形態
    4. メインのサービスと別プロセスのサービスでbindしあう
    サービスとは別のプロセスでサービスを動かし、相互にbindしあって、一方が死んだらすぐに起こしてやるようにします。以下がpsコマンドの出力ですが、helperが今回用意した別のプロセスです。
    app_240 3924 1761 128888 18252 ffffffff 00000000 S jp.skr.imxs.servicetest
    app_240 3959 1761 127812 17772 ffffffff 00000000 S jp.skr.imxs.servicetest:helper

    例の如く手抜きのクラス図。さきほどのとは別プロセスというところだけが違います。
    service4.png

    長時間経過

    1時間程度監視しましたが、ActivityManagerから殺されるという現象は確認できませんでした。

    FMR Memory Cleaer

    見事に耐えきりました。生きたままです。

    実行中のサービスを停止

    これまでの実装形態と同じくpsコマンドでみるプロセスは残っています。前述と同様にbindしているためと思われますがonDestory()が呼ばれませんでした。

    この実装形態であれば、サービスを生かし続けることが可能そうです。

    ということで、常にフリック操作を常に待ち受ける必要のあるFlick Screen Off のv1.0.4からはこの実装形態を採用しています(v1.0.3ではforeground serviceを使っていました)。

    この実装で注意しないといけないのが、端末電源OFFのとき。システムが順番に動いているプロセスを止めて行くようですが、せっかくサービスを止めてもonServiceDisconnected()の実装でサービスを生き返らせてしまいます。
    まぁ、電源OFFなので放っておけば良いという考え方も無くはないですが、logcatで見る限りエラーとして出力されるので対策はしておいたほうがよいでしょう。(android.intent.action.ACTION_SHUTDOWNのブロードキャストインテントをトリガーにBindを解除するなど)。

    ちなみに前述のクラス図、相互依存で気持ち悪いですが、うまくやれば相互依存しない設計も可能です。


    今回実験したケース以外でもサービスが死んでしまう場面はあるかもしれません。Androidでずっと生きていないといけないサービスは極力作りたくないですね。
    関連記事
    スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

トラックバック


この記事にトラックバックする(FC2ブログユーザー)

まとめtyaiました【Androidで死なないServiceを実装してみた】

Androidでサービスを使ったアプリを作った事がある人なら分かるかと思いますが、「サービスを死なないようにする」、これがなかなか難しいです。Developerサイトに説明されているサービ

コメントの投稿

非公開コメント

サイト内検索
プロフィール

Author:imxs

Androidアプリ開発などを行っているimxsの開発者です。気になることを調べてメモって行きます。ほとんどの人にはどうでもいい内容でも、広い世の中一人くらいは同じ疑問を持った奇妙な人がいることを信じつつ。暖かい目で見守ってやってください。
imxsの開発者ブログは移転しました。よろしければこちらへどうぞ。

カテゴリ
最新記事
リンク
RSSリンクの表示
最新コメント
最新トラックバック
FC2カウンター
アクセスランキング
[ジャンルランキング]
携帯電話・PHS
181位
アクセスランキングを見る>>

[サブジャンルランキング]
Android(Google)
51位
アクセスランキングを見る>>
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。