[Android] 안드로이드 Splash Screen 만들기

Hwichan Ji·2021년 7월 26일
1

Android

목록 보기
4/7
post-thumbnail

🎨 액티비티 테마 사용

안드로이드 앱을 사용하다보면 종종 첫화면 전에 빈 화면이 잠깐 나오는 것을 볼 수 있는데요, 이는 앱을 새로 실행하는 경우 첫 화면을 그리는데 시간이 필요하기 때문입니다.

안드로이드 시스템은 이 시간 동안 비어있는 Placeholder Screen을 앱의 windowBackground color로 채우는데요, 액티비티 테마를 이용한다면 이 Placeholder Screen을 단순히 색깔만 가진 화면에서 Splash Screen으로 확장시킬 수 있습니다.

<!-- splash_drawable.xml -->

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

  <!-- background color -->
  <item android:drawable="@android:color/white"/>

  <!-- App logo -->
  <item>
      <bitmap
          android:src="@drawable/app_logo"
          android:gravity="center">
      </bitmap>
  </item>

</layer-list>

먼저 <layer-list>를 통해 Splash Screen으로 사용될 drawable을 만듭니다.

<!-- styles.xml -->

<resources>
  
  <style name="SplashTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
    <item name="android:windowBackground">@drawable/splash_drawable</item>
  </style>
  
</resources>

그런 다음 Spash Screen을 위한 테마를 만들고 windowBackground 속성에 앞에서 만든 drawable을 지정해줍니다.

<!-- AndroidManifest.xml -->

<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
  package="com.~~~">

  <application
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
      
    <activity android:name=".MainActivity"
      android:theme="@style/SplashTheme">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
       
  </application>

</manifest>

만들어진 테마를 첫 화면이 될 액티비티에 적용하면 앱 실행 시 빈화면이 아닌 Splash Screen이 보여지게 됩니다.

/* MainActivity.kt */

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        setTheme(R.style.AppTheme)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

액티비티가 Splash Screen과는 다른 테마를 가져야 한다면, 해당 액티비티 onCreate()에서 setTheme() 메소드를 사용하여 Splash Screen 이후에는 기존 테마가 적용되록 만들어줘야 합니다.

이 방식을 이용한다면 첫 화면을 그리는 동안 빈 화면이 아닌 Splash Screen을 보여줌으로써 사용자 경험을 개선할 수 있지만, Splash Screen에 애니메이션이나 Progress Bar를 포함할 수는 없다는 단점이 있습니다.

Handler, Runnable 그리고 Timer

많은 앱들은 Splash Screen을 위해 별도의 액티비티를 만들고 해당 액티비티에서 일정시간 대기한 후 로직에 따라 다른 액티비티로 이동하는 방식을 사용합니다.

이 방식은 주로 HandlerRunnable 클래스를 통해 구현됩니다.

/* MainActivity.kt */

class MainActivity : AppCompatActivity() {
        
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Handler().postDelayed(2000) {
            var intent = Intent(this@MainActivity, NextActivity::class.java)
            startActivity(intent)
            finish()
        }
    }
    
    override fun onDestroy() {
        handler.removeCallbacksAndMessages(null)
        super.onDestroy()
    }
}

이 방식은 Splash Screen에 애니메이션이나 Progress Bar를 사용할 수 있고, 좀 더 자유로운 Splash Screen 화면을 구성할 수 있습니다. 또한 특정 액티비티로 리다이렉션 하는 로직도 추가할 수 있습니다.

그러나 하드웨어의 뒤로가기 버튼을 누르는 경우 handler의 동작을 취소할 수 없고 예약된 동작으로 인해 화면이 랜덤하게 팝업된다는 단점이 있습니다. 이는 메모리 누수를 발생시키거나 데이터 손실을 유발할 수 있습니다.

이를 해결하기 위해서 Timer 클래스를 사용하기도 합니다.

/* MainActivity.kt */

class MainActivity : AppCompatActivity() {

    var timer = Timer()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_timer_splash)

        timer.schedule(2000) {
            var intent = Intent(this@MainActivity, NextActivity::class.java)
            startActivity(intent)
            finish()
        }
    }

    override fun onPause() {
        timer.cancel()
        super.onPause()
    }
}

TimerHandler와 달리 동작을 취소할 수 있습니다. 다만 Timer는 완전히 새로운 쓰레드를 백그라운드에 생성하기 때문에 TimerTask 객체 안에서 UI와 상호작용할 수 없고, 메모리 측면에서 무거운 작업이며, 쓰레드 스위칭으로 인해 느리다는 단점이 있습니다.

💡 Coroutine 사용

HandlerRunnable 클래스 대신에 코틀린의 Coroutine을 이용할 수도 있습니다.

/* MainActivity.kt */

class MainActivity : AppCompatActivity() {

    val activityScope = CoroutineScope(Dispatchers.Main)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutines_splash)

        activityScope.launch {
            delay(2000)

            var intent = Intent(this@MainActivity, NextActivity::class.java)
            startActivity(intent)
            finish()
        }
    }

    override fun onPause() {
        activityScope.cancel()
        super.onPause()
    }
}

이 방식은 CoroutineScopeDispatchers.Main을 사용하기 때문에 작업을 Main UI Thread에서 수행하며, Thread Switching이 없습니다. 또한 코루틴의 동작을 취소할 수도 있습니다.

즉, Coroutine은 빠르고 안정적이며 메모리를 덜 쓰고, UI에 접근할 수 있다는 장점이 있습니다.

Reference

profile
안드로이드 개발자를 꿈꾸는 사람

0개의 댓글