App Startup
몇몇 클래스는 사용하기 전에 초기화해야 하는 경우가 있다. 예를 들어 로그 라이브러리인 Timber는 앱을 시작한 후 최대한 빨리 초기화해야 한다. ``Application`` 클래스 안에서 초기화할 수도 있지만, startup 라이브러리를 사용하면 앱이 시작될 때 자동으로 초기화할 수 있다.
설치
``build.gradle`` 파일에 다음 의존성을 추가하자.
dependencies {
implementation("androidx.startup:startup-runtime:1.1.1")
}
Startup 라이브러리의 장점
이전에는 클래스를 ``ContentProvider``에서 초기화하는 경우도 있었지만, ``ContentProvider`` 자체가 만들기 비싼 데다가 결정적으로 클래스의 초기화 순서를 지정할 수 없다는 문제가 있다. 어떤 클래스를 초기화할 때 Timber를 사용하고 싶다면, Timber가 이미 초기화됐어야 한다. 하지만 ``ContentProvider``는 초기화 순서를 보장하지 않기 때문에 곤란하다.
Startup 라이브러리를 사용하면 위의 문제를 해결할 수 있다. Startup의 ``Initializer<T>``는 ``ContentProvider``보다 가볍고 빠르며, 초기화 순서를 지정할 수도 있다. 어디 한번 사용해 보자.
Initializer 구현
``Initializer<T>`` 인터페이스를 implement하여 초기화 작업을 구현하자. ``Initializer<T>``에는 다음의 두 함수가 선언되어 있다.
- ``create()``: 클래스를 초기화하고 ``T`` 타입 객체를 반환한다. ``T``는 초기화할 클래스이다. 초기화한 클래스의 인스턴스를 반환하기 어렵다면 ``Unit``으로 지정해도 된다.
- ``dependencies()``: 이 initializer에 의존하는 하위 ``Initializer<T>`` 클래스의 리스트를 반환한다. 여기에서 초기화 순서를 지정할 수 있다.
예를 들어 Android ``WorkManager``를 초기화하는 코드는 다음과 같다.
class WorkManagerInitializer : Initializer<WorkManager> {
override fun create(context: Context): WorkManager {
val configuration = Configuration.Builder().build()
WorkManager.initialize(context, configuration)
return WorkManager.getInstance(context)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
// 부모 initializer 없음
return emptyList()
}
}
이제 ``WorkManagerInitializer``에 의존하는 ``ExampleLoggerInitializer``를 선언해 보자.
class ExampleLoggerInitializer : Initializer<ExampleLogger> {
override fun create(context: Context): ExampleLogger {
// WorkManager는 이미 초기화되어 있음
return ExampleLogger(WorkManager.getInstance(context))
}
override fun dependencies(): List<Class<out Initializer<*>>> {
// 부모 initializer
return listOf(WorkManagerInitializer::class.java)
}
}
``dependencies()`` 함수에서 ``WorkManagerInitializer``를 부모 initializer로 지정했다. 따라서 ``ExampleLogger``는 ``WorkManager``가 초기화된 후에 초기화된다.
Manifest 정의
Android가 initializer를 인식하고 실행하게 하려면, 구현한 initializer를 manifest에 등록해야 한다. Startup 라이브러리에는 ``InitializationProvider``라는 특별한 content provider가 있다. 이 안에 모든 initializer를 ````로 선언하면 된다.
위에서 구현한 두 initializer를 등록하는 구문은 다음과 같다.
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.WorkManagerInitializer"
android:value="androidx.startup" />
<meta-data android:name="com.example.ExampleLoggerInitializer"
android:value="androidx.startup" />
</provider>
Startup 라이브러리는 ``<meta-data>``로 선언된 initializer를 먼저 인식하고, 인식된 initializer의 ``dependencies()``에 선언된 initializer 역시 인식한다. 따라서 위의 경우에는 ``ExampleLoggerInitializer``만 ``<meta-data>``로 등록하면 ``WorkManagerInitializer``까지 인식된다.
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<!-- 이거만 적어도 된다. -->
<meta-data android:name="com.example.ExampleLoggerInitializer"
android:value="androidx.startup" />
</provider>
이제 ``InitializationProvider``에서 ``AppInitializer`` 클래스를 사용하여 initializer를 인식하고 실행할 수 있다.
수동으로 초기화하고 싶다면?
앱이 시작될 때가 아닌, 클래스가 처음으로 사용될 때 초기화하고 싶을 수도 있다. 이처럼 ``AppInitializer``에 직접 접근하여 원하는 시점에 클래스를 초기화할 수 있다.
자동 초기화 비활성화
우선 자동 초기화를 비활성화하기 위해 manifest에서 initializer를 제거하자. 이렇게 하면 제거된 initializer의 부모까지 모두 제거된다.
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.ExampleLoggerInitializer"
tools:node="remove" />
</provider>
모든 자동 초기화 클래스 비활성화
모든 initializer를 한 번에 비활성화하고 싶다면, ``InitializationProvider`` 자체를 제거하면 된다.
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
수동 초기화
이제 원하는 시점에 ``AppInitializer``를 사용하여 클래스를 초기화할 수 있다.
AppInitializer.getInstance(context)
.initializeComponent(ExampleLoggerInitializer::class.java)
이 코드는 ``WorkManagerInitializer``를 먼저 실행한 후 ``ExampleLoggerInitializer``를 실행한다.