Android12 SplashScreen対応
初めに
ポートでAndroid開発をしている @shxun6934 です。
今更ながら、弊社のAndroidアプリで独自に作成したSplashScreen
をAndroid 12 以降はデフォルトで表示されるSplashScreen
に移行しました。
その移行について、お話ししていきたいと思います。
SplashScreen
や移行の仕方については、 公式に載っているので、そちらも確認してみてください。
SplashScreenとは
アプリが起動した際に表示されるアニメーション画面をSplashScreen
と言います。
Android 12 以降からは、アプリ起動時にSplashScreen
がデフォルトで表示されるようになりました。
SplashScreenの役割
SplashScreen
は、アプリ起動からホーム画面 (AndroidManifest.xml
でLAUNCHER
を設定している画面) を表示するまでに、必要最低限のデータの読み込みを行うための画面だと思っています。
コンポーネントやライブラリの読み込みまたは初期化は、極力、必要になった時に行い、Application
のonCreate
をできるだけ軽量にするようにGoogleでは推奨しています。
移行した目的
今までの実装の仕方では、AndroidOSバージョンによる挙動の違いが出てしまいました。
その挙動の差をなくすために移行しようと思ったのが、今回の対応の目的になります。
Android 12以降と以前の挙動の違い
Android 11 以前はSplashScreen
が提供されていないので、弊社ではSplashScreen
を表示するSplashActivity
を作成して、そこで起動時に必要なロジックを書いていました。
このやり方では、Android 12になるとデフォルトのSplashScreen
と独自のSplashScreen
が連続して表示される挙動になってしまいます。
そのため、 ユーザーはホーム画面が表示されるまでの時間が長く感じるので、UXの観点から適切でないと感じました。
移行
では、実施に公式ドキュメントを見ながら移行していきます。
Libraryの導入
compileSdk
を31以上にして、androidx.core:core-splashscreen
を導入します。
android {
// Android 12
compileSdkVersion 31
...
}
dependencies {
implementation 'androidx.core:core-splashscreen:1.0.0'
}
画面
SplashScreen
のテーマを作成します。
<!-- Application Theme -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="colorPrimary">@color/...</item>
...
</style>
<!-- SplashScreen Theme-->
<style name="AppTheme.SplashScreen" parent="Theme.SplashScreen">
<!-- 表示するアイコンを設定する。(必須)-->
<!-- アイコン自体をアニメーションさせたい場合は、AnimationDrawableかAnimatedVectorDrawableを使用したdrawableリソースを設定する。 -->
<item name="windowSplashScreenAnimatedIcon">@drawable/...</item>
<!-- アイコンの背景色を設定する。-->
<item name="android:windowSplashScreenIconBackgroundColor">@color/...</item>
<!-- SplashScreenの背景色を設定する。 -->
<item name="windowSplashScreenBackground">@color/...</item>
<!-- アイコンがアニメーションする場合、アニメーションさせる時間を設定する。 -->
<!-- デフォルトでは、10000ms。推奨は、1000ms以内-->
<item name="windowSplashScreenAnimationDuration">1000</item>
<!-- SplashScreenを表示するActivityのThemeを設定する。(必須) -->
<item name="postSplashScreenTheme">@style/AppTheme</item>
</style>
表示
作成したテーマをSplashScreen
を表示したいApplication
またはActivity
のテーマに設定します。
(AndroidManifest
で設定しています。)
<manifest>
<application android:theme="@style/AppTheme.SplashScreen">
<!-- or -->
<activity android:theme="@style/AppTheme.SplashScreen">
...
Activity
のonCreate
でsuper.onCreate()
の前にinstallSplashScreen
を呼び出します。
class SplashActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
// SplashScreenの呼び出し
installSplashScreen()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
...
これでSplashScreen
をカスタマイズし、表示することができます。
既存のSplashActivityの扱い
AndroidOSのSplashScreen
の表示はできましたが、まだ既存のSplashScreen
が残っている問題が解決されていません。
なので、以下のどれかの対応を行う必要があります。
- SplashActivityを表示されないようにする。(SplashActivityで行うロジックは残す)
- SplashActivityを表示させるが、違和感のないようにする
- SplashActivityを削除し、SplashActivityで行っていたことを他のActivityに移す
弊社では、2の対応を行いました。
SplashActivity
では、ログインチェック・計測サービスにユーザー情報を送る・DynamicLink
による開始Activityのチェックなどを行っていて、他Activityに移すとしても他Activityが肥大化してしまいます。
また、通信できなかった場合は、再通信させるためのダイアログを表示しているので、親Viewが必要になります。
以上のことから、ロジックを処理するためにActivity
を残し、View
の描画は通信に失敗した時のみ描画する方針にしました。
(Googleは削除を推奨しているので、近日対応します。)
SplashScreenの描画時間を伸ばす
アプリの起動に必要なデータが揃うまでは、SplashScreen
を表示しておかないといけません。
SplashScreen
を表示し続けるためには、ViewTreeObserver.OnPreDrawListener
を使用して、アプリを一時停止しておきます。
class SplashActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// rootでOnPreDrawListenerを監視する。
val content: View = findViewById(android.R.id.content)
content.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
// 準備ができた場合は、trueで描画開始。
// 準備中の場合は、falseで一時停止。
if (isReady) {
// rootのOnPreDrawListenerを削除する。
content.viewTreeObserver.removeOnPreDrawListener(this)
true
} else {
false
}
}
})
}
}
SplashActivityのViewを表示しない
Viewを描画しないようにするには、SplashScreen#setKeepOnScreenConditionを使用します。
class SplashActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Splash Screenの呼び出し
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
// SplashScreenを表示し続ける
// trueの場合は、SplashScreenを表示し続ける
// falseの場合は、ActivityのViewを表示する
splashScreen.setKeepOnScreenCondition { true }
}
}
SplashActivityのViewをSplashScreenに寄せる
SplashScreen
とSplashActivity
のView
を同じデザインにすることで、画面が切り替わっているように見えないようにすることもできます。
SplashScreen
に表示されるアイコンのサイズは、背景があるアイコンの場合、240×240dp
で直径160dp
の円、背景がないアイコンの場合、288×288dp
で直径192dp
の円になります。
弊社では
ViewModel
でデータとデータが準備できたかどうかをステータスとして持っておきます。
準備ができたら、SplashScreen
の描画を再開、データの状態によって、SplashScreen
を表示するか、SplashActivity
のView
を表示するかを決めます。
通信エラーをダイアログで表示するためには、親Viewが必要です。
そのため、通信に成功した場合のみ、splashScreen.setKeepOnScreenCondition { true }
を宣言して、Viewを描画しないようにしています。
class SplashActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
val content: View = findViewById(android.R.id.content)
content.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
// viewModelのデータが揃ったら描画開始する。
return if (viewModel.result.value is SplashViewModel.Result.Success) {
content.viewTreeObserver.removeOnPreDrawListener(this)
true
} else {
false
}
}
})
lifecycleScope.launch {
viewModel.result.collect {
when (it) {
is SplashViewModel.Result.Success -> {
// SplashScreenして、Activityに遷移させる
splashScreen.setKeepOnScreenCondition { true }
startNextActivity()
finish()
}
is SplashViewModel.Result.Error -> {
// SplashActivityのViewを表示して、ダイアログを表示する。
showConnectErrorDialog()
}
}
}
}
}
}
これで、SplashScreen
の移行ができました。(完全な移行ではない。)
終わりに
SplashScreen
の実装方法と移行の仕方について見ていきました。
SplashScreen
自体の実装はそれほど難しくないですが、既存のSplashScreen
との折り合いをどうするかが一番迷いました。
完全な移行を行うためには、SplashActivity
のロジックを各ActivityやViewModelに移行する必要があるので、
影響範囲を調べながら最適な移行をした方がいいと思いました。
弊社でも完全な移行に向けて色々調査・実装していきたいと思います。
また、SplashScreen
にアニメーションを使用している場合は、AnimationDrawable
でアプリアイコンを作成し、アニメーションの挙動も設定しなくてはいけないです。
ここら辺を触ることができれば、イケてるアプリに近づけそうです。
Discussion