본문 바로가기

Android/Android Version

[Android] Android 12 Splash Screen 사용하기.

728x90

Android 12부터 Splash API를 제공해준다.

필자가 앞서 Android12에 대한 변경점을 작성한 글이 있는데, 그곳에는 Splash Screen에 대한 설명이 없다. 그 당시에는 해당 부분이 얼마나 영향을 끼치는지에 대해 생각을 안 했던 것으로 보인다.

 

Splash Screen을 사용하여 기존에 별도로 만들어서 사용하던 Splash 화면이 아닌, API를 통해 앱을 실행했을 때 설정한 Splash 화면을 보여주게 된다.

따라서, 기존의 대부분의 앱들이 Splash를 구현하여 사용하였는데 대응을 하지 않으면 앱 아이콘이 default로 보인 후에 스플래시 화면으로 넘어가게 된다.

 

이번 글에서는 Android 12부터 제공하는 Splash Screen을 어떻게 사용해야 하는지 알아볼 예정이다.


우선, Splash Screen의 경우 Android 12부터 사용이 가능하다.

따라서, minSdkVersion이 31이 아닌 경우, 해당 기능을 사용하기 위하여 31 버전에서 사용이 가능한 Theme를 별도로 만들어서 사용해야 한다.

 

보통, minSdkVersion은 최신 버전으로 설정하지 않기 때문에 기존의 minSdkVersion은 그대로 놔두고 31 버전에서 사용이 가능한 Theme를 만들어서 사용하거나, 31 버전을 target 하는 Item으로 만들어서 사용한다.

 

필자는 두 가지 방법 중, 31버전에서만 사용이 가능한 Theme 파일을 생성하여 작업을 진행할 예정이다.

 

안드로이드 공식 페이지에서 Splash API에 대한 글을 확인해 보면,

 

<item name="android:windowSplashScreenBackground">@color/...</item>

 

이와 같이 Theme에 선언하여 사용하라고 한다.

해당 부분을 우선 복사하여 themes.xml에 넣어보면 다음과 같이 나온다.

 

 

사용하기 위해서는 31 버전을 사용해야하는데, 현재 min 버전이 24라고 한다.

 

여기서 Alt+Enter를 통해 해결할 수 있는 가이드를 확인해 보면

 

 

31 버전에서 사용할 수 있도록 override 하는 방식을 안내해 준다.

 

여기서 엔터를 통해 해당 방식으로 수정을 하도록 하면,

 

 

이처럼 (v31)이라는 표시가 달린, 31 버전에서 실행될 수 있는 themes.xml 파일이 생성된다.

 

이처럼 themes.xml을 override 하지 않고 사용하기 위해서는, targetApi 속성을 사용하면 된다.

 

<item name="android:windowSplashScreenBackground" tools:targetApi="s">#ffffff</item>

 

이처럼 targetApi 속성을 통해 31 버전의 키워드인 s를 넣어주게 되면, 31 버전에서만 해당 Item을 사용하겠다는 의미가 되어 31 버전만을 위한 xml 파일을 override 하지 않아도 사용이 가능하다.

하지만, 별도의 xml 파일을 만들어서 사용하는 것이 관리적인 측면에서 더 좋을 것이라고 판단하여 필자는 별도의 파일을 만들어서 사용하였다.

 

<!-- SplashScreen의 Background 색상.-->
<item name="android:windowSplashScreenBackground">#442233</item>
<!-- SplashScreen에서 사용 될 아이콘. 설정하지 않으면 앱 아이콘이 사용된다. -->
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_animation_splash</item>
<!-- 앱 아이콘의 Default 배경 색상. 해당 값을 설정 하지 않으면 아무런 색도 보이지 않는다. -->
<item name="android:windowSplashScreenIconBackgroundColor">@color/white</item>
<!-- Animation이 그려지는 Max 시간. 해당 시간이 지나면 애니메이션이 동작하는 도중이라도 마지막 결과로 이동한다. -->
<item name="android:windowSplashScreenAnimationDuration">5000</item>
<!-- 화면 하단에 나오는 Branding 이미지. -->
<item name="android:windowSplashScreenBrandingImage">@drawable/ic_launcher_background</item>

 

Splash Screen을 사용하기 위하여 필자가 설정한 xml item이다.

상단 주석을 통해 어떠한 역할을 하는지 기입해두었다.

 

AnimationDuration의 경우, 지정하지 않으면 Animation이 정상적으로 완료된 후에 마지막 결과로 이동하게 되며, 필자처럼 지정해주게 되면 애니메이션이 진행 도중이라도 해당 시간이 지나면 마지막 결과로 이동하게 된다.

즉, 해당 속성을 통해서 애니메이션의 지속 시간의 최대 값을 정해주어 코드단에서 애니메이션 작업을 수행할 때 참조할 수 있도록 도와주는 역할이라고 생각한다.

 

이와 같이 테마를 설정하였으면, IntroActivity를 만들어보도록 하자.

필자는 항상 사용하던 Clean Architecture 샘플 예제에 IntroActivity를 추가하여 사용하였다.

 

 

기존에 MainActivity를 Launcher로 사용하고 있었는데, IntroActivity를 만들고 Launcher로 설정 후 startMainActivity를 통해 MainActivity를 호출하도록 하였다.

 

private fun initData() {
    // do something .. Set Data
    // 별도의 데이터 처리가 없기 때문에 3초의 딜레이를 줌.
    // 선행되어야 하는 작업이 있는 경우, 이곳에서 처리 후 isReady를 변경.
    thread(start = true) {
        for (i in 1..3) {
            Thread.sleep(1000)
            Log.d("SleepLog", "Sleep .. i = $i")
        }
        // remove Splash Screen after 3 sec
        isReady = true
    }
}

 

initData는 임시로 작성해둔 함수로, 보통 Splash가 진행되는 동안 별도의 작업을 수행하는 경우가 많은데, 그것을 상정해서 만들어 둔 함수이다.

필자가 작성해둔 샘플에서는 별도의 작업을 하지 않으므로, 3초간의 딜레이를 주고 이후 작업을 진행하도록 하였다.

 

핵심이 되는 initSplashScreen 함수를 확인해보도록 하자.

 

private fun initSplashScreen() {

    initData()

    val content: View = findViewById(android.R.id.content)
    // SplashScreen이 생성되고 그려질 때 계속해서 호출된다.
    content.viewTreeObserver.addOnPreDrawListener(
        object : ViewTreeObserver.OnPreDrawListener {
            override fun onPreDraw(): Boolean {
                // Check if the initial data is ready.
                return if (isReady) {
                    // 3초 후 Splash Screen 제거
                    content.viewTreeObserver.removeOnPreDrawListener(this)
                    true
                } else {
                    // The content is not ready
                    false
                }
            }
        }
    )

    // 31 버전일 때와 아닐 때의 분기 처리.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        errorGuard()

        // splashScreen이 종료 될 때 애니메이션 컨트롤.
        splashScreen.setOnExitAnimationListener { splashScreenView ->
            // Create your custom animation.
            val slideUp = ObjectAnimator.ofFloat(
                splashScreenView,
                View.TRANSLATION_Y, // View.TRANSLATION_X 로 하면 왼쪽으로 사라짐.
                0f,
                -splashScreenView.height.toFloat()
            )
            slideUp.interpolator = AnticipateInterpolator()
            slideUp.duration = 1000L // Splash Screen이 사라지는 시간.
            isStart = true

            // Custom animation이 끝나면 SplashScreenView.remove 호출
            slideUp.doOnEnd {
                splashScreenView.remove()
                startMainActivity()
            }

            // Run your animation.
            slideUp.start()
        }
    } else {
        // 31 버전이 아닌 경우, SplashScreen이 없으므로 Splash Activity를 실행시키는 등 별도의 처리.
        startMainActivity()
    }
}

 

주석으로 해당 라인들이 어떠한 동작을 하는지 작성은 해두었다.

 

우선, addOnPreDrawListener를 통하여 Splash Screen이 그려지는 것에 대한 결과를 받는다.

onPreDraw는 Splash Screen이 보이는 동안 계속해서 호출되며, isReady가 true일 때 true를 반환하면서 해당 스크린을 지우도록 처리하였다.

isReady는 initData에서 3초 뒤에 true로 변경하도록 되어있으므로, Splash Screen이 3초 후에 제거된다.

 

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    ...
}

 

31 버전 이상일 경우에만 수행되도록 하였고, 내부에서는 setOnExitAnimationListener를 호출하고 있다.

 

 

해당 내용을 설명하기 앞서, 왜 31 버전을 체크하는 곳이 여기부터 나오는가? 에 대한 의문을 가질 수 있다.

Splash Screen 자체가 31 버전부터 지원하고 있는데, 처음에 설명한 addOnPreDrawListener의 경우 버전에 상관없이 수행되게 되어있다.

Android 12(31) 버전 이후에만 제공하는 기능을 이전 버전에서 호출하면 안 되는 것 아닌가 싶겠지만, 해당 부분은 버전에 상관없이 사용이 가능한 부분이기 때문에 고려하지 않아도 된다.

 

하지만, Android 12 이전 버전에 대한 대응을 하지 않으면 해당 부분에서 문제가 발생하게 된다.

필자가 선언한대로 31 버전에 따른 Theme를 override 해서 사용하는 경우, 그 외의 버전에 대해서는 Splash Screen에 대한 한 속성 값이 존재하지 않는다.

그렇기 때문에 Android 12 버전 이하의 경우 해당 부분에서 문제없이 IntroActivity의 화면을 보여주고 넘어갈 것이라고 생각했지만, 해당 부분이 적용되어 흰 화면으로 일정 시간 동안 유지되게 된다.

 

<item name="android:windowSplashScreenAnimationDuration">5000</item>

 

Splash Screen의 애니메이션이 진행되는 시간이다.

따라서, 해당 부분을 Android 12 이전 버전에서 선언하여 Splash Screen을 사용하는 경우에만 해당 시간을 사용하도록 해야한다.

 

// Splash Screen
implementation 'androidx.core:core-splashscreen:1.0.0-rc01'

 

gradle에 해당 라이브러리를 추가함으로써 Android 12 이전 버전에서도 대응이 가능하게 된다.

 

<item name="windowSplashScreenAnimationDuration">100</item>

 

기존 31 버전에만 사용되는 경우 android:~ 형태로 선언하였는데, 위의 라이브러리를 추가하게 되면 31 버전 미만에서 이처럼 선언해서 사용할 수 있게 된다.

 

<!-- SplashScreen의 Background 색상.-->
<item name="windowSplashScreenBackground">#442233</item>
<!-- SplashScreen에서 사용 될 아이콘. 설정하지 않으면 앱 아이콘이 사용된다. -->
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_animation_splash</item>
<!-- 앱 아이콘의 Default 배경 색상. 해당 값을 설정 하지 않으면 아무런 색도 보이지 않는다. -->
<item name="windowSplashScreenIconBackgroundColor">@color/white</item>
<!-- Animation이 그려지는 Max 시간. 해당 시간이 지나면 애니메이션이 동작하는 도중이라도 마지막 결과로 이동한다. -->
<item name="windowSplashScreenAnimationDuration">100</item>

 

이처럼 31버전 미만에서 사용할 수 있도록 선언을 할 수 있는데,

필자가 테스트해보았을 때 AnimationDuration을 제외한 나머지 3개의 속성 값들은 정상적으로 적용이 안 되는 것으로 보인다. Splash Screen을 제공하지 않는데, 해당 속성 값이라서 그런 것으로 보인다.

 

하지만 이렇게 처리를 해두어도, 정상적으로 속성 값이 적용되지 않는 경우가 있어 해당 부분을 31 버전 이상일 경우에만 적용되도록 처리를 해두었다.

해당 부분에 대해서 이유를 정확히 알게 되면 수정토록 하겠다.

 

다시 setOnExitAnimationListener를 호출하는 부분으로 돌아가서,

종료 애니메이션에 대한 이벤트를 추가해주는 부분의 경우 Android 12 이상의 버전에서만 제공하고 있으므로 이전 버전에서는 해당 부분이 호출되서는 안 되며, 이전 버전에서 접근이 가능한 경우에는 에러 표시가 나오기 때문에 반드시 분기 처리를 해주어야 한다.

 

따라서, 이처럼 버전에 따른 분기 처리를 진행하고 Android 12 버전이 아닌 경우에는 정상적으로 다음 화면으로 넘어가도록, Android 12 버전 이상인 경우에는 Splash Screen이 끝난 후 애니메이션을 통해 해당 화면을 제거하고 이동하도록 추가해 둔 것이다.

 

해당 애니메이션을 설정하는 함수는 주석을 통해 적어두었으니 확인하면 별도의 다른 애니메이션을 만들 수 있을 것이다.

여기서 중요한 점은, 해당 "스크린 전체"에 대한 애니메이션이라는 것이다.

필자는 처음 해당 애니메이션을 설정할 때, 화면 전체가 아닌 Splash Screen의 가운데에 나오는 아이콘 부분만 해당하는 것인 줄 알았는데, 해당 방법을 사용하게 되면 화면 전체에 대한 애니메이션 설정이 되어서 생각보다 재미있는(?) 결과가 나오게 된다.

 

마지막으로, errorGuard함수를 확인해보자.

 

private fun errorGuard() {
    thread(start = true) {
        Thread.sleep(5000)

        if (!isStart) {
            startMainActivity()
        }
    }
}

 

5초 후에 isStart가 false이면 메인 액티비티로 이동시켜주는 함수이다.

 

해당 함수가 왜 필요한가? 싶겠지만, 필자가 위와 같은 코드를 추가하고 빌드를 했을 때 문제가 발생했다.

최초에 빌드를 수행하여 앱을 실행하게 되면, 이유는 모르겠지만 정상적으로 Splash Screen이 실행되지 않는다.

좀 더 자세히 말하자면, xml에 표기한 배경색은 나오지만, 애니메이션을 추가한 아이콘이라던지, 코드단에 추가한 화면 자체의 애니메이션이 동작하지 않는다.

 

여기서, 화면 자체의 애니메이션이 동작하지 않게 되므로, setOnExitAnimationListener 함수가 호출되지 않게 되고, splideUp.doOnEnd { ... } 함수 또한 호출되지 않아 Splah Screen이 끝난 후 IntroActivity의 화면이 나온 상태에서 다음 화면으로 넘어가지 않는 문제가 발생했다.

 

하지만, 최초의 빌드 후 실행이 아닌 앱 종료 후 다시 실행을 하면 정상적으로 애니메이션이 나오고 종료되고, 메인으로 넘어가는 동작을 수행하는데 최초의 경우에만 해당 문제가 발생하였다.

따라서 필자는 최초의 빌드의 경우에 해당 문제를 통해 다음 화면을 확인할 수 없다는 것이 마음에 들지 않아서, 이처럼 5초의 딜레이를 주고 화면이 넘어가지 않는 경우 강제로 넘어갈 수 있도록 방어 코드를 추가해 두었다.


Android 12버전 이상과 미만의 Splash Screen 대응을 진행해 보았는데, 

Themes.xml에 적용한 속성 값들이 어째서 정상적으로 적용되지 않는지, 최초 실행 시 왜 Splash Screen이 제대로 동작하지 않는지에 대해선 아직도 잘 모르겠다.

 

그래도 불필요하게 Splash가 두번 나오는 경우를 방지하기 위해서 추가해야 할 코드는 모두 적용해본 것 같고,

현재 Splash Screen에 대한 애니메이션 처리를 넣어두어서 IntroActivity의 화면이 보이지만 애니메이션을 지우고 속성 값을 좀 변경해준다면 Android 12 이상 버전에서는 Splash Screen만 보이도록 수정이 가능할 것 같다.

 

Splash Screen의 애니메이션 처리는..

화면 전체가 움직이는 애니메이션이다 보니 사용할 수 있는 용도가 한정적일 것으로 보인다.

 

해당 부분에 대해서 적용해보고, Android 12 미만과 이상 버전에 대해서 테스트를 해보면서 글을 작성해보았지만, 개선되어야 할 부분은 아직까지 많은 것으로 보인다.

작업을 진행하다 좀 더 개선을 하게되면 추가적인 글을 작성하거나, 해당 글을 수정해두도록 하겠다.

 

해당 게시글에 사용된 예제는 Github에 올려두었다.

https://github.com/HeeGyeong/CleanArchitectureSample

 

GitHub - HeeGyeong/CleanArchitectureSample

Contribute to HeeGyeong/CleanArchitectureSample development by creating an account on GitHub.

github.com

 

728x90