파이어베이스 라이브러리는 어떻게 초기화 될까?

이 글은 How does Firebase initialize on Android?의 번역입니다.
안드로이드에서 파이어베이스를 사용해 본 적이 있다면 대개의 경우 개발자가 직접 파이어베이스 초기화 코드를 작성할 필요가 없다는 것을 알 것입니다. 관련 기능을 사용하기 위해서는 단순히 싱글톤 객체를 호출하는 것만으로도 충분합니다. 심지어 파이어베이스의 크래시 리포팅은 일체의 추가적인 코드가 필요하지 않습니다. 2016년 구글 I/O에서 이와 관련된 얘기를 한 적이 있는데요. 이 글에서 파이어베이스 초기화와 관련된 좀 더 자세한 이야기를 해볼까 합니다.
문제
많은 SDK들이 초기화를 위해 안드로이드 컨텍스트를 필요로 합니다. 이 컨텍스트는 런타임에 생성이 되며 SDK에서 앱 리소스 및 애셋에 접근하거나 시스템 서비스 사용, 브로드캐스트 리시버를 등록하는 작업에 사용됩니다. 대다수의 경우 컨텍스트는 정적 초기화 메서드를 통해 전달되기 때문에 앱 프로세스가 살아있는 한 SDK에서는 해당 참조를 보유하고 사용할 수 있습니다. 보통 SDK의 문서들은 이 컨텍스트를 얻기 위해 Application의 하위 클래스에서 다음과 같은 방식으로 전달하도록 안내하고 있습니다.
Application의 하위 클래스를 매니페스트에 등록하지 않았다면 다음과 같이 application 태그의 android:name 속성에 해당하는 항목을 추가해야 합니다.
위와 같은 방법도 괜찮아 보입니다만 파이어베이스는 개발자에게 SDK를 훨씬 쉽게 사용할 수 있는 방법을 제공합니다.
해결책
파이어베이스 SDK는 앱 프로세스 초기 실행 단계에 훅을 설치하는 트릭을 사용합니다. 이 트릭은 SDK를 초기화하는데 필요한 타이밍과 컨텍스트를 얻기 위해 컨텐트 프로바이더(ContentProvider)를 이용하는 방식입니다. 앱 개발자는 이를 통해 초기화 코드를 작성할 필요가 전혀 없습니다. 다음은 컨텐트 프로바이더를 선택하게 된 두 가지 이유입니다.
- 컨텐트 프로바이더는 앱 프로세스가 시작된 후 액티비티, 서비스, 브로드캐스트 리시버와 같은 다른 컴포넌트들 보다 먼저 (메인 스레드에서) 생성되고 초기화됩니다.
- 안드로이드 라이브러리 프로젝트의 매니페스트에 컨텐트 프로바이더가 선언된 경우 이는 빌드 타임에 앱의 매니페스트로 병합됩니다. 결과적으로 앱의 최종 매니페스트에 자동으로 추가된다는 것을 의미합니다.
위 두 특성에 대해서 좀 더 살펴보겠습니다.
컨텐트 프로바이더의 이른 초기화
안드로이드 앱 프로세스가 시작될 때 다음과 같은 명확한 작업 순서가 있습니다.
- 매니페스트에 선언된 각 컨텐트 프로바이더가 생성됩니다. 이때 각각의 우선순위는 android:initOrder에 의해 결정됩니다.
- 애플리케이션 클래스(또는 하위 클래스)가 생성됩니다.
- 만일 인텐트를 통해 컴포넌트가 호출된 경우 해당 컴포넌트가 생성됩니다.
컨텐트 프로바이더가 생성되면 안드로이드는 해당 컨텐트 프로바이더의 onCreate 함수를 호출합니다. 파이어베이스 SDK는 바로 이곳에서 getContext 함수를 호출해 컨텍스트를 획득합니다.
이곳은 ActivityLifecycleCallbacks (파이어베이스 애널리틱스에서 사용) 또는 UncaughtExceptionHandler(파이어베이스 크래시 리포팅에서 사용)와 같이 앱의 수명주기 동안 활성화되어야 하는 항목을 설정하는 데 사용할 수 있는 장소이기도 합니다. 또한 의존성 주입 프레임 워크를 여기에서 초기화 할 수도 있습니다.
컨텐트 프로바이더의 매니페스트 병합
매니페스트 병합은 안드로이드 빌드 도구에 의해 앱의 최종 매니페스트를 생성하는 빌드 타임 프로세스입니다. APK에는 애플리케이션 구성 요소, 권한, 하드웨어 요구 사항 등이 작성된 앱의 AndroidManifest.xml 파일과 앱이 사용하는 모든 안드로이드 라이브러리 프로젝트의 매니페스트 요소들이 병합된 매니페스트가 내장됩니다.
결과적으로 모든 안드로이드 라이브러리 프로젝트는 자체 매니페스트에서 컨텐트 프로바이더를 선언 할 수 있으며 이렇게 선언된 요소는 앱의 최종 매니페스트에 병합이 됩니다. 이 방법을 통해 파이어베이스 크래시 리포팅은 컨텐트 프로바이더의 onCreate 함수에서 자체적으로 컨텍스트를 획득하고 있습니다. 이로써 개발자는 SDK의 초기화에 필요한 코드를 직접 작성하지 않아도 됩니다.
FirebaseInitProvider의 역할
파이어베이스를 사용하는 모든 애플리케이션은 firebase-common 라이브러리에 의존성을 갖습니다. 이 라이브러리는 FirebaseInitProvider를 제공하는데 이는 프로젝트의 google-services.json 파일의 설정을(구글 서비스 플러그인에 의해 안드로이드 리소스로 주입) 사용해 디폴트 FirebaseApp 인스턴스를 초기화하는 역할을 합니다. 만일 앱에서 여러 개의 파이어베이스 프로젝트를 참조하는 경우, 이곳에서 설명한 것처럼 FirebaseApp 인스턴스를 초기화하는 코드를 직접 작성해야 합니다.
컨텐트 프로바이더를 사용한 초기화 방식의 단점
컨텐트 프로바이더를 사용하여 앱이나 라이브러리를 초기화하는 경우 다음의 두 가지 사항을 고려해야 합니다.
첫째, 안드로이드 기기에서 android:authority를 가진 컨텐트 프로바이더는 하나만 존재 할 수 있습니다. 따라서 한 기기에서 authority를 가진 라이브러리를 하나 이상의 앱에서 사용하는 경우, 서로 다른 authority를 사용해야 합니다. 그렇지 않을 경우, 두 번째 앱은 설치가 거부 됩니다. 보통 이 authority는 안드로이드 매니페스트의 컨텐트 프로바이더에 하드 코딩되지만 안드로이드 빌드 도구를 이용하면 앱 별로 서로 다른 authority를 부여할 수 있습니다.
안드로이드 그래들은 플레이스홀더를 통해 매니페스트에 적절한 문자열을 삽입 할 수 있는 기능을 제공합니다. 다음과 같이 앱의 고유한 applicationId를 android:authorities에 사용할 수 있는데 이를 통해 앱 별로 컨텐트 프로바이더의 authority를 지정할 수 있습니다.
둘째, 컨텐트 프로바이더는 앱의 메인 프로세스에서만 실행됩니다. 대다수 앱의 경우, 단일 프로세스로 실행되기 때문에 문제가 되지는 않습니다. 다만 앱의 컴포넌트 중 하나가 다른 프로세스에서 실행된다면 해당 프로세스는 컨텐트 프로바이더를 만들지 않기 때문에 onCreate 함수가 호출되지 않습니다. 이 경우 초기화가 필요한 어떤한 함수도 호출해서는 안되며 다른 안전한 방법으로 SDK를 초기화 할 수 있는 대안을 제공해야 합니다. 컨텐트 프로바이더가 메인 프로세스에서만 실행되는 속성은 해당 앱의 모든 프로세스에서 호출되는 Application(또는 하위 클래스)의 동작 방식과는 다른 부분입니다.
그렇다면 컨텐트 프로바이더를 잘못 사용하고 있는 것 아닌가요?
그렇습니다. 실제로 아무런 콘텐츠를 제공하지도 않는 컨텐트 프로바이더를 사용하는 것은 조금 이상한 부분이라 할 수 있습니다. 또한 컨텐트 프로바이더의 추상 함수 내부를 모두 null을 반환하도록 구현하는 것도 그렇고요. 하지만 이 방법이 추가 코드 없이 자동으로 SDK를 초기화 할 수 있는 가장 신뢰할 수 있는 방법으로 밝혀졌습니다. 제 생각에는 파이어베이스를 사용하는 개발자의 편의성은 컨텐트 프로바이더의 다소 이상한 사용성을 보완한다고 생각합니다. 코드가 전혀 없는 것보다 쉬운 것은 없습니다. 바로 파이어베이스처럼요.
