기초다지기(4) - ContentProvider

JongHyunSeo·2025년 2월 28일

Android 기초

목록 보기
4/7

ContentProvider?

일반적으로 각 앱은 하나의 프로세스로 실행되며 자신의 프로세스에서 사용하는 데이터는 자신만 접근 가능하다. 하지만 사진이나 연락처등 정보를 가져와야 하는 경우 ContentProvider는 앱 내의 데이터(데이터베이스, 파일)를 다른 앱에서도 접근할 수 있도록 통로를 제공해주는 역할을 하며, 보안을 위해 안드로이드 시스템에서 관리하며 Manifest 파일에 명시해줘야 시스템에서 알 수 있다.

ContentProvider가 생성된 다른 애플리케이션의 데이터에 접근하기 위해선 Context에 있는ContentResolver 객체를 사용하여 ContentProvider와 서버-클라이언트 구조로 통신을 주고받아야 한다.

UI에서 ContentProvider에 접근하는 일반적인 패턴은 CursorLoader를 사용하여 백그라운드에서 비동기식 쿼리를 실행하는 방식으로, 쿼리를 진행하는 동안 UI를 계속 제공할 수 있다. 위 그림처럼 Activity 또는 FragmentCursorLoader를 호출하고 ContentResolver를 사용하여 ContentProvider와 통신을 한다.

기본 구조

ContentProvider는 데이터를 CRUD 방식으로 처리하며 ContentProvider를 사용하려면 아래와 같은 구조의 URI를 기반으로 데이터에 접근한다.

content://<authority>/<table>/<id>
  • content:// : ContentProvider 식별자
  • authority : 특정 ContentProvider를 식별하는 고유 이름
  • table : 접근하려는 데이터 (Table or Collection)
  • id : 특정 데이터의 고유 ID (Optional)

ContentProvider 구현

1. Manifest에 ContentProvider 등록

<provider
    android:name=".MyContentProvider"
    android:authorities="com.example.myprovider"
    android:exported="true"
    android:grantUriPermissions="true" />
  • authorities : ContentProvider를 식별하는 고유 이름
  • exported : 외부 앱에서 접근 가능 여부 (true / false)
  • grantUriPermissions : 특정 앱에 데이터 접근 권한을 동적으로 부여할지 여부
    - 특정 URI에 대해서만 접근 권한을 부여할 수 있음

2. ContentProvider 클래스 구현

class MyContentProvider : ContentProvider() {

    companion object {
        const val AUTHORITY = "com.example.myprovider"
        val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY/users")
    }

    private lateinit var databaseHelper: MyDatabaseHelper

    override fun onCreate(): Boolean {
        databaseHelper = MyDatabaseHelper(context!!)
        return true
    }

    override fun query(
    uri: Uri,               // 요청된 데이터의 URI
    projection: Array<String>?,  // 조회할 컬럼 목록 (null이면 모든 컬럼 조회)
    selection: String?,     // WHERE 절 (Optional)
    selectionArgs: Array<String>?, // WHERE 절의 값
    sortOrder: String?      // 정렬 기준 (예: "name ASC")
	): Cursor? {
        val db = databaseHelper.readableDatabase
        return db.query("users", projection, selection, selectionArgs, null, null, sortOrder)
    }

    override fun insert(
    uri: Uri, // 삽입할 테이블 URI
    values: ContentValues? // 삽입할 데이터
    ): Uri? {
        val db = databaseHelper.writableDatabase
        val id = db.insert("users", null, values)
        return Uri.withAppendedPath(CONTENT_URI, id.toString())
    }

    override fun update(
    uri: Uri, // 업데이트할 테이블 URI
    values: ContentValues?, // 변경할 데이터
    selection: String?, // WHERE 절 (Optional)
    selectionArgs: Array<String>? // WHERE 절의 값
    ): Int {
        val db = databaseHelper.writableDatabase
        return db.update("users", values, selection, selectionArgs)
    }

    override fun delete(
    uri: Uri, // 삭제할 데이터의 URI
    selection: String?, // WHERE 절 (Optional)
    selectionArgs: Array<String>? // WHERE 절의 값
    ): Int {
        val db = databaseHelper.writableDatabase
        return db.delete("users", selection, selectionArgs)
    }

    override fun getType(uri: Uri): String? {
    	// "vnd.android.cursor.dir/vnd.$AUTHORITY.users" : 여러개 데이터
        // "vnd.android.cursor.item/vnd.$AUTHORITY.users" : 단일 데이터
        return "vnd.android.cursor.dir/vnd.$AUTHORITY.users"
    }
}
  • onCreate : ContentProvider가 처음 생성될 때 호출
  • query : 데이터 조회 (SELECT)
  • insert : 데이터 추가 (INSERT)
  • update : 데이터 수정 (UPDATE)
  • delete : 데이터 삭제 (DELETE)
  • getType : MIME type 반환

3. ContentProvider 사용

데이터 조회 (query)

val cursor = contentResolver.query(
    Uri.parse("content://com.example.myprovider/users"),
    null, null, null, null
)

cursor?.use {
    while (it.moveToNext()) {
        val name = it.getString(it.getColumnIndexOrThrow("name"))
        Log.d("ContentProvider", "User Name: $name")
    }
}

데이터 삽입 (insert)

val values = ContentValues().apply {
    put("name", "Jong Tang")
    put("email", "jong@example.com")
}

val newUserUri = contentResolver.insert(
    Uri.parse("content://com.example.myprovider/users"), values
)
Log.d("ContentProvider", "New User URI: $newUserUri")

데이터 수정 (update)

val values = ContentValues().apply {
    put("email", "new_email@example.com")
}

val rowsUpdated = contentResolver.update(
    Uri.parse("content://com.example.myprovider/users"),
    values,
    "name = ?",
    arrayOf("Jong Tang")
)
Log.d("ContentProvider", "Rows Updated: $rowsUpdated")

데이터 삭제 (delete)

val rowsDeleted = contentResolver.delete(
    Uri.parse("content://com.example.myprovider/users"),
    "name = ?",
    arrayOf("Jong Tang")
)
Log.d("ContentProvider", "Rows Deleted: $rowsDeleted")

권한 부여

특정 앱에 URI 접근 권한을 부여 가능

val uri = Uri.parse("content://com.example.myprovider/users/1")
val packageName = "com.otherapp.example"

contentResolver.takePersistableUriPermission(
    uri,
    Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)

다만 권한은 영구적으로 유지되지 않으며, 앱이 다시 시작되면 권한이 사라질 수 있다는점을 유의해야한다.

  • FLAG_GRANT_READ_URI_PERMISSION : 읽기 권한 허용
  • FLAG_GRANT_WRITE_URI_PERMISSION : 쓰기 권한 허용

파일 공유 (FileProvider)

FileProvider는 내부 파일을 외부 앱과 안전하게 공유할 수 있도록 도와주는 ContentProvider의 한 종류이다.

  1. ManifestFileProvider 등록
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.example.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>
  1. res/xml/file_paths.xml 공유 경로 지정
<paths>
    <external-files-path name="shared_images" path="Pictures/" />
</paths>
  1. FileProvider로 파일 공유
val file = File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), "image.jpg")
val uri = FileProvider.getUriForFile(context, "com.example.fileprovider", file)

val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.type = "image/*"
shareIntent.putExtra(Intent.EXTRA_STREAM, uri)
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

startActivity(Intent.createChooser(shareIntent, "이미지 공유하기"))

grantUriPermissions를 동해 특정 URI에 대한 일시적 권한을 부여하여 다른 앱이 image.jpg 파일 읽기 가능

profile
공부는 꾸준하게

0개의 댓글