앱 경계를 넘어 데이터를 공유하는 표준 인터페이스.
content://com.android.contacts/contacts content://media/external/images/media
URI 로 데이터를 지목하고 ContentResolver 로 접근한다 프로세스가 다르므로 Binder IPC 를 탄다
쓰는 쪽이 대부분이다
val projection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME)
context.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection, null, null,
"${MediaStore.Images.Media.DATE_ADDED} DESC"
)?.use { cursor -> // ★ use 로 반드시 닫는다
val idCol = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
while (cursor.moveToNext()) { ... }
}
Cursor 를 안 닫으면 누수다 — use{} 로 감싼다 query 는 동기 호출이다 → Dispatchers.IO 에서 부른다
파일을 주고받을 때 — FileProvider
val uri = FileProvider.getUriForFile(ctx, "${ctx.packageName}.fileprovider", file)
val intent = Intent(Intent.ACTION_SEND).apply {
type = "image/jpeg"
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // ★ 임시 권한을 준다
}
왜 file:// 를 못 쓰나
- Android 7.0(N) 부터 앱 밖으로 file:// URI 를 넘기면 FileUriExposedException
- 받는 앱이 내 파일 시스템 경로를 알게 되는 것이 위험하기 때문
FileProvider 는 content:// 로 감싸고 FLAG_GRANT_READ_URI_PERMISSION 으로 '이 URI 만, 잠깐' 접근을 허용한다
만드는 쪽은 드물다
내 앱만 쓰는 데이터라면 ContentProvider 를 만들 이유가 없다
- Room 이 더 낫다 (타입 안전 · 코루틴/Flow 통합)
만들어야 하는 경우
- 다른 앱에 데이터를 공개한다
- 시스템 기능에 연결한다 (검색 제안 · 위젯 · 동기화 어댑터)
- 라이브러리가 초기화 훅으로 요구한다 (androidx.startup 이 이 방식이다)
보안
<provider
android:name=".MyProvider"
android:authorities="com.example.provider"
android:exported="false" /> <!-- 기본을 닫아 둔다 -->
exported="true" 로 열면서 permission 을 안 걸면 아무 앱이나 내 데이터를 읽는다 — 실제로 흔한 취약점이다 Android 12+ 는 exported 명시를 강제한다
면접 함정
- ❌ "앱 내부 DB 접근에도 ContentProvider를 쓴다" → IPC 오버헤드만 낸다. Room을 쓴다.
- ❌ "query()는 백그라운드에서 알아서 돈다" → 동기 호출이다. 직접 IO 디스패처로 옮긴다.