Content Provider는 애플리케이션 간 데이터 액세스를 위한 통합 인터페이스를 제공하는 Android 구성 요소입니다. 물리적 저장소(SQLite, 파일, 네트워크 소스)를 추상화하고 ContentResolver를 통해 안전한 정보 교환을 가능하게 합니다. Android Developer Guide, 2026에 따르면, Content Provider는 Activity, Service, BroadcastReceiver와 함께 Android 애플리케이션의 네 가지 주요 구성 요소 중 하나입니다. 그 역할은 읽기 및 쓰기 권한 제어를 통해 다른 애플리케이션이 데이터를 사용할 수 있도록 하는 것입니다.
핵심 사항
Content Provider는 중앙 집중식 데이터 저장소에 대한 액세스를 관리하고 통합 계약 인터페이스를 통해 다른 애플리케이션에 데이터를 제공하는 Android 구성 요소입니다. 저장소 구현 세부 정보를 숨깁니다. 데이터는 SQLite, 파일 시스템, 클라우드에 저장되거나 네트워크 요청의 결과일 수 있습니다.
Android에는 시스템 데이터를 위한 내장 Content Providers가 포함되어 있습니다 — ContactsContract, MediaStore, CalendarContract, CallLog. 타사 애플리케이션도 안전한 데이터 교환을 위해 자체 공급자를 만들 수 있습니다. 각 공급자는 URI의 첫 번째 부분을 구성하는 고유 문자열인 authority와 함께 AndroidManifest.xml에 등록됩니다.
Content Provider는 클라이언트-서버 모델로 작동합니다. 공급자는 query, insert, update, delete, getType, onCreate의 여섯 가지 필수 메서드를 구현하는 서버 역할을 합니다. 클라이언트(다른 애플리케이션)는 ContentResolver를 통해 공급자에 액세스하며, ContentResolver는 Android의 IPC 메커니즘을 통해 호출을 공급자의 해당 메서드로 변환합니다.
각 Content Provider는 content:// 스키마의 URI로 식별됩니다. 예: content://com.example.app.provider/items. 첫 번째 부분인 authority(com.example.app.provider)는 매니페스트에서 공급자 클래스에 바인딩됩니다. 경로 /items는 테이블을, /items/5는 ID=5인 특정 레코드를 가리킵니다.
애플리케이션이 ContentResolver.query(URI)를 호출하면 Android는 호출 패키지의 권한을 확인하고 authority로 공급자를 찾은 다음 아직 실행 중이 아니면 해당 프로세스를 시작합니다. 공급자는 쿼리를 실행하고 결과를 포함하는 Cursor 객체를 반환하여 클라이언트가 레코드를 반복할 수 있게 합니다.
ContentProvider 클래스는 6개의 추상 메서드 구현이 필요합니다. 각 메서드는 URI를 받아들이고 작업 유형에 해당하는 결과를 반환합니다. 시스템은 모든 프로세스에서 이러한 메서드를 호출하므로 스레드 안전해야 하며 장시간 실행을 차단하지 않아야 합니다.
query 메서드는 URI, 열의 projection 배열, 인수가 있는 selection 문자열 및 정렬 순서를 받아들입니다. 데이터가 포함된 Cursor를 반환합니다. 구현에서 UriMatcher를 사용하여 URI를 분석하고 데이터베이스에서 해당 SQL 쿼리를 실행해야 합니다.
이 메서드들은 저장소의 데이터를 수정합니다. insert는 ContentValues(키-값 쌍)를 받아 새 레코드의 URI를 반환합니다. update와 delete는 레코드 필터링을 위한 selection을 받아 영향받은 행 수를 반환합니다. 데이터 변경 후 공급자는 ContentResolver.notifyChange를 통해 이에 대해 알려야 합니다.
| 메서드 | 목적 | 반환 |
|---|---|---|
| query | URI로 데이터 검색 | Cursor 또는 null |
| insert | 새 레코드 추가 | 새 레코드의 URI |
| update | 기존 레코드 업데이트 | int (행 수) |
| delete | 레코드 삭제 | int (행 수) |
| getType | URI의 MIME 타입 | String |
| onCreate | 공급자 초기화 | boolean |
ContentResolver는 시스템의 모든 Content Providers와 작업하기 위한 단일 액세스 포인트입니다. 클라이언트 애플리케이션은 공급자 메서드를 직접 호출하지 않으며 항상 ContentResolver를 통해 호출합니다. ContentResolver의 CRUD 작업은 공급자와 동일한 이름을 가지지만 직접 참조 대신 URI를 받아들입니다.
공급자 내에서 들어오는 URI를 분석하기 위해 UriMatcher가 사용됩니다. URI를 숫자 코드에 매핑할 수 있습니다. 예를 들어 URI content://authority/items는 코드 1, content://authority/items/#는 코드 2를 생성합니다. 이렇게 하면 각 메서드에서 수동 URI 문자열 분석이 필요하지 않습니다.
// ContentResolver를 사용하여 연락처에 액세스하기
val uri = ContactsContract.Contacts.CONTENT_URI
val cursor = contentResolver.query(
uri,
arrayOf(ContactsContract.Contacts.DISPLAY_NAME),
null, null, null
)
cursor?.use {
while (it.moveToNext()) {
val name = it.getString(it.getColumnIndexOrThrow(
ContactsContract.Contacts.DISPLAY_NAME
))
Log.d("연락처", "이름: $name")
}
}
Cursor는 사용 후 항상 닫아야 합니다 — 위 예제에서는 use 함수(Kotlin 확장)가 이를 수행합니다. Cursor를 닫지 않으면 Binder 풀의 데이터에 대한 참조를 보유하므로 메모리 누수가 발생합니다. UI 시나리오의 경우 LiveData/Flow와 함께 CursorLoader 또는 Room을 사용하세요.
자체 Content Provider를 만들려면 ContentProvider 클래스를 확장하는 것부터 시작합니다. 공급자는 SQLiteOpenHelper를 통해 SQLite 데이터베이스와 작동하고 UriMatcher를 사용하여 쿼리 유형을 결정합니다. 메모 목록을 관리하기 위한 최소 구현을 고려해 보세요.
공급자는 AndroidManifest.xml의 application 태그 내에 등록됩니다. authorities 속성은 고유 식별자를 설정하고 exported는 다른 애플리케이션이 공급자에 액세스할 수 있는지 여부를 결정합니다. exported=true가 없으면 공급자는 애플리케이션 내에서만 액세스할 수 있습니다.
// 메모용 Content Provider 예제
class NotesProvider : ContentProvider() {
companion object {
const val AUTHORITY = "com.example.app.notes"
const val NOTES_PATH = "notes"
const val NOTES_URI = "content://$AUTHORITY/$NOTES_PATH"
const val NOTES_ID = "content://$AUTHORITY/$NOTES_PATH/#"
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(AUTHORITY, NOTES_PATH, 1)
addURI(AUTHORITY, "$NOTES_PATH/#", 2)
}
}
override fun query(uri: Uri, projection: Array<String>?,
selection: String?, args: Array<String>?, sort: String?): Cursor? {
return when (uriMatcher.match(uri)) {
1 -> dbHelper.readableDatabase.query(TABLE_NOTES,
projection, selection, args, null, null, sort)
2 -> dbHelper.readableDatabase.query(TABLE_NOTES,
projection, "_id=?", arrayOf(uri.lastPathSegment), null, null, null)
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val id = dbHelper.writableDatabase.insert(TABLE_NOTES, null, values)
context?.contentResolver?.notifyChange(uri, null)
return ContentUris.withAppendedId(uri, id)
}
override fun delete(uri: Uri, selection: String?, args: Array<String>?): Int {
val count = dbHelper.writableDatabase.delete(TABLE_NOTES, selection, args)
context?.contentResolver?.notifyChange(uri, null)
return count
}
// getType, update, onCreate는 생략됨
}
공급자 클래스를 만든 후에는 android:authorities 및 android:exported 속성(공급자가 공개인 경우 true)과 함께 매니페스트에 등록해야 합니다. 시스템은 첫 번째 액세스 시 공급자 인스턴스를 생성합니다. 이는 UI 스레드에서 발생하므로 onCreate가 빠르게 실행되어야 합니다.
Content Provider는 두 가지 수준에서 데이터 액세스를 관리할 수 있습니다: 읽기 권한 및 쓰기 권한. 이들은 매니페스트에서 android:readPermission 및 android:writePermission 속성으로 설정됩니다. 클라이언트 애플리케이션에 해당 권한이 없으면 시스템은 SecurityException으로 호출을 거부합니다.
Android는 FLAG_GRANT_READ_URI_PERMISSION 및 FLAG_GRANT_WRITE_URI_PERMISSION 플래그를 통해 임시 권한을 지원합니다. 이는 애플리케이션이 Intent를 통해 다른 애플리케이션에 파일 URI를 전달할 때 유용합니다. 수신자는 제한된 시간 동안만 특정 URI에 액세스할 수 있습니다. 시스템은 수신 애플리케이션이 종료된 후 임시 권한을 취소합니다.
시스템 공급자의 경우 Android는 애플리케이션 매니페스트에서 특정 권한을 지정해야 합니다. 예를 들어 연락처에 액세스하려면 READ_CONTACTS가 필요하고 캘린더에 액세스하려면 READ_CALENDAR가 필요합니다. Android 6부터 이러한 권한은 설치 시가 아니라 런타임에 요청됩니다.
자주 묻는 질문
Content Provider는 ContentResolver를 통한 애플리케이션 간 데이터 교환을 위한 표준 인터페이스를 제공하는 Android 구성 요소입니다. 저장소 방식(SQLite, 파일, 네트워크)을 추상화하고 읽기 및 쓰기 권한 제어를 통해 안전한 데이터 액세스를 보장합니다.
Authority는 AndroidManifest.xml에 지정된 공급자의 고유 식별자 문자열입니다. URI content://authority/path의 첫 번째 부분을 형성하며 시스템이 ContentResolver 호출을 올바른 공급자로 라우팅하는 데 사용됩니다. Authority는 기기의 모든 애플리케이션 간에 고유해야 합니다.
UriMatcher는 URI를 숫자 코드에 매핑합니다. addURI를 통해 패턴을 추가한 다음 match를 호출하여 들어오는 URI의 코드를 얻습니다. 이를 통해 query, insert, update, delete 메서드가 요청된 테이블 또는 레코드를 결정하고 데이터베이스에서 해당 작업을 수행할 수 있습니다.
네, 사용 후 Cursor는 항상 닫아야 합니다. Cursor를 닫지 않으면 공급자의 데이터에 대한 Binder 참조를 보유하므로 메모리 누수가 발생합니다. Kotlin에서는 자동 닫기를 위해 use 함수를 사용하고 Java에서는 try-with-resources 또는 finally 블록에서 cursor.close()를 사용하세요.
Content Provider는 애플리케이션 간 데이터 액세스를 위한 구성 요소이고, SQLiteDatabase는 단일 애플리케이션을 위한 내부 저장소 메커니즘입니다. Content Provider는 URI 인터페이스와 권한 제어를 제공하는 반면, SQLiteDatabase는 OS 수준 보안 메커니즘 없이 데이터베이스를 직접 조작합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.