티스토리 뷰
4년만에 다시 공부 기록을 시작하려 한다.
오랜만에 들어온 블로그 글의 조회수가 아직도 올라가는걸 보니 신기하고,
글로 써놓은 내용은 확실히 더 잘 기억하고 있는 나를 보며 기록의 소중함을 느낀다.
이번엔 Jetpack에서 흥미로운 라이브러리들을 공부해보려 한다.
android 공식문서에서 jetpack 라이브러리를 잘 활용한 앱 예시를 보던 중, TicTok의 사례에서 아래 내용을 보게 되었다.
TikTok의 시작 시간을 줄이기 위해 팀은 Android Jetpack의 앱 시작 라이브러리를 기반으로 시작 프레임워크를 리팩터링했습니다.
어떤 내용인가 살펴보니, 앱과 라이브러리 모두에서 초기화 로직을 간소화 하고 초기화 실행 순서 자체도 제어할 수 있는
사용법은 매우 간단하지만 활용도는 아주 높은 라이브러리였다.
(얼마전 회사에서 프로젝트를 진행하던 중, 다수의 모듈의 초기화 순서를 제어해야하는 상황을 겪었기에 앱 시작 라이브러리의 필요성이 더 와닿았다.)
앱에서는 Application class를 가질 수 있기 때문에 해당 클래스에서 초기화 로직을 실행할 수 있지만,
라이브러리는 Application 클래스를 가질 수 없어 ContentProvider의 onCreate에서 초기화 로직을 실행하도록 개발하곤 했다.
App Startup 라이브러리에서 제공하는 InitializerProvider 클래스도 코드를 보면 ContentProvider를 상속받고 있기 때문에, ContentProvider를 초기화에 사용하는 이유부터 짚고 넘어가겠다.
ContentProvider
- 한 프로세스의 데이터를 다른 프로세스에서 실행 중인 코드와 연결하는 표준 인터페이스
- 앱 프로세스가 시작할 때 가장 먼저 생성되는 컴포넌트로 ContentProvider.onCreate() 메소드에서 초기화 로직 실행 가능
- AndroidManifest.xml에 <provider>와 <meta-data> 태그로 클래스 등록하면, gradle 빌드 시 앱의 manifest에 자동 merge됨
- manifest 등록 시 고유한 Authority를 가져야함
- android:exported="false" 속성으로 앱 내부에서 사용하도록 설정해 초기화 로직만 수행하는 목적으로 사용 가능
초기화에 사용하는 관점에서 정리해본 ContentProvider의 특징은 위와 같다.
App Startup은 Content Provider를 상속받은 InitilizerProvider를 통해 개발자가 등록해놓은 초기화 로직을 실행한다.
In InitializationProvider API Reference:
The ContentProvider which discovers Initializers in an application and initializes them before onCreate.
InitializationProvider
- ContentProvider를 상속받는 App Startup의 interface
- 빌드 후 merge된 manifest에 등록되는 유일한 ContentProvider
- 앱 실행 시, AppInitializer.discoverAndInitialize() 메소드에서 meta-data에 등록된 Initializer를 찾아 순차 실행
결국 App Startup을 사용하더라도 ContentProvider의 특징을 활용해 manifest에 등록된 내용으로 초기화 로직을 실행한다는 점은 같다.
차이점은 앱과 모듈에서 각각 ContentProvider를 가지면 최종적으로 merge된 manifest에는 여러개의 provider가 등록되게 된다.
이 때 provider생성은 Android 시스템 내부 최적화 로직에 따라 이루어지기 때문에 개발자가 순서를 제어할 수 없는 문제가 있다.
만약, module1의 초기화 로직이 module2의 초기화 로직 결과에 영향을 받는다면 ContentProvider로는 성공을 보장할 수 없다.
(ex. module2가 network 요청 관련 초기화를 수행하고 module1은 api call을 통해 초기화를 해야한다면, module1의 초기화는 반드시 module2 생성 이후에 이루어져야만 성공한다.)
App Startup에서는 단일 ContentProvider를 통해 여러개의 초기화 로직을 찾아 개발자가 정의한 순서대로 실행하기 때문에, 위와 같은 상황에 적합하게 사용될 수 있다.
실제 사용하기 위한 코드 작업은 아래와 같다.
나는 아래와 같은 구조로 테스트를 했다. (공식문서에 나와있는 예제와 동일하다)
📦 AppStartUp_test/
├── app/
│
├── module1/ <-- 앱이 직접 의존하는 모듈
│ ├── LoggerInitializer.kt <-- App Startup용 Initializer
│ └── AndroidManifest.xml
│
├── module2/ <-- module1이 의존하는 하위 모듈
│ ├── WorkManagerInitializer.kt <-- 가장 먼저 초기화 되어야하는 Initializer
│ └── AndroidManifest.xml
1. Dependency 추가 (Kotlin DSL)
dependencies {
implementation("androidx.startup:startup-runtime:1.2.0")
}
2. Initializer 정의
Initilizer<T>를 상속받는 구현체 클래스를 정의한다.
이렇게 구현한 클래스가 기존 ContentProvider를 대체하게 된다.
앞서 설명한 InitializationProvider에서는 여기서 생성한 Initializer 클래스를 찾아 생성시킨다.
// module1 - LoggerInitializer.kt
class LoggerInitializer : Initializer<Logger> {
override fun create(context: Context): Logger {
// WorkManager가 생성된 후 실행되어야함
return Logger(WorkManager.getInstance(context))
}
override fun dependencies(): List<Class<out Initializer<*>>> {
// module2의 Initializer에 의존함
return listOf(WorkManagerInitializer::class.java)
}
}
// module2 - WorkManagerInitializer.kt
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<*>>> {
return emptyList() // 다른 모듈에 의존성 없음
}
}
각 Intializer의 onCreate 에서는 T 타입 객체를 생성해서 return 한다.
return 값이 필요 없는 경우에는 Unit으로 선언하여 생략이 가능하다.
이렇게 생성한 객체는 AppInitializer 내부에 등록되고, 개발자는 아래 코드로 꺼내서 사용할 수 있다.
val logger = AppInitializer.getInstance(context)
.initializeComponent(LoggerInitializer::class.java)
AppInitializer 내부 코드
private val initialized: MutableMap<Class<out Initializer<*>>, Any> = mutableMapOf()
fun <T> initializeComponent(component: Class<out Initializer<T>>): T {
// 이미 초기화되어 있으면 캐시에서 꺼내서 리턴
initialized[component]?.let { return it as T }
// 없으면 새로 생성하고 등록
val instance = component.newInstance()
val result = instance.create(appContext)
initialized[component] = result as Any
return result
}
3. Manifest 설정
마지막으로, InitializationProvider에서 각 Initializer를 찾아 실행할 수 있도록, manifest에 해당 클래스들을 선언해야한다.
3-1. 앱에서 startup 의존
만약 앱에서 startup-runtime 의존성을 가지고 있다면, 빌드 시 manifest에 InitializationProvider를 자동으로 등록한다.
그러면 앱 포함 하위 모든 모듈에서 <application> 태그 내에 <meta-data>만 선언해주면 된다.
// app/build.gradle
implementation("androidx.startup:startup-runtime:1.2.0")
// module1 - AndroidManifest.xml
<meta-data
android:name="com.example.module1.LoggerInitializer"
android:value="androidx.startup" />
// module2 - AndroidManifest.xml
<meta-data
android:name="com.example.module2.WorkManagerInitializer"
android:value="androidx.startup" />
3-2. 앱에서 startup 의존 여부 파악 불가
라이브러리를 개발하는 상황이라면, 앱에서 startup 의존성을 가지는지 알 수 없으므로 manifest에 <provider> 태그도 함께 등록해줘야한다.
만약 앱에서 startup 의존성을 가지고 있다고 해도, manifest가 병합되는 과정에서 하나의 InitializationProvider 만 남게 된다.
tools:node="merge" 를 작성해주면, Gradle 빌드 시점에 해당 요소가 지워지거나 대체되지 않도록 명시할 수 있다.
// module1 - AndroidManifest.xml
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.example.module1.LoggerInitializer"
android:value="androidx.startup" />
</provider>
참고 자료
ContentProvider 공식 문서
콘텐츠 제공자 | App data and files | Android Developers
콘텐츠 제공자는 구조화된 데이터 세트의 액세스를 관리합니다. 데이터를 캡슐화하고, 데이터 보안을 정의하는 데 필요한 메커니즘을 제공합니다. 콘텐츠 제공자는 한 프로세스의 데이터를 다
developer.android.com
App Startup 공식 문서
앱 시작 | App architecture | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 앱 시작 라이브러리는 간단하고 효율적으로 초기화하
developer.android.com
Tictok 프로젝트 개선 사례
Android 도구로 사용자 환경을 최적화한 TikTok | Developer stories | Android Developers
전 세계 10억 명이 넘는 사람들이 커뮤니티 중심의 엔터테인먼트 공간인 TikTok을 통해 좋아하는 콘텐츠를 발견하고 창작하며 공유합니다.
developer.android.com
'Android' 카테고리의 다른 글
| [Android] MasterKey와 EncryptedSharedPreferences (0) | 2025.10.15 |
|---|---|
| [Android & Kotlin] Intent로 Data Class 타입 객체 넘기기 (1) | 2020.08.21 |
| [Android] 안드로이드 스레드 (Android Thread) (0) | 2020.06.24 |
| [Android] 안드로이드 Activity와 Fragment의 생명주기 (0) | 2020.06.23 |
| [Android] 어플리케이션 기본 항목 - 4대 컴포넌트, App Manifest (0) | 2020.06.23 |