๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ’ป Programming/Android Developer AtoZ

[Android] App Components - (5) Content Provider ์™„๋ฒฝ ๊ฐ€์ด๋“œ

by ๊ธฐ๋ฌด์ • 2025. 3. 24.
728x90

 

 

 

Android์˜ 4๋Œ€ ์ปดํฌ๋„ŒํŠธ ์ค‘ ํ•˜๋‚˜์ธ Content Provider๋Š” ์•ฑ ๊ฐ„ ๋ฐ์ดํ„ฐ ๊ณต์œ ๋ฅผ ์œ„ํ•œ ํ‘œ์ค€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ํŒŒ์ผ, ๋„คํŠธ์›Œํฌ ๋ฐ์ดํ„ฐ ๋“ฑ์„ ๋‹ค๋ฅธ ์•ฑ๊ณผ ์•ˆ์ „ํ•˜๊ฒŒ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ง€๊ธˆ๋ถ€ํ„ฐ Content Provider์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 


 

Content Provider

Android์—์„œ๋Š” ๊ฐ ์•ฑ์ด ์ž์‹ ๋งŒ์˜ ์ €์žฅ ๊ณต๊ฐ„(SandBox)์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‹ค๋ฅธ ์•ฑ์ด ์ง์ ‘ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ Content Provider๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ณด์•ˆ ์ •์ฑ…์„ ์œ ์ง€ํ•˜๋ฉด์„œ๋„ ๋ฐ์ดํ„ฐ ๊ณต์œ ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

Content Provider๋Š” ์•ฑ ๋‚ด๋ถ€์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฅธ ์•ฑ์ด ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์—ฐ๋ฝ์ฒ˜ ์•ฑ์˜ ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ๋‹ค๋ฅธ ์•ฑ์ด ์ฝ์„ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

 

์ฃผ์š” ์—ญํ• ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ์•ฑ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ํŒŒ์ผ, ๋„คํŠธ์›Œํฌ ๋“ฑ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๊ณต์œ 
  • ๋‹ค๋ฅธ ์•ฑ์ด ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ธฐ, ์ถ”๊ฐ€, ์ˆ˜์ •, ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋„๋ก API๋ฅผ ์ œ๊ณต
  • ๊ถŒํ•œ ์ œ์–ด๋ฅผ ํ†ตํ•ด ํŠน์ • ์•ฑ๋งŒ ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ฐ€๋Šฅ

 

Content Provider ๋™์ž‘ ๋ฐฉ์‹

Content Provider๋Š” ContentResolver๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉฐ, ์•ฑ์€ ์ด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ณ  ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

URI(Uniform Resource Identifier)

Content Provider์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์‹๋ณ„ํ•˜๋Š” ๊ณ ์œ ํ•œ ์ฃผ์†Œ์ž…๋‹ˆ๋‹ค.

URI๋Š” content:// ํ˜•์‹์„ ๊ฐ€์ง€๋ฉฐ, ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

content://<Authority>/<Path>/<ID>
๊ตฌ์„ฑ ์š”์†Œ ์„ค๋ช…
content:// Content Provider๋ฅผ ์‹๋ณ„ํ•˜๋Š” ๊ธฐ๋ณธ ํ”„๋กœํ† ์ฝœ
Authority ํŠน์ • Content Provider๋ฅผ ์‹๋ณ„ํ•˜๋Š” ๋ฌธ์ž์—ด (AndroidManifest.xml ์—์„œ ์ •์˜)
Pah ํ…Œ์ด๋ธ” ๋˜๋Š” ๋ฐ์ดํ„ฐ ๊ทธ๋ฃน ๋“ฑ ๋ฐ์ดํ„ฐ์˜ ํŠน์ • ๊ฒฝ๋กœ
ID (์„ ํƒ ์‚ฌํ•ญ) ํŠน์ • ํ–‰(๋ ˆ์ฝ”๋“œ)๋ฅผ ์‹๋ณ„ํ•˜๋Š” ID

 

 

์˜ˆ๋ฅผ ๋“ค์–ด users ํ…Œ์ด๋ธ”์˜ ID๊ฐ€ 1์ธ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•œ๋‹ค๋ฉด, URI๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

content://com.example.provider/users/1

 

728x90

 

 

CRUD ๋ฉ”์„œ๋“œ

Content Provider๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์กฐ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๋ฉ”์„œ๋“œ ์—ญํ• 
query() ๋ฐ์ดํ„ฐ ์กฐํšŒ
insert() ๋ฐ์ดํ„ฐ ์‚ฝ์ž…
update() ๋ฐ์ดํ„ฐ ์ˆ˜์ •
delete() ๋ฐ์ดํ„ฐ ์‚ญ์ œ
getType() URI์— ํ•ด๋‹นํ•˜๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž… ๋ฐ˜ํ™˜

 

 

Content Provider ๊ตฌํ˜„

๋‹ค์Œ์€ SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ Content Prvider๋ฅผ ๊ตฌํ˜„ํ•œ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.

class MyContentProvider : ContentProvider() {

    private lateinit var dbHelper: MyDatabaseHelper

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

    override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor? {
        val db = dbHelper.readableDatabase
        return db.query("users", projection, selection, selectionArgs, null, null, sortOrder)
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        val db = dbHelper.writableDatabase
        val id = db.insert("users", null, values)
        return ContentUris.withAppendedId(uri, id)
    }

    override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {
        val db = dbHelper.writableDatabase
        return db.update("users", values, selection, selectionArgs)
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
        val db = dbHelper.writableDatabase
        return db.delete("users", selection, selectionArgs)
    }

    override fun getType(uri: Uri): String? {
        return "vnd.android.cursor.dir/vnd.com.example.provider.users"
    }
}
<provider
    android:name=".MyContentProvider"
    android:authorities="com.example.provider"
    android:exported="false"
    android:grantUriPermissions="true" />

 

 

๋‹ค๋ฅธ ์•ฑ์—์„œ Content Provider๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ContentResolver๋ฅผ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ ์กฐํšŒ

fun selectData() {
    
    val cursor: Cursor? = contentResolver.query(
        Uri.parse("content://com.example.provider/users"),
        null, null, null, null
    )

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

 

๋ฐ์ดํ„ฐ ์‚ฝ์ž…

fun insertData() {

    val values = ContentValues().apply {
        put("name", "John Doe")
    }

    val uri = contentResolver.insert(Uri.parse("content://com.example.provider/users"), values)
    Log.d("ContentProvider", "Inserted URI: $uri")
}

 

๋ฐ์ดํ„ฐ ์ˆ˜์ •

fun updateData() {

    val values = ContentValues().apply {
        put("name", "Updated Name")
    }

    val updatedRows = contentResolver.update(
        Uri.parse("content://com.example.provider/users"),
        values, "id=?", arrayOf("1")
    )
    Log.d("ContentProvider", "Updated Rows: $updatedRows")
}

 

๋ฐ์ดํ„ฐ ์‚ญ์ œ

fun deleteData() {
    
    val deletedRows = contentResolver.delete(
        Uri.parse("content://com.example.provider/users"),
        "id=?", arrayOf("1")
    )
    Log.d("ContentProvider", "Deleted Rows: $deletedRows")
}

 

 

์‹ค์ œ๋กœ ์ €๋Š” Content Provider๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ๋ณด๋‹จ, ๊ฐค๋Ÿฌ๋ฆฌ ์•ฑ์—์„œ ์ด๋ฏธ์ง€์— ์ ‘๊ทผํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ๊ฐค๋Ÿฌ๋ฆฌ ์•ฑ์—์„œ ๊ฐ€์žฅ ์ตœ๊ทผ ์‚ฌ์ง„์„ ๊ฐ€์ ธ์˜ค๋Š” ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

fun getLatestImageUri(context: Context): Uri? {

    val contentResolver = context.contentResolver
    val uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    val projection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DATE_ADDED)
    val sortOrder = "${MediaStore.Images.Media.DATE_ADDED} DESC LIMIT 1"

    val cursor = contentResolver.query(uri, projection, null, null, sortOrder)

    cursor?.use {
        if (it.moveToFirst()) {
            val idIndex = it.getColumnIndex(MediaStore.Images.Media._ID)
            val imageId = it.getLong(idIndex)
            return ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageId)
        }
    }
    return null
}
val latestImageUri = getLatestImageUri(context)
if (latestImageUri != null) {
    Log.d("Gallery", "Latest image URI: $latestImageUri")
}
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

 


 

Content Provider๋Š” Android์—์„œ ์•ฑ ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋œ ๊ฐ•๋ ฅํ•œ ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ํŒŒ์ผ, ๋„คํŠธ์›Œํฌ ๋ฐ์ดํ„ฐ ๋“ฑ์„ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ถŒํ•œ ์ œ์–ด๋ฅผ ํ†ตํ•ด ๋ณด์•ˆ์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ๊ตฌํ˜„์ด ๋ณต์žกํ•˜๊ณ  ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•˜๋Š” ํ•œ๊ณ„๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

์ด์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๊ถŒํ•œ ์„ค์ •๊ณผ URI ๊ด€๋ฆฌ๋กœ ํšจ์œจ์ ์ด๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

 


 

์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์ž ๋กœ๋“œ๋งต์„ ๋”ฐ๋ผ ์ •๋ฆฌํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

 

 

 

728x90