Edited at

Androidでスプラッシュ画面を作る方法

More than 1 year has passed since last update.


⚠️ 注意

以下わりと古い内容です。いまだと、Activity Theme の android:windowBackground の方が早く描画されることを利用した実装が有名かもです。

Splash Screens the Right Way など参考にしてみてください。


作らないという選択肢

いきなりタイトルと矛盾しますが、Androidにおいてスプラッシュ画面は必要がないので 作るな

大抵はiOSの真似でスプラッシュを表示させているのだと思うのだけど、iOSは体感起動時間を短縮させるという哲学のためにスプラッシュ画面を導入している。


  • iOS7以降、スプラッシュ画面としてアプリの起動状態の画面と似せたものを利用することが推奨されている。(=機敏に起動していると錯覚させることが目的で、 ブランディング目的の利用は非推奨

  • iOS6以降のViewControllerの復元機能においては、アプリがバックグラウンドに移行した瞬間の画面をPNGで保存し、次回起動時のスプラッシュとして表示することで、復元処理が行われていることを意識させないようにする、という凝った仕様だった。もっとも、この機能はその後のマイナーアップデートで消えたけど。

もちろん、プラットフォームが押し付けるやり方を鵜呑みにすることが常に正しいわけじゃない。iOSの良さを取り入れたいという考え方が間違っているとは思わない。ならば表層的な画面表示ではなく、体感起動時間を短したいという哲学を真似しよう。

その最適解は余計なスプラッシュを表示しないことです。


スプラッシュ画面の作り方

とはいっても、クライアント様のご意向でスプラッシュ画面を表示しなければならないということはよくある。すごくよくある。ありまくる。

作ること自体は簡単なのだけど、それゆえにストア上位のアプリでも間違った実装をしていることがある。

実装方法は様々あるが、ここでは一般的だと思われるスプラッシュ用のActivityを用意する方法について考える。恐らくそれはベストな選択肢ではない。ただクライアント様の意向を満たすという条件は達せられる。

(個人的には、DialogFragmentを用いるのがベストなのではと思うが、実証する気もしない。)


AndroidManifestを有効活用する

Activityにはバックスタックの概念がある。スプラッシュをActivityとして実装する場合、二度とスプラッシュのActivityを表示しないための処理を入れる必要がある。

真っ先に考え付くのは、画面遷移後にfinish()することだろう。

しかしスプラッシュからの画面遷移の出口が一つだけになるとは限らない。初回起動時、バージョンアップ時には機能のチュートリアルへ遷移したいケースが出てくる。

全ての遷移で箇所でfinish()を書くというのが一つの手だが、当該のActivityのマニフェストにnoHistory="true"を記述するのがはるかに手っ取り早い。noHistory="true"を指定したActivityは、バックボタンによって戻ることはできなくなる。

ただし両者の挙動は異なる点に注意。

finish()による実装ではスプラッシュ画面を離れた時点でそのActivityはスタックから取り除かれる。対してマニフェストにnoHistory="true"を指定した場合には その画面に戻ることはできないが、スタックには存在している 状態となる。実際にスタックから取り除かれるタイミングは、タスクを切り替えた際などとなる。

よほど意味不明な処理をしない限り(例えばnoHistory="true"を指定したActivityで巨大なリソースを管理しているとか、singleTasksingleInstanceを指定する必要があるとか、親に指定してNavUtilsでUpの遷移を行うとか)気にする必要はないはずだが…。


Handlerを使う時にはアプリ終了判定を入れよう

スプラッシュを数秒間表示して、その後目的のActivityを表示する場合、Handlerを使うことが多い。

よくある ダメな実装例 が以下。

    new Handler().postDelayed(new Runnable() {

@Override
public void run() {
Intent intent = new Intent(SplashActivity.this, DestinationActivity.class);
startActivity(intent);
}
}, 3000);

この実装の問題は、ユーザーがスプラッシュ画面の時点でバックボタンを押し、Activityを終了させたつもりでも、問答無用で次の画面へ遷移すること。

お仕着せのブランディング画面を長々と見せ付けられた上に、終了させたはずのアプリから、ゾンビのように画面が立ち上がれば、自分なら即座にアンインストールする。

対策としては、



  • HandlerによってLooperに積まれた画面遷移のタスクを、何らかのタイミングでremoveCallBacks()を用いてキャンセルする。タイミングとしては、onPauseまたはonStop辺りが候補になると思われる。


  • isFinishing()Activityがまだ終了させられていないことを判定してからstartActivityを呼び出す。

などがある。

スプラッシュからの遷移をキャンセルした場合、postDelayed()を再度呼ぶ必要がある。確実に一度だけ遷移することを保証するためには、onResume()で画面遷移のタスクをHandlerに渡して、onPause()でキャンセルする前者の手法が推奨されると思われる。

しかしこれをやってないアプリはほんとに多い。マジで多い。


スプラッシュ画面に余計な処理を入れないこと

スプラッシュのActivityには、メイン画面への遷移以外の特別な処理は一切させない、という認識であるべき。

なぜなら、必ずスプラッシュ画面から起動されるという保証がないからだ。

例えばアプリケーションの初期化処理をスプラッシュのActivityに書いたとしよう。開発した時点ではそれで問題がないかもしれない。しかし後から要件が追加され、スプラッシュActivity以外の画面から起動するパターン生まれたらどうするべきなのだろうか?

最悪なのはコピペによって初期化処理が分裂することだ。気の利いたプログラマなら初期化処理を一箇所に集めて、そちらを呼び出すようにリファクタリングしてくれることが期待できる。

しかしアプリケーションスコープで必要なデータの管理は、カスタムなApplicationを作成し、アプリケーションのライフサイクルメソッドで制御することができる。アプリケーションで一度だけ行いたい処理があるならば、可能な限りカスタムApplicationを作成すること。

ただしActivityContextと強く紐付く処理(AsyncTaskの実行や、ダイアログの表示など)を既にスプラッシュに記述してしまっている場合、それらの処理を差し替えるのは非常に困難となる。 スプラッシュ画面では余計な処理を実行しない ことが強く推奨される。