Android 8.0 Oreo で setOnClickPendingIntent を用いて PendingIntent を呼び出す際の注意点
概要
Android 開発初心者が Widget 開発しようとしたら PendingIntent が虚空に飲まれて困ったので対処しました。 長々と本文がありますが、最後の余談に書いてあることの方が真理で即解決可能な方法です。
注意
筆者は Android 開発初心者で理解が間違っている、不正確な文章、表現が含まれる可能性があるため話半分で読むことをおすすめします。 誤りの指摘、本当の解決法(?)等がございましたらコメントなり Twitter なりでお伝えいただければ幸いです。
導入と動かないサンプルコード
自宅の照明を制御するためにいちいち Google アシスタントに話しかけるのは、引きこもり大学院生の声帯に対してあまりに過酷すぎる問題があります。 というわけで、照明操作のフロントエンドに Android の Widget を使おうと思ってサクッと作ることにしました。
サクッと、とは言ったものの、今まで Android 開発は GPS ロガーを作るのに触ってみた程度の経験で Widget 開発に関しては全く経験がなかったので爆速で座礁しました。 とりあえず実現したかった機能としては、
という機能で、2.の部分を実現する Service を作成して、Widget をタップするとこの Service を起動するという構成が一般的そうです。 HTTP リクエストは後ほど実装するとして、とりあえず Widget タップ → Service 起動の流れを実装することにすると、以下のようなサンプルがヒットします。
Android 8.0での変更
このサンプル、最初は上手く動いていたのですが、つい先日端末を Android 8.0 Oreo にアップデートしたところ動かなくなってしまいました。 そもそも Service の onCreate( ) すら呼ばれていないようで、無言で生成されることもなく死んでいるようです。 色々調査してみると、どうやら Oreo から Service の起動に制約が増えたようです。
Android 8.0 での動作変更点 | Android Developers
要約すると、
- バックグラウンドで起動される行儀の悪い Service によるバッテリ消費の問題が深刻化している
- Android O では予め許可された特殊な Service を除いてバックグラウンド実行を禁止することに
- 許可されない(通常の) Service を実行する際にはフォアグラウンドとして実行する必要がある
今回の Service も許可されない Service のためどこかで差し止められたのだと考えられます。 (変更を加えずに従来通り Service を呼び出しても、数秒は実行されるっぽいので Service の onCreate( ) すら呼ばれていないのは奇妙ですが……)
この変更点は日本語の記事でもいくつか取り上げている例があったのですが、どれも Activity からバックグラウンド実行する Service を呼び出す話で、startService( ) を書き換えるだけでオッケーみたいなことを書いているのですが、Widget では PendingIntent 経由で呼ばれるのでこの方法は使えず全然オッケーではありません。
対処法
ここから行き詰まってインターネットを彷徨い続けることになったのですが、結論から言うと PendingIntent を作成する際に getService( ) から getForegroundService( )に変更する + Service 側で startForeground( ) を呼び出すことで問題は解決しました。 getForegroundService( ) はAPI Level 26で追加されたようなので、場合によってはビルド設定を見直す必要がありそうです。
余談
差し当たってはビルド設定の targetSdkVersion を25にすることでこの変更を逃れられて、当初のサンプルコードを入力することで動作させることが可能なようです。