画竜点睛を衝く@mapyo

日々やった事をつらつらと書くブログです

別プロセスで動いている生き死にするServiceで非同期処理をする事を考える

AndroidのServiceのお話。以下のようなServiceがある。

  • Serviceを別プロセスで動かしている
  • destroyが呼ばれた時は、Process.killProcess(Process.myPid())を呼んで自分自身のプロセスをkillする。
  • 定期的に生き死にする

こういったSerivceで非同期処理をすると、ちゃんと非同期処理の終了を待つ処理をいれないと、 非同期処理してる間にサービスが死しに、プロセス自体が死ぬので非同期処理が完了する前に終わってしまう。。。

みたいな事があります。

なので、別プロセスのSerivceのライフサイクルに依存しない形で非同期処理をしたい。

IntentServiceを使ってアプリのプロセスで動かすようにすると、 管理しやすいのでないか?と思ったので実際にコードを書いて試してみます。また、Serviceの中で非同期処理して、見事に途中で終わってるよね。という事も確認したかったので、それも試してみています。

実験した内容は以下の2つ

  1. 何も考えずにServiceの中で非同期処理をする
  2. アプリのプロセスでIntentServiceを呼んで非同期処理する

実験するServiceの仕様

  1. Serviceが起動する
  2. 5秒たったらstopServiceを呼んで停止する
  3. onDestroyが呼ばれた時にProcess.killProcess(Process.myPid())を呼んで自分自身をkillする

以下のようなコードをSerivceのonCreateに入れて、5秒たったら停止するようにする

        Handler(Looper.getMainLooper()).postDelayed({
            showMessage("stop service")
            this.stopService(Intent(this, SampleService::class.java))
        }, 5_000)

1. 何も考えずにServiceの中で非同期処理する

以下のような感じで1秒毎にメッセージを表示して、10秒たったら終了。みたいな処理をServiceのonCreateの中に書く。

        var counter = 0
        timer.schedule(object : TimerTask() {
            override fun run() {
                counter++
                showMessage("counter: " + counter)
                if (counter == 10) {
                    Handler(Looper.getMainLooper()).post {
                        showMessage("counter finished")
                        timer.cancel()
                    }
                }
            }

        }, 1_000, 1_000)

結果

counter: 1
counter: 2
counter: 3
counter: 4
stop service
destroy service

Serviceは5秒で停止する。onDestoryが呼ばれた時に自分自身をkillしてるので、counterは10秒までは回らない。 まぁ、プロセスが死ぬんだから、その通りだ。

2. アプリのプロセスでIntentServiceを呼んで非同期処理する

こんな感じでAndroidManifest上で以下のように書きます。Serviceを別プロセスで動かして、IntentServiceをアプリのプロセスで動かす。という書き方。

        <service android:name=".SampleService"
            android:process=":SampleService" />

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

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

android:processの仕様はこの辺に書いてある。

processを書かない場合はアプリのプロセスで動くと書いてあって、ちゃんとアプリのプロセスで動くよね?呼び出し元のプロセスで動かないよね?という事も確認したかった。

実験したIntentServiceの仕様

SampleIntentServiceというIntentServiceのクラスを作って、onHandleIntentの中で以下のように書きます。

        (1..15).map {
            Thread.sleep(1000)
            showMessage("SampleIntentService counter: " + it)
        }

こんな感じで、1秒毎にカウンターを表示している。

結果

  • IntentServiceの方は最後までカウンターが表示されていた。
  • プロセスもちゃんとアプリのプロセスで動いてた。
  • 別プロセスで動いているServiceの方は普通に起動して5秒後に死んでいた。

最後に

実験に使ったコードはこちらにアップしています。 https://github.com/mapyo/ProcessSample

IntentServiceを使うと、処理が終わったら勝手に終了してくれるし、連続で呼んでしまったとしても、逐次実行してくれるようになってるし、いろいろと便利です。