사전 콘텐츠 프로바이더
용어함정

콘텐츠 프로바이더

gabury1고친 사람 github-actions[bot]

콘텐츠 프로바이더는 안드로이드 앱이 자기 데이터를 다른 앱에 내주는 부품입니다. 연락처 앱이 가진 이름과 전화번호를 메신저 앱이 읽어 가는 길이 이것입니다. 다른 앱은 데이터가 담긴 파일을 직접 열지 못하고 이 부품에 물어서 받습니다. 통신 업계에서는 같은 이름이 인터넷으로 콘텐츠를 내놓는 사업자를 가리킵니다.

쉽고 빠른 이해

무슨 일을 하는 물건인가 — 한 앱의 데이터를 다른 앱이 읽고 고칠 수 있게 열어 주는 부품입니다. 사진 편집 앱이 갤러리의 사진 목록을 받아 오는 것도 이 부품을 거칩니다.

왜 이렇게 하나 — 안드로이드에서는 앱마다 담이 쳐져 있습니다. 한 앱이 남의 앱 파일을 마음대로 열 수 없습니다. 그래도 데이터를 나눠야 할 때가 있습니다. 그때를 위해 담에 문을 하나 냅니다. 콘텐츠 프로바이더가 그 문입니다.

어떻게 도나

  1. 데이터를 내주는 앱이 콘텐츠 프로바이더를 만들고 이름을 붙여 등록합니다
  2. 데이터가 필요한 앱은 그 이름이 든 주소로 「이 데이터를 달라」고 요청합니다
  3. 운영체제가 요청을 그 콘텐츠 프로바이더에 전해 줍니다. 콘텐츠 프로바이더는 데이터를 표 모양으로 돌려줍니다

대가 — 문을 내는 순간 다른 앱이 들어올 길이 생깁니다. 누구에게 무엇을 보여 줄지 권한을 잘못 걸면 남의 앱이 데이터를 통째로 읽어 갑니다. 데이터를 자기 앱 안에서만 쓴다면 문을 낼 까닭이 없습니다.

상세

이 절은 「콘텐츠 프로바이더」의 두 뜻을 표로 가른 뒤 안드로이드의 뜻을 차례로 펼칩니다. 마지막 두 소절에서 통신 업계의 뜻을 짧게 보고 두 뜻을 가르는 단서를 모읍니다.

콘텐츠 프로바이더가 가리키는 물건들

같은 이름이 두 세계에서 쓰입니다. 하나는 앱 안의 부품입니다. 다른 하나는 회사입니다. 둘은 이름만 같고 서로 이어지지 않습니다.

맥락 콘텐츠 프로바이더가 가리키는 것 예
안드로이드 앱 앱의 데이터를 다른 앱에 내주는 부품 연락처 목록을 내주는 부품
통신 · 인터넷 산업 인터넷으로 콘텐츠를 내놓는 사업자 넷플릭스 같은 동영상 스트리밍 회사 · 네이버 같은 포털

이 문서는 주로 첫째 줄을 다룹니다. 안드로이드 개발 문서와 대화에서 이 이름이 나오면 대개 첫째 뜻입니다.

앱 샌드박스와 데이터 공유

안드로이드는 스마트폰에서 도는 운영체제입니다. 속은 리눅스 커널 위에서 돕니다. 안드로이드는 앱을 하나 깔 때마다 그 앱에 리눅스 사용자 번호를 따로 내줍니다. 이 번호를 UID(User ID, 사용자 ID)라고 부릅니다.

앱이 만든 파일과 데이터베이스는 그 UID 만 읽을 수 있습니다. 다른 앱은 UID 가 달라서 운영체제가 막습니다. 이렇게 앱마다 쳐 둔 담을 앱 샌드박스라고 부릅니다.

담은 앱끼리 데이터를 훔쳐 가지 못하게 막습니다. 그런데 데이터를 나눠야 할 때도 있습니다. 메신저 앱은 연락처 앱이 가진 전화번호가 필요합니다. 사진 편집 앱은 갤러리의 사진 목록이 필요합니다.

콘텐츠 프로바이더는 이때 담에 내는 문입니다. 데이터를 가진 앱이 문을 만들어 둡니다. 다른 앱은 파일을 직접 열지 않고 그 문에 요청을 보냅니다. 문을 만든 앱이 무엇을 내줄지 정합니다.

네 가지 앱 컴포넌트 중 하나

안드로이드 앱은 운영체제가 알아보고 직접 띄우는 부품 네 가지로 이루어집니다. 이 부품들이 앱 컴포넌트입니다. 콘텐츠 프로바이더는 그 넷 가운데 하나입니다.

앱 컴포넌트 맡는 일
액티비티 화면 한 장을 맡고 입력을 받는다
서비스 화면 없이 뒤에서 작업을 이어 간다
브로드캐스트 리시버 충전 시작처럼 시스템이 알리는 일에 반응한다
콘텐츠 프로바이더 앱의 데이터를 다른 앱에 내준다

나머지 셋은 「이 일을 해 달라」는 요청을 받습니다. 그 요청은 인텐트라는 객체에 담겨 옵니다. 콘텐츠 프로바이더만 인텐트로 부르지 않습니다. 일을 시키는 대상이 아니라 데이터를 묻는 대상이기 때문입니다.

콘텐츠 URI

다른 앱이 데이터를 달라고 할 때는 주소로 가리킵니다. 이 주소가 콘텐츠 URI(Uniform Resource Identifier, 자원 식별자)입니다. 웹 주소처럼 생겼고 content:// 로 시작합니다.

content://com.example.notes/notes/7

이 주소는 네 조각으로 나뉩니다. 조각마다 가리키는 대상이 한 단계씩 좁아집니다.

조각 값 가리키는 것
스킴 content:// 콘텐츠 프로바이더로 가는 주소라는 표시
어소리티 com.example.notes 어느 콘텐츠 프로바이더인가
경로 notes 그 안의 어느 데이터 묶음인가
ID 7 그 묶음의 몇 번째 행인가

어소리티(authority)는 콘텐츠 프로바이더의 이름입니다. 기기 안에서 하나뿐이어야 해서 대개 앱의 패키지 이름을 붙여 짓습니다. 패키지 이름은 앱마다 하나씩 붙는 식별 이름입니다. com.example.notes 가 그런 꼴입니다. 운영체제는 이 이름을 보고 요청을 어느 앱으로 보낼지 정합니다.

ID 를 빼면 묶음 전체를 가리킵니다. content://com.example.notes/notes 는 메모 전부입니다. 끝에 /7 을 붙이면 7번 메모 하나입니다.

안드로이드가 원래 가진 데이터도 이 주소로 엽니다. 연락처는 content://com.android.contacts/contacts 입니다. 기기에 저장된 사진은 content://media/external/images/media 입니다.

콘텐츠 리졸버로 데이터를 묻기

데이터가 필요한 앱은 콘텐츠 프로바이더 객체를 직접 잡지 않습니다. 그 객체는 다른 앱의 프로세스에 살기 때문입니다. 프로세스는 운영체제가 실행 중인 프로그램 하나에 내주는 공간입니다. 앱마다 따로 돕니다.

요청은 대신 콘텐츠 리졸버에 건넵니다. 콘텐츠 리졸버는 콘텐츠 URI 를 받아 맞는 콘텐츠 프로바이더에 요청을 이어 주는 객체입니다. 앱은 주소만 알면 됩니다. 상대 앱의 클래스 이름은 몰라도 됩니다.

아래는 코틀린으로 메모 앱의 메모 제목을 읽는 코드입니다. query 에 주소와 가져올 열 이름을 넘깁니다.

Kotlin
val uri = Uri.parse(
    "content://com.example.notes/notes")
val cursor = contentResolver.query(
    uri, arrayOf("title"), null, null, null)
cursor?.use {
    it.moveToFirst()
    it.getString(0) // "장보기"
}

query 의 뒤쪽 세 null 은 거르는 조건, 조건에 넣을 값, 정렬 순서입니다. 여기서는 셋 다 비워서 모든 메모를 순서 없이 받습니다.

돌아온 값은 커서입니다. 커서는 표 모양의 결과를 한 행씩 가리키며 읽게 해 주는 객체입니다. moveToFirst 로 첫 행에 가서 getString(0) 으로 첫 열을 읽으면 첫 메모의 제목이 나옵니다.

이 요청이 상대 앱까지 가는 길은 이렇습니다.

sequenceDiagram
    participant 요청앱 as 요청 앱 · 콘텐츠 리졸버
    participant OS as 운영체제
    participant 제공앱 as 콘텐츠 프로바이더
    participant DB as 데이터베이스
    요청앱->>OS: query · content://com.example.notes/notes
    Note over OS: 어소리티로 앱을 찾고 권한을 확인한다
    Note over OS: 그 앱 프로세스가 없으면 띄운다
    OS->>제공앱: query 를 넘긴다
    제공앱->>DB: 메모를 읽는다
    DB-->>제공앱: 행들
    제공앱-->>요청앱: 커서

운영체제는 먼저 어소리티로 어느 앱의 콘텐츠 프로바이더인지 찾습니다. 요청한 앱에 권한이 있는지도 확인합니다. 그 앱의 프로세스가 떠 있지 않으면 운영체제가 먼저 띄웁니다. 그다음에 요청을 넘깁니다.

요청 앱과 콘텐츠 프로바이더는 서로 다른 프로세스에 있습니다. 프로세스끼리 데이터를 주고받는 일이 프로세스 간 통신입니다. 프로세스는 서로의 메모리를 볼 수 없어서 운영체제가 사이에서 데이터를 날라 줘야 합니다.

안드로이드에서는 바인더(Binder)라는 운영체제의 통신 장치가 이 일을 맡습니다. 앱 개발자는 바인더를 직접 다루지 않습니다. 콘텐츠 리졸버가 그 밑에서 대신 씁니다.

콘텐츠 프로바이더를 짜는 쪽

데이터를 내주는 앱은 ContentProvider 클래스를 물려받아 콘텐츠 프로바이더를 만듭니다. 그리고 정해진 메서드 여섯 개를 채웁니다.

그중 넷은 데이터를 읽고 쓰는 메서드입니다. 이 넷은 SQL(Structured Query Language) 문장 넷과 짝을 이룹니다. SQL 은 관계형 데이터베이스에 데이터를 묻는 언어입니다. 아래 표의 오른쪽 칸이 그 짝입니다.

메서드 하는 일 SQL 로 치면
onCreate 처음 만들어질 때 한 번 불린다. 데이터베이스를 열 준비를 한다 없음
query 조건에 맞는 행을 커서로 돌려준다 SELECT
insert 행을 하나 넣고 그 행의 주소를 돌려준다 INSERT
update 조건에 맞는 행을 고치고 고친 수를 돌려준다 UPDATE
delete 조건에 맞는 행을 지우고 지운 수를 돌려준다 DELETE
getType 주소가 가리키는 데이터의 종류를 알려 준다 없음

짝이 이렇게 맞아서 콘텐츠 프로바이더 뒤에는 흔히 SQLite가 붙습니다. SQLite 는 기기 안에 파일 하나로 두는 작은 데이터베이스입니다.

뒤에 무엇을 두든 상관은 없습니다. 파일을 읽어서 돌려줘도 됩니다. 메모리에 든 목록을 돌려줘도 됩니다. 요청하는 앱은 커서만 받으므로 저장 방식을 모릅니다.

getType 은 MIME 타입(Multipurpose Internet Mail Extensions type)을 문자열로 돌려줍니다. MIME 타입은 데이터의 종류를 적는 이름표입니다. 콘텐츠 프로바이더에서는 여러 행이면 vnd.android.cursor.dir/ 로, 한 행이면 vnd.android.cursor.item/ 으로 시작하게 짓습니다.

주소 하나하나에 맞는 처리를 고르려고 UriMatcher 라는 도우미 클래스를 씁니다. 주소 꼴을 미리 등록해 두면 들어온 주소가 어느 꼴인지 번호로 알려 줍니다. 「묶음 전체」인지 「한 행」인지를 이것으로 가릅니다.

매니페스트에 등록하기

운영체제가 콘텐츠 프로바이더를 찾으려면 어느 앱에 어떤 어소리티가 있는지 먼저 알아야 합니다. 그래서 앱은 매니페스트에 콘텐츠 프로바이더를 적어 둡니다. 매니페스트는 앱에 함께 들어가는 설정 파일입니다. 앱의 부품 목록과 필요한 권한을 담습니다.

XML
<provider
    android:name=".NoteProvider"
    android:authorities="com.example.notes"
    android:exported="true"
    android:readPermission="com.example.notes.READ" />

android:name 은 콘텐츠 프로바이더를 짠 클래스입니다. android:authorities 는 앞에서 본 어소리티입니다. android:exported 를 true 로 두어야 다른 앱이 이 콘텐츠 프로바이더에 닿습니다. false 면 같은 앱 안에서만 씁니다.

권한과 잠깐 빌려주기

콘텐츠 프로바이더를 연다고 모든 앱이 읽게 두지는 않습니다. android:readPermission 과 android:writePermission 에 권한 이름을 걸면 그 권한을 받은 앱만 읽고 씁니다. 연락처가 그렇습니다. 연락처를 읽으려는 앱은 연락처 읽기 권한을 사용자에게 먼저 받아야 합니다.

권한을 통째로 주기엔 넓을 때가 있습니다. 메일 앱이 사진 한 장만 다른 앱에 보여 주고 싶은 경우입니다. 이때는 주소 하나에 대한 읽기 권한만 잠깐 빌려줍니다. 이것을 URI 권한 부여라고 부릅니다.

빌려주는 앱은 인텐트에 그 사진의 콘텐츠 URI 를 담습니다. 그리고 FLAG_GRANT_READ_URI_PERMISSION 이라는 표시를 붙여 보냅니다. 받은 앱은 권한을 따로 받지 않고도 그 주소 하나를 읽습니다. 빌려준 권한은 오래 남지 않습니다. 받은 앱이 그 일을 마치고 화면을 닫으면 풀립니다.

파일 하나를 다른 앱에 넘길 때도 이 방법을 씁니다. file:// 로 시작하는 파일 경로를 그대로 넘기면 받은 앱은 그 파일을 열지 못합니다. 받은 앱은 파일 주인과 UID 가 달라서 운영체제가 막기 때문입니다.

이럴 때는 경로 대신 콘텐츠 URI 를 넘기고 그 주소의 읽기 권한을 빌려줍니다. 이 일에 쓰라고 미리 만들어 둔 콘텐츠 프로바이더가 FileProvider입니다. 앱 안의 파일 경로를 콘텐츠 URI 로 바꿔 줍니다.

바뀐 것을 알리기

다른 앱이 데이터를 읽어 화면에 띄운 뒤에 원본이 바뀔 수 있습니다. 그때마다 다시 물어보게 하면 쓸데없는 요청이 늘어납니다. 그래서 콘텐츠 프로바이더는 바뀐 순간에 알려 주는 길을 둡니다.

알림은 세 단계로 갑니다. 단계마다 부르는 쪽이 다릅니다.

  1. 데이터를 읽어 간 앱이 관심 있는 주소에 ContentObserver 를 등록합니다. 바뀜 소식을 받을 객체입니다
  2. 콘텐츠 프로바이더는 데이터를 고친 뒤 notifyChange 를 부릅니다. 그 주소가 바뀌었다고 알리는 메서드입니다
  3. 등록해 둔 앱마다 ContentObserver 의 onChange 가 불립니다. 앱은 여기서 데이터를 다시 읽습니다

이 구조는 옵저버 패턴입니다. 바뀌는 쪽이 지켜보는 쪽 목록을 들고 있다가 바뀔 때 차례로 알리는 설계입니다.

코드가 도는 때와 스레드

onCreate 는 앱 프로세스가 뜰 때 불립니다. 다른 앱의 요청으로 운영체제가 프로세스를 띄운 때도 같습니다. Application 은 앱 하나에 하나씩 생겨 앱 전체를 대표하는 객체입니다. 콘텐츠 프로바이더의 onCreate 는 이 객체의 onCreate 보다도 먼저 불립니다.

이 메서드는 메인 스레드에서 돕니다. 메인 스레드는 화면을 그리고 터치를 받는 스레드입니다. 여기서 오래 걸리는 일을 하면 앱이 뜨는 동안 화면이 멈춥니다.

그래서 onCreate 에서는 데이터베이스를 열 준비만 합니다. 실제로 여는 일은 첫 요청이 들어올 때로 미룹니다.

query 같은 나머지 메서드는 다른 앱이 부르면 바인더가 관리하는 스레드에서 돕니다. 요청이 여럿이면 여러 스레드에서 동시에 불릴 수 있습니다. 콘텐츠 프로바이더 코드는 여러 스레드가 함께 불러도 데이터가 꼬이지 않게 짜야 합니다. 이렇게 짠 코드는 스레드 안전성을 갖췄다고 합니다.

콘텐츠 프로바이더를 두지 않아도 되는 때

데이터를 자기 앱 안에서만 쓴다면 콘텐츠 프로바이더가 필요 없습니다. 앱은 자기 데이터베이스를 직접 열어 읽으면 됩니다. 콘텐츠 프로바이더를 하나 더 거치면 코드만 늘어납니다.

콘텐츠 프로바이더가 필요한 때는 담 밖으로 데이터를 내줄 때입니다. 다른 앱이 내 데이터를 읽거나 고쳐야 할 때, 파일 하나를 다른 앱에 잠깐 보여 줄 때가 그렇습니다. 안드로이드의 연락처 · 캘린더 · 사진처럼 운영체제가 여러 앱에 내주는 데이터도 이 방식으로 열려 있습니다.

REST API 와 닮은 점

백엔드 개발자에게 콘텐츠 프로바이더는 낯익은 꼴입니다. REST(Representational State Transfer) 방식의 API(Application Programming Interface, 프로그램끼리 부르는 약속) 서버와 뼈대가 같습니다. 둘 다 주소로 데이터를 가리킵니다. 그리고 정해진 동작 몇 개로 읽고 씁니다.

콘텐츠 프로바이더 REST API 서버
콘텐츠 URI URL(Uniform Resource Locator, 웹 주소)
query · insert · update · delete GET · POST · PUT · DELETE
getType 이 돌려주는 MIME 타입 Content-Type 헤더
커서 응답 본문
매니페스트의 읽기 · 쓰기 권한 인증과 인가

오가는 길은 다릅니다. REST API 요청은 네트워크를 타고 다른 기계로 갑니다. 콘텐츠 프로바이더 요청은 한 기기 안에서 프로세스 사이를 건넙니다.

통신 업계의 콘텐츠 프로바이더

이 소절부터 뜻이 바뀝니다. 앱 부품이 아니라 회사 이야기입니다.

통신 업계에서 콘텐츠 프로바이더는 인터넷으로 콘텐츠나 서비스를 내놓는 사업자입니다. 줄여서 CP(Content Provider)라고 부릅니다. 넷플릭스 · 유튜브 같은 동영상 스트리밍 회사, 네이버 같은 포털, 게임 회사가 여기에 듭니다. 포털에 기사를 대는 언론사를 CP 라고 부르기도 합니다.

CP 와 맞서는 쪽은 ISP(Internet Service Provider, 인터넷 서비스 제공자)입니다. ISP 는 가정과 회사에 인터넷 회선을 대주는 통신사입니다. 사용자가 동영상을 보면 그 데이터는 CP 의 서버에서 나와 ISP 의 회선을 타고 사용자에게 닿습니다.

이 둘 사이에서 자주 부딪히는 문제가 망 사용료입니다. 망 사용료는 ISP 가 회선을 쓰는 대가로 CP 에게 받으려는 돈입니다.

트래픽은 회선을 타고 오가는 데이터의 양입니다. CP 가 일으키는 트래픽이 커지면 ISP 는 회선을 늘려야 합니다. 그 비용을 CP 도 나눠 내야 하는지를 두고 다툼이 이어집니다.

망 중립성은 ISP 가 데이터를 내용이나 출처에 따라 차별하지 말라는 원칙입니다. 망 사용료 다툼은 이 원칙과 함께 이야기됩니다.

CP 는 데이터를 사용자 가까이 두려고 CDN(Content Delivery Network, 콘텐츠 전송 네트워크)을 씁니다. CDN 은 같은 콘텐츠를 여러 지역의 서버에 복사해 두고 가까운 서버에서 내주는 망입니다. 백엔드 개발자가 이 뜻의 CP 를 만나는 때는 대개 이 CDN 과 트래픽 비용 이야기에서입니다.

어느 뜻인지 가르는 단서

대화나 문서에서 「콘텐츠 프로바이더」가 나오면 함께 붙은 낱말을 봅니다. 대개 그것만으로 어느 뜻인지 갈립니다.

함께 나오는 말 뜻
content:// · 콘텐츠 리졸버 · 커서 · 매니페스트 · 어소리티 안드로이드
ISP · 망 사용료 · 망 중립성 · 트래픽 · 회선 · CP 통신 업계

줄임말 CP 가 보이면 거의 통신 업계의 뜻입니다. 안드로이드 개발자는 이 부품을 CP 같은 머리글자로 줄이지 않습니다. 대개 「프로바이더」라고 부릅니다.

관련 항목

콘텐츠 프로바이더와 함께 안드로이드 앱을 이루는 컴포넌트

Android · 앱 컴포넌트 · 액티비티 · 서비스 (Android) · 브로드캐스트 리시버 · 인텐트 · 프래그먼트

콘텐츠 프로바이더에 데이터를 묻고 받는 장치

콘텐츠 리졸버 · 콘텐츠 URI · URI · 커서 · MIME 타입 · UriMatcher · ContentObserver · 옵저버 패턴

콘텐츠 프로바이더가 넘는 앱 사이의 벽

앱 샌드박스 · UID · 프로세스 · 프로세스 간 통신 · Binder · 샌드박스

콘텐츠 프로바이더를 등록하고 문을 지키는 장치

매니페스트 · 권한 · 런타임 권한 · URI 권한 부여 · FileProvider · 인텐트 플래그

콘텐츠 프로바이더 뒤에 두는 저장소

SQLite · 데이터베이스 · Room · SQL · 파일 시스템

콘텐츠 프로바이더를 짤 때 챙기는 실행 환경

메인 스레드 · 스레드 안전성 · 생명주기 · Application (Android) · ANR

안드로이드 콘텐츠 프로바이더와 같은 꼴의 서버 쪽 구조

REST · API · URL · Content-Type · CRUD

통신 업계의 콘텐츠 프로바이더를 둘러싼 이름

ISP · 망 사용료 · 망 중립성 · CDN · 트래픽 · 피어링 · 포털

다른 이름: ContentProvider · content provider · 안드로이드 콘텐츠 프로바이더 · 콘텐츠 제공자 · CP