이번 이슈는 멀티 모듈에서 일어날 수 있는 Gradle 설정 오류로 생길 수 있는 일이다.
구글링했을 때 마땅한 해결 방법을 찾을 수 없어 당황했던 기억이 있는 이슈라 포스팅하기로 했다.
사실 멀티 모듈이 아니라면 일어날 수 없는 이슈일 것 같다.
에러 로그
domain 모듈과 feature 모듈을 연결하고 난 뒤 발생한 에러였다.
사실 처음에는 이것 때문이라고는 생각하지 못했다. 두 모듈을 연결한 뒤 또 다른 것들을 했기 때문에...
java.lang.RuntimeException: Unable to get provider androidx.startup.InitializationProvider: android.content.res.Resources$NotFoundException: String resource ID #0x7f0f001b
at android.app.ActivityThread.installProvider(ActivityThread.java:8633)
at android.app.ActivityThread.installContentProviders(ActivityThread.java:8126)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7812)
at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2486)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:230)
at android.os.Looper.loop(Looper.java:319)
at android.app.ActivityThread.main(ActivityThread.java:9063)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:588)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)
Caused by: android.content.res.Resources$NotFoundException: String resource ID #0x7f0f001b
at android.content.res.Resources.getText(Resources.java:511)
at android.content.res.Resources.getString(Resources.java:604)
at android.content.Context.getString(Context.java:904)
at androidx.startup.AppInitializer.discoverAndInitialize(AppInitializer.java:216)
at androidx.startup.AppInitializer.discoverAndInitialize(AppInitializer.java:206)
at androidx.startup.InitializationProvider.onCreate(InitializationProvider.java:45)
at android.content.ContentProvider.attachInfo(ContentProvider.java:2700)
at android.content.ContentProvider.attachInfo(ContentProvider.java:2670)
at android.app.ActivityThread.installProvider(ActivityThread.java:8628)
at android.app.ActivityThread.installContentProviders(ActivityThread.java:8126)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7812)
at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2486)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:230)
at android.os.Looper.loop(Looper.java:319)
at android.app.ActivityThread.main(ActivityThread.java:9063)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:588)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)
해결 방법 1
현실 부정하기 방법이 있다.
- Invalidate Caches
- Clean Project → Rebuild Project
- 컴퓨터 재부팅
가끔 Android studio는 알 수 없는 오류가 생길 때가 있는데, 그럼 위의 방법 중 하나를 선택하면 해결되는 경우가 있다.
그래서 나는 이번에도 저 3가지 방법을 모두 사용해봤지만 결과는 똑같았다. (눈물이 나는 순간이었다)
그래서 에러 로그에 있는 AppInitializer.java 라는 링크를 타고 들어갔더니 문제의 String resource ID #0x7f0f001b,
R.string.androidx_startup을 import하지 못하고 있는 것을 발견했다.
해결 방법 2
무작정 구글링 해본다.
(아무리 봐도 모르겠었다)
그나마 힌트를 얻을 수 있었던 Github 이슈 글
마지막 코멘트를 보고 힌트를 얻었다.
혹시나 해서 특정 모듈의 build.gradle 파일을 보니 application 모듈로 설정되어 있었다.
생각치도 못한 곳에서 실수가 있어 더 찾기 어려웠다ㅜ
// As-is
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.google.ksp)
}
// To-do
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.google.ksp)
}
이번 이슈를 해결하면서 2가지 궁금함이 생겼다.
com.android.application와 com.android.library
android.application과 android.library는 각 모듈에 필요한 플러그인이다
- Application 모듈 : 앱 소스 코드, 리소스 파일, 앱 설정을 포함하기 위한 컨테이너 제공
- 라이브러리 모듈 : 다른 앱 모듈에서 dependency로 사용하거나 다른 프로젝트로 가져올 수 있는 재사용 가능한 코드를 포함하기 위한 컨테이너를 제공함
- Android 라이브러리
- Android 네이티브 라이브러리
- Java 또는 Kotlin 라이브러리
왜 하나의 Project에는 하나의 Application 모듈만 존재해야 하는가?
그렇지 않아도 된다. Project : Application 모듈 = 1 : 1이 아니다
여러 개의 Application 모듈을 하나의 project에 생성할 수 있지만 권장하지 않는다고 한다. (상황에 따라 다를 수 있음)
단일 Application을 권장하는 이유는 다음과 같다고 한다.
- Simplicity와 Maintainability
- 프로젝트 구조가 더 간단하고 관리가 쉬워짐
- build config, dependencies, 전체적인 프로젝트의 구조의 복잡도를 낮출 수 있음
- 확실한 Application 경계
- Application 모듈은 앱의 고유하고 배포 가능한 단위를 나타냄
- 따라서 하나만 존재한다면 Application 코드와 리소스에 대한 명확한 경계를 설정할 수 있음
- 즉, 앱의 여러 부분 간의 우발적인 종속성과 conflict를 방지하는데 도움이 됨
- Build의 효율성
- 단일 Application 모듈을 빌드하는 것은 (당연히) Multi Application 모듈을 빌드하는 것보다 효율적임
- 빌드 시스템은 단일 결과에 대해 프로세스를 최적화해서 불필요한 컴파일을 줄일 수 있음
- 배포
- Application 앱 빌드의 결과물은 APK 파일임
- 각 Application 모듈은 자체의 APK 파일을 생성하는데, Application 모듈이 여러개일 경우 배포가 복잡해질 수 있음
- Resource의 충돌 방지
- 여러 Application 모듈에서 중복된 Resource Id 또는 Resource 값 충돌 등이 발생할 수 있음
- 이로 인해 앱에서 예기치 않은 동작이 발생할 수 있음
아마 이번 이슈의 핵심은 이 다섯번째 문단이 아니었을까 싶다.
dependency를 설정함에 있어서 예상치 못한 리소스 충돌이 발생했고, 그로 인해 startup 라이브러리에서 원하는 리소스를 찾지 못해 앱이 죽었던 것으로 추측된다.
androidx.startup
이런 라이브러리가 있는 줄도 몰랐다.
- 앱 시작 시, component들을 간단하고 효율적(performant)인 방법으로 초기화하는 방법을 제공함
- 라이브러리 개발자와 앱 개발자 모두 해당 라이브러리를 사용하여 시작 시퀀스를 간소화하고 초기화 순서를 명시적으로 설정할 수 있음
- 초기화해야하는 각 component의 각 content provider를 정의하는 대신,
하나의 contnet provider를 공유하는 component initializer를 정의할 수 있음 → 앱 startup 시간을 크게 단축할 수 있음 - 각 라이브러리 초기화의 순서를 지정해줄 수 있음
사용해보지 않아서 잘은 모르겠지만 마지막 줄이 핵심이 될 것 같다.
앱을 시작할 때 초기화할 컴포넌트가 많을 때, 해당 라이브러리를 사용해보면 좋을 것 같다.
Reference
- single application module에 대한 답변 : gemini
- startup 라이브러리 예제 참고한 포스팅
- startup 관련 참고한 포스팅
'ANDROID > Android 개발 이슈 & 해결' 카테고리의 다른 글
[Android] Compose에서 Kakao 로그인 구현하기 (1) | 2024.10.04 |
---|---|
[Android] LiveData를 1번만 관찰하는 방법 (2) | 2022.04.06 |
[Android] CalendarView 라이브러리 추천 : kizitonwose/CalendarView (1) | 2022.04.05 |
[Android] Do not concatenate text displayed with 'setText' (0) | 2022.03.27 |
[Android] Room Migration : 데이터베이스 테이블 수정하기 (0) | 2022.03.26 |
[Android] 조회, 수정이 가능한 Room Database 디버깅 라이브러리 (4) | 2022.03.10 |
[Android] LiveData를 유연하게 사용하는 방법 (feat.Transformations) (0) | 2022.03.07 |
[Android] 화면 터치로 키보드 내려가게 하기 (0) | 2022.02.25 |
댓글