안드로이드 개발을 처음 공부하다 보면 "컴포넌트"라는 단어를 정말 자주 만난다. 나도 처음에는 이게 그냥 "클래스"를 어렵게 부르는 말인 줄 알았는데, 공부를 하면서 그게 아니라는 걸 알게 됐다. 이번 글에서는 안드로이드 앱을 이루는 4가지 핵심 구성요소가 무엇인지, 그리고 이 4개가 공통으로 갖는 성격이 무엇인지를 정리한다. 그중 가장 비중이 큰 Activity와 컴포넌트 간 통신 수단인 Intent는 각각 1-2편, 1-3편에서 따로 자세히 다룬다.
컴포넌트(Component)는 말 그대로 "구성요소"라는 뜻이다. 안드로이드 4대 컴포넌트란 안드로이드 앱을 만드는 데 필요한 4개의 핵심 구성요소를 말하며, 다음과 같다.
안드로이드 공식 문서는 이 4가지를 "앱의 필수 구성요소이며, 각각은 시스템이나 앱으로 들어갈 수 있는 진입점(entry point)"이라고 설명한다. 여기서 "진입점"이라는 표현이 핵심이다. 일반적인 프로그램은 main() 함수 하나에서 시작해서 순차적으로 실행되지만, 안드로이드 앱은 그렇지 않다. 시스템이나 사용자, 혹은 다른 앱이 이 4개 컴포넌트 중 하나를 호출하면 그 컴포넌트가 살아나면서 앱의 특정 부분이 실행된다. 즉 안드로이드 앱에는 정해진 "시작 지점" 하나가 없고, 여러 개의 입구가 동시에 존재하는 셈이다.
4개 컴포넌트는 각자 하는 일은 다르지만, 아래 세 가지 특징을 공통으로 가진다.
첫째, 각 컴포넌트는 독립적으로 존재한다. Activity 하나가 죽어도 Service는 계속 돌아갈 수 있고, 반대의 경우도 마찬가지다. 서로 강하게 묶여 있지 않다.
둘째, 각 컴포넌트는 고유의 기능을 수행한다. Activity는 화면을 보여주는 일, Service는 백그라운드 작업, Broadcast Receiver는 이벤트 수신, Content Provider는 데이터 공유라는 역할이 명확히 분리되어 있다. 그래서 "이 작업을 어디에 구현해야 하나"를 고민할 때 이 역할 구분이 기준이 된다.
셋째, 각 컴포넌트는 인텐트(Intent)를 통해 서로 상호작용한다. 컴포넌트끼리 서로를 직접 new로 생성해서 호출하는 게 아니라, Intent라는 메시지 객체를 시스템에 던지면 시스템이 알맞은 컴포넌트를 찾아 실행해주는 방식이다. Intent가 정확히 뭔지는 1-3편에서 자세히 다룬다.
Activity는 비중이 커서 1-2편에서 따로 다루고, 여기서는 나머지 세 컴포넌트를 간단히만 짚고 넘어간다.
서비스(Service)는 사용자와 직접 상호작용하지 않고 백그라운드에서 작업을 처리하는 컴포넌트다. Activity가 화면에서 사라져도 Service는 계속 동작할 수 있다는 점이 특징이다. Service는 크게 포그라운드 서비스와 백그라운드 서비스로 나뉘는데, 포그라운드 서비스는 사용자가 실행 중임을 알 수 있도록 알림(notification) 표시가 필수다. 한 가지 오해하기 쉬운 부분은, Service가 "무조건 별도 스레드에서 돈다"고 생각하는 것이다. Service는 기본적으로 앱의 메인 스레드에서 실행되며 별도 프로세스가 아니다. 오래 걸리는 작업을 Service 안에서 처리하려면 개발자가 직접 스레드를 분리해줘야 한다.
작업 예약과 관련해서는 WorkManager와 AlarmManager를 구분해서 알아둘 필요가 있다. "즉시 실행이면 WorkManager, 지연 실행이면 AlarmManager"라고 알고 있는 경우가 있는데, 공식 가이드 기준으로는 이보다는 다음 기준으로 나누는 게 정확하다. WorkManager는 "정확히 언제"보다는 "반드시 실행되긴 해야 하는" 지연 가능한(deferrable) 작업, 예를 들어 서버로 로그를 올리거나 데이터를 동기화하는 작업에 권장된다. 배터리나 시스템 상황을 고려해서 적절한 시점에 실행해준다. 반면 AlarmManager는 알람 시계나 특정 시각 알림처럼 "정확히 지정한 시각"에 실행되어야 하는 작업에 권장되며, 기기가 절전 모드에 들어가 있어도 깨워서 실행할 수 있다. 즉 "시점이 유연해도 되지만 실행 보장이 중요한가(WorkManager)" 대 "정확한 시각이 중요한가(AlarmManager)"로 나누는 게 더 정확한 기준이다.
방송 수신자(Broadcast Receiver)는 안드로이드 OS나 다른 앱이 보내는 이벤트(부팅 완료, 배터리 부족, 네트워크 끊김 등)를 수신해서 처리하는 컴포넌트다. 거의 UI를 갖지 않는다는 게 특징이다. 다만 한 가지 최근 제약을 알아두면 좋은데, 안드로이드 8.0(Oreo) 이상을 타겟팅하는 앱은 대부분의 암시적 브로드캐스트를 매니페스트에 등록하는 방식으로는 받을 수 없다(백그라운드 실행을 제한하기 위한 정책이다). ACTION_BOOT_COMPLETED(부팅 완료)처럼 일부 예외를 빼면, 앱이 실제로 실행 중일 때만 동작하는 "컨텍스트 등록 리시버" 방식을 써야 한다. "매니페스트에 등록만 하면 어떤 브로드캐스트든 항상 받을 수 있다"고 생각하면 안 되는 이유가 여기에 있다. (이 컴포넌트는 8편에서 훨씬 자세히 다룬다.)
콘텐트 제공자(Content Provider)는 앱이 관리하는 데이터(SQLite DB, 파일, 웹 데이터 등)를 다른 앱과 공유할 수 있게 해주는 컴포넌트다. ContentResolver를 통해 query, insert, update, delete라는 4가지 메서드로 CRUD(생성/조회/수정/삭제)를 수행하는 원칙을 따른다. 다른 앱이 내 데이터에 함부로 접근하지 못하도록 매니페스트에 읽기/쓰기 권한(android:readPermission, android:writePermission)을 선언해서 접근을 제어할 수 있다.
"컴포넌트"와 "클래스"를 같은 것으로 오해하기 쉽다. Activity나 Service는 그냥 상속받아서 쓰는 일반 클래스가 아니라, 안드로이드 OS가 생명주기를 직접 관리해주는 "진입점"이라는 점이 일반 자바/코틀린 클래스와 근본적으로 다르다. 개발자가 직접 MainActivity()처럼 인스턴스를 생성하지 않고, 시스템이 Intent를 받아서 대신 인스턴스를 만들어준다는 점을 기억하면 다른 헷갈림도 자연스럽게 풀린다.
Service는 무조건 별도 스레드에서 돈다는 오해도 실무에서 흔히 하는 착각이다. Service 자체는 기본적으로 메인 스레드에서 실행되므로, 오래 걸리는 작업이라면 개발자가 직접 스레드를 분리해야 한다.
이번 글에서는 안드로이드 앱을 이루는 4대 컴포넌트(Activity, Service, Broadcast Receiver, Content Provider)가 각각 무엇이고, 왜 "진입점"이라고 불리는지 정리했다. 4개 컴포넌트는 독립적으로 존재하면서 각자 고유한 역할을 수행하고, Intent라는 메시지를 통해 서로 연결된다는 공통점이 있었다. Service, Broadcast Receiver, Content Provider의 기본 개념도 함께 훑었다.