ネイティブコードでメインの処理を行うとして、Javaの部分は単にActivityという入れ物にしか使わない、という感じでアプリケーションを作成することを考えている。今のところOpenGLESをつかっていて、Android特有の問題に悩まされている。
- 複雑なライフサイクルで、OpenGLのコンテキストが再生成されている
- その再生成のタイミングで今まで使用していたリソースが無効状態となる
- そもそもOpenGLなのに、DirectXのようなデバイスロストに似た症状が発生する
Androidの通常アプリのActivityは以下の図に示す遷移をたどる。この図はGoogleのAndroidのページにあるものと同じである。
調べてみたところ、ユーザーの操作によって、似たように見えていても違う遷移となる点も確認できた。
- 起動後、ホームボタンを押す。その後もう1度起動させるケース
- onCreate – onStart – onResume – (実行中) – onPause – onStop – onRestart – onStart – onResume – (実行中)
- 起動後、Backボタンを押す。
- onCreate – onStart – onResume – (実行中) – onPause – onStop – onDestroy
- 起動後、放置してスクリーンのタイムアウトなどでActivityが見えなくなる
- onCreate – onStart – onResume – (実行中) – onPause – (スクリーン復帰) – onRestart – onStart – onResume – (実行中)
一方で、surfaceDestroyedなる部分は、onPauseの後になる模様。onStopに遷移するタイミングで実行されるみたい。
このつくりだとアプリケーションが再び実行状態になる前には surfaceCreatedを通過するため毎回リソース生成が行われるので、問題なく画面描画ができることになる。リソースの破棄処理がどうなっているのか疑問は残るが。
たぶん、確実にリソース処理しようとすれば onPauseで一時解放しておくのがよく、surfaceCreatedのタイミングで再生成するのがいいんだろう。
さて、上記の遷移はあくまでJavaの部分でのActivityの各イベントハンドラで検知した感じの情報。
これを C/C++の部分でどうやろうか、というのが問題となってくる。従来までの作りをしているアプリケーションだと、最初に初期化して、アプリ内グローバルなインスタンスはアプリ最後まで生き残ることを想定している。このグローバルなインスタンスにはグラフィックスやサウンドの管理部分がいたりする。
書籍「Androidネイティブプログラミング」 にもある例だと、surfaceCreatedのタイミングで初期化を行い、onDrawFrameの部分でC/C++のメインループへ実行を飛ばしている。これの作りをそのままにしておくと、アプリの切り替えから復帰しようとした時には常に初期化処理から実行されることになる。2重初期化を検知してエラーとか出すような作りにしていれば、そこでエラーとなる。あるいは初期化から再スタートなので、先ほどまでのデータが残っていたりはしない。
さて、もう1度よく考えてみよう。
再初期化が必要なのはいったい何か。アプリケーションのデータは初期化してしまう必要はあるか?
そう。必要なのはデバイスにアクセスしているようなグラフィックスやサウンドの部分についてだけ。その他のアプリケーションがメモリに持っている各データ状態はそのままでいいはずである。画面遷移するまでに使っていたリソースを再度確保してあげるコードを用意して、ほかの状態から戻ってきたときにその部分を処理するようにすれば、理論上はそのアプリケーションは切り替え前の状態に戻るわけで問題なく再開が可能なはずだ。
では、リソースの解放や再確保の部分はどこで行うべきか。
個人的には次のように考えている。onPauseの部分でリソースの解放処理を行ってしまい、ほかのアプリケーションの邪魔をしないようにする。これにはリソースを減らしてあげることでAndroidシステムから真っ先に消される可能性を幾分か減らしてくれるはずだ。 そしてこのonPauseを通過したことを示すフラグを立てておく。
リソースの再確保の部分はどこに書くのか、実行するのか。これはonDrawFrameの中でやるか、surfaceCreatedあたりでやるかしか方法はなさそうである。理由はコンテキストの再生成が行われた後、ウィンドウが表示されていることなどその他の要因があるからである。どちらの部分にせよ、C/C++へのジャンプコードが入っているので、先ほど覚えておいたフラグを用いてonPauseを通過済みであるならリソースの再確保処理を行ってあげるコードを実行すればよいだろう。最後に忘れてはならない点は、このonPauseの通過フラグは onCreateのタイミングで確実にfalseを設定してあげること。
理由を説明すると、このフラグはおそらくグローバルに持つ感じになるだろう。Androidのシステムにおいてメモリイメージは再利用される可能性があるので、あるタイミングではこのフラグがtrueのまま最初から実行されレしまう可能性もある。一応onPause, onStopからonCreateへの遷移ではメモリ解放となっているが、onCreateでfalseにしてしまったほうが安全に思える。
まとめると、onPauseの部分で onLostDevice相当の関数を実装&実行させ、onResumeの後のsurfaceCreated/onDrawFrame内でonResetDevice相当の関数を実装&実行させれば、リソースの復帰が可能だろうと思われる。
AndroidのGLSurfaceViewが面倒を見てくれるのは、EGL,GLのコンテキストの再生成まで。それ以外のテクスチャやシェーダー、FBO,VBOなどは再生成を手動で行う必要がある。それはオールJavaで書いてあっても同様だ。