Android12からのsplash screenに対応させる

# Android 12(API 31)からのsplash screenの変更
[Androidでスプラッシュ・スクリーンを出してみる](https://qiita.com/cozyk100/items/dc2b697a2d64aeeaf899)で、Androidでsplash screenを出す方法を紹介しましたが、Android12(API 31)からsplash screenの仕組みが変わっています。

これまでの上のURLで紹介した方法でもsplash screenは出せますが、端末のバージョンがAndroid 11(API 30)以下からAndroid12(API 31)に変わると共にsplash screenを表示させる方法を変更する必要があります。

公式ホームページ
[スプラッシュ画面](https://developer.android.com/about/versions/12/features/splash-screen?hl=ja)
[既存のスプラッシュ画面の実装を Android 12 以降に移行する](https://developer.android.com/guide/topics/ui/splash-screen/migrate?hl=ja)

## AndroidX SplashScreen 互換性ライブラリに移行する
そのためにはAndroidX SplashScreen 互換性ライブラリを使用します。上のURLで従来の方式とAndroidX SplashScreen 互換性ライブラリうぃ使った場合で、ずいぶんと見え方、動きが違います。

従来の方式
<img width="30%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/8c3f2080-e4cf-3edc-03b1-1050e1b13acc.gif">
AndroidX SplashScreen 互換性ライブラリを使用した場合
<img width="30%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/b8a69a1a-1559-facb-3b22-f114bcfc0d17.gif">
違いが一目でわかると思います。従来の方法では端末のスクリーン全体にsplash screenが表示されていますが、AndroidX SplashScreen 互換性ライブラリの方は中央に丸くくり抜かれて一部しか表示されていません。従来の方式ではsplash screenのデザインによってはそこにタイトル文字や、注意書きをいれてユーザにそれを読ませることができますが、AndroidX SplashScreen 互換性ライブラリだとそれが難しいです。あくまでも、「今、画面を開いているところです・・・」ぐらいの意思表示しかできません。

なんか・・・スクリーン全体に表示できないのはイマイチのような気がします。

それと、splash screenが表示されるタイミングも異なります。従来の方法ではアプリが完全に停止している状態からの起動(コールドスタート)だけでしたが、AndroidX SplashScreen 互換性ライブラリだと
| 状態名 | 状態 | スプラッシュ画面 |
|:-|:-|:-|
|コールドスタート|アプリが完全に終了している状態からの起動|表示|
|ウォームスタート|システムによりプロセスや Activity が破棄された状態からの起動|表示|
|ホットスタート|Activity がメモリ上に生存している状態からの起動|表示しない|

ウォームスタートの時も表示されることに注意しなければいけません。

なんか、表示的にも動作的にもイマイチなんですが、その分(?)実装は従来の方法に比べると簡単になっています。

# AndroidX SplashScreen 互換性ライブラリでsplash screenを実装してみる
## build.gradleから
compileSdkは31以上を指定します。依存ライブラリを追加します。
```gradle
android {
    ・・・
    compileSdk 31
}

dependencies {
    ・・・
    implementation "androidx.core:core-splashscreen:1.0.0"
}
```
## splash画面を追加する
これは違いを比較するため、前回と同じ画像を流用しました。本当は、AndroidX SplashScreen 互換性ライブラリの仕様に合わせたサイズを用意したほうがいいです。
splash画像は、静止画でもアニメーションでも可能です。アニメーションの継続時間に制限はありませんが、1,000 ミリ秒以下にすることをおすすめします。(後述)デフォルトでは、ランチャー アイコンが使用されます。

![Screenshot_20230304_222528.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/31df9b4b-ce84-68f8-bf2d-61b1ad797296.png)

## themes.xmlに追加する
themes.xmlはふたつあります。nightモード用とノーマルモード用。両方に追加します。Theme.SplashScreenを親とするstyleエレメントを追加します。
```xml
<resources xmlns:tools="http://schemas.android.com/tools">
    ・・・
    <style name="AppTheme.Splash" parent="Theme.SplashScreen">
        <item name="windowSplashScreenBackground">@color/white</item>
        <item name="windowSplashScreenAnimatedIcon">@drawable/run_businessman_aseru</item>
        <item name="postSplashScreenTheme">@style/Theme.SplashKotlin</item>
        <item name="windowSplashScreenAnimationDuration">1000</item>
    </style>
    ・・・

    <style name="Theme.SplashKotlin" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
       ・・・
    </style>
</resources>
```
上記XMLのエレメントの説明は公式ホームページ、[スプラッシュ画面](https://developer.android.com/about/versions/12/features/splash-screen?hl=ja)のアプリのスプラッシュ画面をカスタマイズするを参照してください。
windowSplashScreenAnimatedIconが上の「splash画面を追加する」で追加した画像のリソースです。
postSplashScreenThemeにはsplash screenが表示し終わった後のテーマ(通常運転のテーマ)を指定します。ここでは元々のthemes.xmlにあった、プロジェクト作成時に自動生成されたTheme.SplashKotlinを指定します

ここで注意しなければいけないのは、
> スプラッシュ画面が閉じるまでの表示時間を設定するには、windowSplashScreenAnimationDuration を使用します。最長時間は 1,000 ミリ秒です。

な、なんと、splash screenは最長でも1秒しか表示されない。そういう仕様です。(短すぎないか？)
この時間を伸ばす方法については後で説明します。

# Manifestの修正
AndroidManifest.xmlにthemes.xmlで追加したsplash screenのテーマを設定します。
```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
   ・・・
    <application
        ・・・
        android:theme="@style/AppTheme.Splash"
```

## MainActivity
onCreateでレイアウトXMLを適用する前にsplash screenを表示するように、installSplashScreen()の呼び出しを追加します。
```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        installSplashScreen()
        binding = ActivityMainBinding.inflate(layoutInflater).apply {
            setContentView(this.root)
        }
    }
}
```
以上で、AndroidX SplashScreen 互換性ライブラリでsplash screenの実装は完了です。従来の方法よりも簡単だと思います。

# splash screenの表示時間をもう少し長く、自分で制御したい。
splash screenの表示を自分で制御したい場合は以下のようにします。
公式ホームページ、[スプラッシュ画面を長時間表示する](https://developer.android.com/about/versions/12/features/splash-screen?hl=ja#suspend-drawing)では、ViewTreeObserver.OnPreDrawListener を使用した例を紹介していますが、SplashScreen#setKeepOnScreenConditionでもできます。しかし、この場合はUIを長時間ブロックしないように注意しなければなりません。
```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val splashScreen = installSplashScreen()

        splashScreen.setKeepOnScreenCondition{ true }
        // ここは適当にsleepしてるけど本当は別な処理、DB処理とか
        Thread.sleep(3000)
        splashScreen.setKeepOnScreenCondition{ false }

        binding = ActivityMainBinding.inflate(layoutInflater).apply {
            setContentView(this.root)
        }
    }
}
```

# Androidエミュレータで謎の事象
AndroidX SplashScreen 互換性ライブラリでsplash screenの実装なんですが、Androidエミュレータだとうまく表示されない場合があります。Android Studioの実行ボタンからの実行だとsplash screenが表示されないのに、エミューレタのホーム画面がからアイコンをタップすると表示される場合があります。起動のタイミングに寄るものなのかもしれません。実機がないので実機だとちゃんと表示されるのかもしれません。

各、APIレベルのエミュレータでAndroidStudioからの実行ボタンで実行してみた結果です。
* Android 11(API 30) 表示される
* Android 12(API 31)  表示されない
* Android 12L(API 32) 表示されない
* Android 13(API 33) 表示される

API 30が表示されて、API 31と32が表示されなくて、33でまた表示されるようになっているという謎事象です。

# 最後に
ソースは[gitHub](https://github.com/cozyk100/AndroidStudioProjectsPublic/tree/main/SplashKotlin12)にpushしました