🤖

Android アプリ≠プロセス

2021/03/01に公開

Android アプリを開発するとき、他の環境と大きく異なっている重要な点として、アプリのライフサイクル=プロセスの起動/終了ではない、という点があります。

Android 開発していれば常識の部類ですし、だいたいここに書いてあるのでこれを読めば終了です。

https://developer.android.com/guide/components/activities/process-lifecycle?hl=ja

内容

  • Android ではアプリの実行中でもプロセスは終了させられることがある(最初からそういう仕様)
  • プロセスが終了されてまた起動されるのを前提にして開発する
  • バググラウンドとか言っているものも一部はアプリの実装にも問題があるのでは……(しらんけど)

Android アプリとプロセスとの関係

プロセスは終了される

https://developer.android.com/guide/components/activities/process-lifecycle?hl=ja

こちらのページにもある通り、Android で実行中のプロセスには

  • foreground
  • visible
  • service
  • cached

の4つの重要度が割り当てられ、cached 状態のプロセスはいつでもシステムからkillされる場合があります。Activity などは onStop が実行された後の状態なのでこのタイミングでなにか通知されるようなことも発生せず、単に終了するのが仕様です。

ユーザから認識できるアプリの状態としては、

  • 現在アクティブになっているアプリ (foreground)
  • フォアグラウンド状態で通知が表示されているサービス (foreground)
  • マルチウィンドウ表示や半透明の背景などで見えてはいるけど操作できないアプリ(visible)
  • その他タスク一覧でしか見えない、バックグラウンドにいるアプリ(service, cached)

に対応しています。つまり、タスク一覧に見えていてもプロセスは生きているとは限らない、これを大前提におかなければなりません。

プロセスがいつ起動されるのか

今度は逆にプロセスの起動について考えてみると、アイコンをタップしてアプリを起動した場合だけはないことがわかります。つまり、AndroidManifest.xml で記載する

  • Activity
  • Service
  • Receiver

これらはすべて Intent によって起動されシステムや他アプリから呼び出されることがありますが、プロセスが終了している場合には改めて起動されます。
同様にタスク一覧から選択する場合にも、すでにプロセスは終了していることがあるので、改めて起動される場合があります。

Android アプリ開発での注意点

アプリ開発では上記のことを踏まえて実装することが必要です。

エントリポイント

まず、Android のプロセスを起動するタイミングも前述のとおり複数あるので、MainActivity を用意してもそこが必ず起動される前提で作ってしまってはいけません。

Service や Receiver を使っていない場合でさえも、MainActivity → SecondActivity と起動していてプロセスが終了された場合、タスク一覧から戻ってプロセスが起動されると SecondActivity だけ起動された状態になります。

このような動作ができるようにするため、Android では ActivityManager が起動されている(タスクの一覧にある)アプリの一覧と、起動中のアプリには BackStack があってそれぞれの Activity の起動順(終了したときの戻り先の画面)を保持しています。

どの場所から実行されたとしても最初に実行したい処理がある場合には Application クラスがありますのでそれを使うことはできます。

プロセス内のデータは保証されない(終了させられるから)

Android アプリでは、プロセス内にデータを変数として保持しているつもりでも、次にアプリがアクティブになった時には消えているかもしれないということを意識しなければなりません。

気をつけるべき言語機能としては

  • static 変数
  • シングルトンのインスタンス
    • Application クラスのメンバ変数も含む

などがありますが、Android 以外の環境だとこういったところに情報を保存できているとしても同じように Android で実装してしまうと、プロセスが終了されてからまた起動されたときには情報は全部消えてしまいます。

これらの機能が一切使用禁止というわけではないのですが、

  • シングルトンに状態を持たない場合や生成時に状態が確定して変化しない場合は使っても問題はない
  • ほかのデータ(DBやネットワークアクセスなど)のキャッシュとして使う場合で null だったら再度取り直すことができる用途であれば問題ない
    • ただしその取り直しのために新たな処理が発生すると任意のタイミングでなんでもできるとは限らない別の注意は必要

など、用途は限定して考えなければなりません。

これを回避するためには、DB やファイル、あるいはネットワーク上にデータを保存することもできますし、saveInstanceState で ActivityManager にデータを保持することもできます。

タスク一覧からスワイプしたときにはデータは消したい、といった一時的なデータの場合には saveInstanceState を使うことが通常の選択肢になります。

また、Activity 間の遷移についても、前述のとおり遷移先の Activity だけ先に起動されて、戻る操作の後で遷移元の Activity が起動される場合があるので、メンバ変数にだけ情報を保持していたりすると異常な動作になることがあります。

動作テスト時の考慮点

Android で開発者モードのメニュー内に「アクティビティを保持しない」があります。これをOnにすることで、Activity間の遷移時にメンバ変数にだけ情報を保持していて saveInstanceState を使っていない場合に起きる現象を再現できます。

それ以外でもプロセスが終了した状態から Intent によって起動したり、JobScheduler の Job を起動してみたりなどすることができるので、これらの動作確認(少なくともアプリが落ちたり異常状態にならないこと) は必須です。

おわりに

  • "バググラウンド" みたいに言われているがプロセスを終了することができるのは Android の仕様であってアプリ側の実装に問題があることも多い
  • サンプルアプリなんかで簡略化されているものに騙されてはいけなくて、組み合わせでの動作確認は重要

たとえば Xamarin.Forms のドキュメントでは MainActivity が仮定されていて、Prism のテンプレート でもそれを踏襲してしまっています。

それ以外でも他環境で慣れているひとが Android アプリを片手間で開発する場合なんかにこの問題ではまっているひとは見かけます。つらいですね。

Discussion