-
[Android] ContentProvider ๊ตฌํ ๋ฐ ์ฌ์ฉ๋ฒAndroid 2022. 2. 5. 21:40๋ฐ์ํ
ContentProvider๋?
ContentProvider๋ Activity, BroadcastReceiver, Service์ ๋์ผํ๊ฒ ์๋๋ก์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ฑํ๋ 4๋ ์์ ์ค ํ๋๋ก ๋ค๋ฅธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ดํฐ์ ์ ๊ทผ์ด ํ์ํ ๋ ์ฌ์ฉํ๊ฒ ๋๋ ์ปดํฌ๋ํธ์ ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ๊ฐ ์ฑ์ ํ๋์ ํ๋ก์ธ์ค๋ก ์คํ๋๋ฉฐ ์์ ์ ํ๋ก์ธ์ค์์ ์ฌ์ฉํ๋ ๋ฐ์ดํฐ๋ ์์ ๋ง ์ ๊ทผ๊ฐ๋ฅํ๋๋ก ๋์ด ์์ต๋๋ค. ํ์ง๋ง, ์ฌ์ง์ฒฉ์ ์๋ ์ฌ์ง๋ค์ ๊ฐ์ ธ์ค๊ฑฐ๋ ์ฐ๋ฝ์ฒ์ ์๋ ์ฐ๋ฝ์ฒ ์ ๋ณด๋ค์ ๊ฐ์ ธ์์ผํ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. ์ด๋ฅผ ContentProvider๋ฅผ ์ฌ์ฉํ๋ฉด ๋ค๋ฅธ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ทผํ๊ฒ ๋ง๋ค ์ ์๋ ํต๋ก ์ญํ ์ ์ ๊ณตํด์ค๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฑ์ ๋ณด์์ ์ํด์ ์๊ฒจ๋ ContentProvider ์ปดํฌ๋ํธ๋ ์๋๋ก์ด๋ ๊ตฌ์ฑ์์์ด๊ธฐ ๋๋ฌธ์ ์์คํ ์์ ๊ด๋ฆฌํ๋ฉฐ, Manifest ํ์ผ์ ๋ช ์ํด์ค์ผ ์์คํ ์์ ์ ์ ์์ต๋๋ค.
์ผ๋ฐ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ด ๋ ๊ฐ์ง ๊ฒฝ์ฐ์์ ์ฃผ๋ก ContetProvider๋ฅผ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค.
- ๋ด ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ค๋ฅธ ์ ํ๋ฆฌ์ผ์ด์ ์ ContentProvider์ ์ก์ธ์ค ํ๊ธฐ ์ํด ์ฝ๋ ๊ตฌํ
- ๋ด ์ ํ๋ฆฌ์ผ์ด์ ์ ContentProvider๋ฅผ ์์ฑํ์ฌ ๋ค๋ฅธ ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ๋ฐ์ดํฐ ๊ณต์
ContentProvider๊ฐ ์์ฑ๋ ๋ค๋ฅธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ทผํ๊ธฐ ์ํด์๋ Context์ ์๋ ContentResolver ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ContentProvider์ ์๋ฒ-ํด๋ผ์ด์ธํธ ๊ตฌ์กฐ๋ก ํต์ ์ ์ฃผ๊ณ ๋ฐ์์ผ ํฉ๋๋ค. ์ฆ, ContentResolver ๊ฐ์ฒด๊ฐ ContentProvider์ ๋ฐ์ดํฐ ์์ฒญ์ ํ๊ฒ ๋๊ณ ContentProvider๋ ์์ฒญ๋ ์์ ์ ์คํํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํ๋๋ ๊ตฌ์กฐ์ ๋๋ค.
ContentProvider์์ ๊ณต์ ํ ์ ์๋ ๋ฐ์ดํฐ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค, ํ์ผ, SharedPreference 3๊ฐ์ง๊ฐ ์์ต๋๋ค. ํ์ง๋ง ์ผ๋ฐ์ ์ผ๋ก๋ ContentProvider๋ CRUD ๋์์ ๊ธฐ๋ณธ์ผ๋ก ํ๊ณ ์๊ธฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์ฃผ๋ก ์ฌ์ฉ๋ฉ๋๋ค. ์์ ๊ทธ๋ฆผ์์๋ Activity๋ Fragment์์ CursorLoader๋ฅผ ํธ์ถํ๊ณ ContentResolver๋ฅผ ์ฌ์ฉํ์ฌ ContentProvider๋ฅผ ๊ฐ์ ธ์ค์ง๋ง ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๊ตณ์ด ์ฟผ๋ฆฌ๋ฅผ ํ์ง ์๊ธฐ์ CursorLoader๋ ์ฌ์ฉํ์ง ์๊ณ ์ค์ต์ฝ๋๋ฅผ ์์ฑํ์์ต๋๋ค. ์๋๋ ๋ ๊ฐ์ง์ ์ฑ์ ๋ง๋ค์ด์ ํ๋์ ์ฑ์์ Content Provider ์์ฑํ๊ณ ๋ค๋ฅธ ์ฑ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋๋ก ํ๋๊ฒ ๋ง์ง๋ง, ๊ตฌํ์ ํธ๋ฆฌํจ์ ์ํด ํ๋์ ์ฑ์์ ํ ์คํธ ํ๋๋ก ํ๊ฒ ์ต๋๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ตฌํ
์๋๋ก์ด๋์์ ์คํค๋ง๋ฅผ ์ ์์ฑํ๊ธฐ ์ํด์ Contract ํด๋์ค๋ฅผ ์์ฑํ๊ณ BaseColumns ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ์ฌ ๊ธฐ๋ณธํค ํ๋๋ฅผ ์์ํ๋๋ก ๊ฐ์ด๋ํ๊ณ ์์ต๋๋ค.
object PersonContract { object PersonEntry : BaseColumns{ const val TABLE_NAME = "person" const val PERSON_NAME = "name" const val PERSON_AGE = "age" const val PERSON_MOBILE = "mobile" } }
PersonContract ๊ฐ์ฒด์์ PersonEntry๋ฅผ ์ ์ํ์ฌ ํด๋น ํ ์ด๋ธ์ ํ์ํ ํ ์ด๋ธ ๋ช , ์ปฌ๋ผ ๋ช ์ ์ ์ํ๊ณ ์์ต๋๋ค.
class DatabaseHelper private constructor(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { override fun onCreate(db: SQLiteDatabase?) { db?.execSQL(CREATE_TABLE) } override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) { TODO("Not yet implemented") } companion object{ const val DATABASE_NAME = "person.db" const val DATABASE_VERSION = 1 var instance : DatabaseHelper? = null val ALL_COLUMNS = arrayOf(BaseColumns._ID, PERSON_NAME, PERSON_AGE, PERSON_MOBILE) val CREATE_TABLE = "CREATE TABLE $TABLE_NAME (${BaseColumns._ID} INTEGER PRIMARY KEY AUTOINCREMENT, " + "$PERSON_NAME TEXT, $PERSON_AGE INTEGER, $PERSON_MOBILE TEXT)" fun getInstance(context: Context): DatabaseHelper = if(instance == null) DatabaseHelper(context.applicationContext) else instance!! } }
DatabaseHelper๋ SQLiteOpenHelper ํด๋์ค๋ฅผ ์์๋ฐ๊ณ ์์ผ๋ฉฐ, person.db ํ์ผ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ์์ต๋๋ค. DatabaseHelper ์ธ์คํด์ค๊ฐ ์์ฑ๋ ๋ onCreate ์ฝ๋ฐฑ์ด ํธ์ถ๋๊ณ PersonContract ์ ์ ์ํ person ํ ์ด๋ธ์ ์์ฑํฉ๋๋ค. SQLiteOpenHelperํด๋์ค๋ DB ์์ฑ๊ณผ ๋ฒ์ ๊ด๋ฆฌ๋ฅผ ๋์์ฃผ๋ ํฌํผ ํด๋์ค๋ก ์์ธํ๊ฒ ์๊ณ ์ถ์ผ๋ฉด ์๋ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ์๋ฉด ๋ฉ๋๋ค.
https://math-coding.tistory.com/246
ContentProvider ํด๋์ค ๊ตฌํ
PersonProvider๋ผ๋ ์ด๋ฆ์ ํด๋์ค๋ฅผ ๊ตฌํํ๊ณ ContentProvider ํด๋์ค๋ฅผ ์์ํ๋๋ก ํฉ๋๋ค.
class PersonProvider : ContentProvider() { private lateinit var database : SQLiteDatabase override fun onCreate(): Boolean { if(context == null) return false database = DatabaseHelper.getInstance(context!!).writableDatabase return true } override fun query( uri: Uri, projcetion: Array<out String>?, selection: String?, selctionArgs: Array<out String>?, sortOrder: String? ): Cursor? { var cursor: Cursor? = null when(uriMatcher.match(uri)){ PERSONS -> { cursor = database.query(TABLE_NAME, projcetion, selection, selctionArgs, null, null, sortOrder) } else -> throw IllegalArgumentException("์ ์ ์๋ URI : $uri") } cursor.setNotificationUri(context?.contentResolver, uri) return cursor } override fun getType(uri: Uri): String? { when(uriMatcher.match(uri)){ PERSONS -> return "vnd.android.cursor.dir/persons" else -> throw IllegalArgumentException("์ ์ ์๋ URI : $uri") } } override fun insert(uri: Uri, values: ContentValues?): Uri? { val id = database.insert(TABLE_NAME, null, values) if(id > 0){ val uri = ContentUris.withAppendedId(CONTENT_URI, id) context?.contentResolver?.notifyChange(uri, null) return uri } throw SQLException("์ถ๊ฐ ์คํจ URI : $uri") } override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int { var count = 0 when(uriMatcher.match(uri)){ PERSONS -> count = database.delete(TABLE_NAME, selection, selectionArgs) else -> throw IllegalArgumentException("์ ์ ์๋ URI : $uri") } context?.contentResolver?.notifyChange(uri, null) return count } override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int { var count = 0 when(uriMatcher.match(uri)){ PERSONS -> count = database.update(TABLE_NAME, values, selection, selectionArgs) else -> throw IllegalArgumentException("์ ์ ์๋ URI : $uri") } context?.contentResolver?.notifyChange(uri, null) return count } companion object{ const val AUTHORITY = "com.example.contentprovider" const val BASE_PATH = "person" val CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH) const val PERSONS = 1 const val PERSON_ID = 2 val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply { addURI(AUTHORITY, BASE_PATH, PERSONS) addURI(AUTHORITY, BASE_PATH + "/#", PERSON_ID) } } }
์ถ์ ํด๋์ค์ธ ContentProvider๋ onCreate, query, getType, insert, delete, update 6๊ฐ์ง ํ์ ๋ฉ์๋๋ฅผ ๊ฐ์ ์ ์ผ๋ก ๊ตฌํํ๋๋ก ํฉ๋๋ค. onCreate ๋ฉ์๋๋ฅผ ์ ์ธํ๊ณ ๋ ContentResolver์ ๋์ผํ ๋ฉ์๋ ๋ช ์ด ์ ์๋์ด ์์ผ๋ฉฐ ํด๋น ๋ฉ์๋๋ฅผ ํตํด ContentProvider์ ํต์ ํ์ฌ ๋ฐ์ดํฐ์ ์ก์ธ์คํ ์ ์์ต๋๋ค. ์ ์ฒด ์ฝ๋๋ฅผ ํ ๋ฒ์ ๋ณด๋ฉด ์ด๋ ค์ฐ๋ ์ชผ๊ฐ์ ํ๋์ฉ ์ดํด๋ด ์๋ค.
companion object{ const val AUTHORITY = "com.example.contentprovider" const val BASE_PATH = "person" val CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH) const val PERSONS = 1 const val PERSON_ID = 2 val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply { addURI(AUTHORITY, BASE_PATH, PERSONS) addURI(AUTHORITY, BASE_PATH + "/#", PERSON_ID) } }
Content Provider๋ฅผ ๋ง๋ค๊ธฐ ์ํด์๋ ๊ณ ์ ํ ๊ฐ์ ๊ฐ์ง Content URI๋ฅผ ๋ง๋ค์ด์ผ ํฉ๋๋ค. Content URI๋ Provider์์ ๋ฐ์ดํฐ๋ฅผ ์๋ณํ๋ URI๋ก Provider์ ๊ถํ๊ณผ ํ ์ด๋ธ ๋๋ ํ์ผ์ ๊ฐ๋ฆฌํค๋ ์ด๋ฆ์ด ํฌํจ๋ฉ๋๋ค. ๋ํ ID ๋ถ๋ถ์ ํ ์ด๋ธ ๋ด ๊ฐ๋ณ์ ์ธ ํ์ ๊ฐ๋ฆฌํค๊ฒ ๋ฉ๋๋ค. ์ด Content URI๊ฐ ContentProvider์ ๋ชจ๋ ๋ฉ์๋์ ํ์ ์ธ์๋ก ๋ค์ด๊ฐ๊ฒ ๋ฉ๋๋ค. ์ด๋ฅผ ํตํด ์ก์ธ์คํ ํ ์ด๋ธ, ํ ๋๋ ํ์ผ์ ๊ฒฐ์ ํ ์ ์์ต๋๋ค.
- content://com.example.contentprovider/person/1
- content:// : ContentProvider์ ์ ์ด๋๋ ๋ฐ์ดํฐ๋ผ๋ ์๋ฏธ๋ก ํญ์ content://๋ก ์์๋จ
- Authority : com.example.contentprovider๋ฅผ ๊ฐ๋ฆฌํค๋ฉฐ, ContentProvider๋ฅผ ๊ตฌ๋ถํ๋ ๊ณ ์ ์ ๊ฐ์ผ๋ก ์ฌ์ฉ๋จ
- Base Path : person์ ๊ฐ๋ฆฌํค๊ณ ํ ์ด๋ธ ๋๋ ํ์ผ์ ๊ฐ๋ฆฌํค๋ ์ด๋ฆ
- ID : ๋ง์ง๋ง ์ซ์๋ฅผ ๊ฐ๋ฆฌํค๋ฉฐ ํ ์ด๋ธ ๋ด ํ(๋ ์ฝ๋)
Content URI ํ์์ ๋ค์๊ณผ ๊ฐ์ผ๋ฉฐ ์์ ์์ ์ฝ๋์์๋ Authority๋ ํจํค์ง๋ช ์ผ๋ก ์ง์ ํ์๊ณ , Base Path๋ PersonContract์ ์ ์ํ ํ ์ด๋ธ ๋ช ๊ณผ ๋์ผํ๊ฒ person์ผ๋ก ์ง์ ํ์์ต๋๋ค. ๊ทธ ์ธ์ UriMatcher ๊ฐ์ฒด๊ฐ ์์ฑ๋์๊ณ addURI ๋ฉ์๋๋ฅผ ํตํด URI๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ฌ์ง๋๋ค. UriMatcher ํด๋์ค๋ URI๋ฅผ ๋งค์นญํ๋๋ฐ ์ฌ์ฉ๋๋ ํด๋์ค๋ก ํน์ URI๊ฐ addURI ๋ฉ์๋๋ก ๋ฑ๋ก๋์๋์ง ํ์ธํ ์ ์์ต๋๋ค. addURI ๋ฉ์๋์ ์ธ์๋ก Authority, Base Path, Code๋ฅผ ํ์๋ก ํ๋ฉฐ match ๋ฉ์๋๋ฅผ ํตํด ๋งค์นญ๋ Authority์ Base Path๋ก ๊ตฌ์ฑ๋ URI๊ฐ ์ผ์นํ๋ค๋ฉด Code๋ฅผ ๋ฐํํ๊ณ ์ผ์นํ์ง ์์ผ๋ฉด -1์ ๋ฐํํฉ๋๋ค.
override fun query( uri: Uri, projcetion: Array<out String>?, selection: String?, selctionArgs: Array<out String>?, sortOrder: String? ): Cursor? { var cursor: Cursor? = null when(uriMatcher.match(uri)){ PERSONS -> { cursor = database.query(TABLE_NAME, projcetion, selection, selctionArgs, null, null, sortOrder) } else -> throw IllegalArgumentException("์ ์ ์๋ URI : $uri") } cursor.setNotificationUri(context?.contentResolver, uri) return cursor }
query ๋ฉ์๋๋ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ๋ ์ฌ์ฉํฉ๋๋ค. ์ด์ ์ uriMatcher์ ๋ฑ๋กํ URI์ ์ผ์นํ URI๋ผ๋ฉด SQLiteDatabase query ๋ฉ์๋๋ฅผ ํธ์ถํ๊ฒ ๋๊ณ ์ผ์นํ์ง ์์ผ๋ฉด IllegalArgumentException์ ๋ฐํํ๋๋ก ํ์์ต๋๋ค. ๊ฐ ๋งค๊ฐ๋ณ์๋ SQLiteDatabase์ query์ ๊ฐ์ ๋งค๊ฐ๋ณ์๊ฐ ๋ค์ด๊ฐ๊ธฐ ๋๋ฌธ์ ๋ณ๋์ ์ค๋ช ์ ํ์ง ์๋๋ก ํ๊ฒ ์ต๋๋ค. cursor.setNotificationUri ๋ฉ์๋๋ ๋ฐํํ๊ธฐ ์ ์ ํธ์ถํ๊ฒ ๋๋๋ฐ ๋ง์ฝ ๋น๋๊ธฐ๋ก ์ฟผ๋ฆฌ ์ค ์ฝ์ ์ด ๋ ๊ฒฝ์ฐ์ ์ ๋ฐ์ดํธ ๋๋ ์ ๋ณด๋ ์์์ ์ ์ ์๋๋ก ์ค์ ํ๋ ๋ฉ์๋์ ๋๋ค. query ๋ฉ์๋์ ๊ตฌํ์ ๋ํด ์ ์๊ณ ์ถ๋ค๋ฉด ๊ณต์๋ฌธ์๋ฅผ ์ดํด๋ด๋ ์ข์ต๋๋ค.
override fun insert(uri: Uri, values: ContentValues?): Uri? { val id = database.insert(TABLE_NAME, null, values) if(id > 0){ val uri = ContentUris.withAppendedId(CONTENT_URI, id) context?.contentResolver?.notifyChange(uri, null) return uri } throw SQLException("์ถ๊ฐ ์คํจ URI : $uri") }
insert๋ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ ๋ ์ฌ์ฉํ๋ ๋ฉ์๋๋ก ๋งค๊ฐ๋ณ์์ธ ContentValues ๊ฐ์ฒด์๋ ์ ์ฅํ ์ปฌ๋ผ๋ช ๊ณผ ๊ฐ๋ค์ด ๋ค์ด๊ฐ๋๋ค. SQLiteDatabase์ insert ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ์ฝ์ ๋ row ID๋ฅผ ๋ฐํ๋ฐ์ ํ ContentUris์ withAppendedId ํจ์๋ฅผ ํตํด ํด๋น ํ ์ด๋ธ์ Content URI์ row ID๊ฐ ํฉ์ณ์ง URI๋ฅผ ๋ฐํ๋ฐ์ ์ ์์ต๋๋ค. ์ฌ๊ธฐ์ notifyChange ๋ฉ์๋๋ ๋ฐ์ดํฐ๊ฐ ์ถ๊ฐ, ์์ , ์ญ์ ๋์์ ๋ ๋ณ๊ฒฝ์ด ์ผ์ด๋ฌ์์ ์๋ ค์ฃผ๋ ์ญํ ์ ํ๊ฒ ๋ฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ๋ชจ๋ ์ฟผ๋ฆฌ๋ ๋น๋๊ธฐ๋ก ์์ฒญ์ ํ๊ธฐ์ notifyChange ํจ์๋ฅผ ํธ์ถํด์ผ ์ํํ๊ฒ ๋ฐ์ดํฐ ์ก์์ ์ด ๊ฐ๋ฅํฉ๋๋ค.
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int { var count = 0 when(uriMatcher.match(uri)){ PERSONS -> count = database.delete(TABLE_NAME, selection, selectionArgs) else -> throw IllegalArgumentException("์ ์ ์๋ URI : $uri") } context?.contentResolver?.notifyChange(uri, null) return count }
delete ๋ฉ์๋๋ where์ ์ ํด๋นํ๋ selection ๋งค๊ฐ๋ณ์์ where์ ๋ด์ ?์ ํด๋น๋๋ selectionArgs ๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ๋ ์ญํ ์ ํฉ๋๋ค. uriMatcher๋ก URI ์ผ์นํ๋์ง ํ์ธํ ํ SQLiteDatabase์ delete ๋ฉ์๋๋ฅผ ์ํํฉ๋๋ค. ๋์ผํ๊ฒ ๋ณ๊ฒฝ์ฌํญ์ ์๋ฆฌ๊ธฐ ์ํด notifyChange ๋ฅผ ํธ์ถํฉ๋๋ค.
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int { var count = 0 when(uriMatcher.match(uri)){ PERSONS -> count = database.update(TABLE_NAME, values, selection, selectionArgs) else -> throw IllegalArgumentException("์ ์ ์๋ URI : $uri") } context?.contentResolver?.notifyChange(uri, null) return count }
update ๋ฉ์๋๋ ํฐ ์ฐจ์ด๊ฐ ์์ด์ ์ค๋ช ์ ์๋ตํฉ๋๋ค.
override fun getType(uri: Uri): String? { when(uriMatcher.match(uri)){ PERSONS -> return "vnd.android.cursor.dir/persons" else -> throw IllegalArgumentException("์ ์ ์๋ URI : $uri") } }
getType ๋ฉ์๋๋ Content Uri๊ฐ ๋ฐํํ๋ ๋ฐ์ดํฐ ์ ํ(MIME)์ ์๊ณ ์ถ์ ๋ ์ฌ์ฉํ๋ ๋ฉ์๋์ ๋๋ค. ํ ์คํธ , HTML ๋๋ JPEG์ ๊ฐ์ ๋ณดํธ์ ์ธ ์ ํ์ ๋ฐ์ดํฐ๋ผ๋ฉด getType ์ด ํด๋น ๋ฐ์ดํฐ์ ๋ํ ํ์ค MIME ์ ํ์ ๋ฐํํด์ผ ํ๊ณ , ๋ฐ์ดํฐ ํ ์ด๋ธ ๋๋ ํ ์ด๋ธ์ ํ์ ๊ฐ๋ฆฌํจ๋ค๋ฉด Android ๊ณต๊ธ์ ์ฒด๋ณ MIME ํ์์ผ๋ก ๋ฐํํด์ผ ํฉ๋๋ค. ๋ฐํ๋๋ ์ ํ๋ณ MIME ํ์์ ๊ณต์๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์๋ฉด ๋ฉ๋๋ค.
https://developer.android.com/guide/topics/providers/content-provider-creating?hl=ko#MIMETypes
๋ฐ์ํContentProvider๋ฅผ ํตํด ๋ฐ์ดํฐ ์ก์ธ์ค
ContentProvider๋ฅผ ์์ฑํ์ฌ ๋ด ์ฑ์ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฅธ ์ฑ์์ ์ ๊ทผํ ์ ์๋๋ก ํ์์ต๋๋ค. ์ด์ ๋ค๋ฅธ ์ฑ์์ ๋ฐ์ดํฐ๋ฅผ ์ก์ธ์คํ๋ ์ค์ต ์ฝ๋๋ฅผ ์์ฑํด๋ณด๋ ค๊ณ ํฉ๋๋ค. ์ด์ ์ ์ค๋ช ํ์๋ฏ์ด ContentProvider๊ฐ ์์ฑ๋ ์ฑ์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ํด๋ผ์ด์ธํธ ์ญํ ํ๋ ContentResolver๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
private fun insertData() { println("insertData๊ฐ ํธ์ถ๋จ") var uri = Uri.parse("content://com.example.contentprovider/person") val values = ContentValues().apply { put(PERSON_NAME, "ows") put(PERSON_AGE, 28) put(PERSON_MOBILE, "010-0000-0000") } uri = contentResolver.insert(uri, values) println("insertDatat ๊ฒฐ๊ณผ : $uri") }
ContentProvider์์ ์ ์ํ Content Uri๋ฅผ ์ ์ธํ ๋ค ContentResolver์ insert ๋ฉ์๋์ Uri์ ContentValues์ ์ ์ฅํ ์ถ๊ฐํ ๋ฐ์ดํฐ๋ค์ ๋์ ํ๋ฉด ๋ฉ๋๋ค. ์์ ์ฝ๋์์๋ name์ ows, age๋ 28, mobile์ 010-0000-0000์ผ๋ก ์ค์ ํ ์ถ๊ฐํ์์ต๋๋ค.
@SuppressLint("Range") private fun queryData() { val uri = Uri.parse("content://com.example.contentprovider/person") val columns = arrayOf(PERSON_NAME, PERSON_AGE, PERSON_MOBILE) val cursor = contentResolver.query(uri, columns, null, null,"name ASC") println("queryData ๊ฒฐ๊ณผ ${cursor?.count}") cursor?.let { cursor -> var index = 0 while(cursor.moveToNext()){ val name = cursor.getString(cursor.getColumnIndex(columns.get(0))) val age = cursor.getInt(cursor.getColumnIndex(columns.get(1))) val mobile = cursor.getString(cursor.getColumnIndex(columns.get(2))) println("#${index} -> ${name}, ${age}, ${mobile}") index++ } } }
๋ค๋ฅธ ์ฑ ๋ฐ์ดํฐ๋ฅผ ์ฟผ๋ฆฌํ๊ธฐ ์ํด์๋ ์กฐํํ๊ณ ์ถ์ ์ปฌ๋ผ ๋ช ๊ณผ where ์ ์ ํ์ํ ๋ณ์๋ค, ์ค๋ฆ์ฐจ์์ ๋ํ ๋ฌธ์์ด์ ContentResolver์ query ๋ฉ์๋์ ๋์ ์ ํ๋ฉด ๋ฉ๋๋ค. query ๋ฉ์๋์ ๊ฒฐ๊ณผ๋ก Cursor ๊ฐ์ฒด๊ฐ ๋ฐํ๋๊ฒ ๋๋๋ฐ, Cursor ๊ฐ์ฒด๋ ์ฟผ๋ฆฌ์ ๊ฒฐ๊ณผ๋ฅผ ์ฝ๊ณ ์ธ ์ ์๊ฒ ํด์ฃผ๋ ์ธํฐํ์ด์ค ์ญํ ์ ํ๊ฒ ๋ฉ๋๋ค. Cursor์ moveToNext ๋ฉ์๋๋ก ๋ฐ์ดํฐ๋ฅผ ํ๋์ฉ ํ์ธํ ์ ์์ผ๋ฉฐ ์ปฌ๋ผ๋ช ์ ํด๋นํ๋ ์ธ๋ฑ์ค๋ฅผ ์๊ณ ์ถ์ผ๋ฉด getColumnIndex ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์๋ฉด ๋ฉ๋๋ค. ๋ฐ์ดํฐ์ ๋ค์ด๊ฐ ๋ชจ๋ ์ปฌ๋ผ๋ช ๋ค์ ํ์ธํ๊ณ ์ถ์ ๋๋ getColumnNames ๋ฉ์๋๋ฅผ ์ด์ฉํ๋ฉด ๋ฉ๋๋ค.
private fun updateDate() { val uri = Uri.parse("content://com.example.contentprovider/person") val selection = "mobile = ?" val selectionArgs = arrayOf("010-0000-0000") val values = ContentValues().apply { put("mobile","010-1000-1000") } val count = contentResolver.update(uri, values, selection, selectionArgs) println("updateData ๊ฒฐ๊ณผ ${count}") } private fun deleteData() { val uri = Uri.parse("content://com.example.contentprovider/person") val selection = "name = ?" val selectionArgs = arrayOf("ows") val count = contentResolver.delete(uri, selection, selectionArgs) println("deleteData ๊ฒฐ๊ณผ ${count}") } private fun println(str: String) = with(binding){ resultTextView.append("$str\n") }
update, delete ๋ช ๋ น์ด๋ ํฌ๊ฒ ์ฐจ์ด๋์ง ์๊ณ ContentProvider์์ ์ ์ํ Content Uri๊ฐ ํ์์ ์ผ๋ก ํ์๋ก ํ๊ณ ๊ทธ์ธ wherer์ ์ ํ์ํ ๋งค๊ฐ๋ณ์๊ฐ ๋ค์ด๊ฐ๋๋ค. update ๋ฉ์๋์์ mobile ์ ๋ณด๊ฐ 010-0000-0000์ผ๋ก ํด๋น๋๋ ๋ฐ์ดํฐ๋ค์ ๋ชจ๋ 010-1000-1000์ผ๋ก ๋ณ๊ฒฝํ๋๋ก ๊ตฌํํ์๊ณ , delete ๋ฉ์๋์์๋ name ์ ๋ณด๊ฐ ows๋ก ์ง์ ๋ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ์ญ์ ํ๋๋ก ๊ตฌํํ์์ต๋๋ค.
ContentProvider Manifest ๋ฑ๋ก
์์ ์ฝ๋๋ง ์์ฑํ ํ ์คํ์ ํ๊ฒ ๋๋ฉด ๋ฐํ์ ์๋ฌ๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค. ContentProvider๋ ์๋๋ก์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์ฑ์์๋ก ์์คํ ์ ๋ฑ๋กํ๋ ๊ณผ์ ์ด ํ์ํฉ๋๋ค. Manifest์ ContentProvider๋ฅผ ๋ช ์ํ๊ฒ ๋๋ฉด ์๋๋ก์ด๋ ์์คํ ์์ ํด๋น ์ ๊ณต์์ ์ ๋ณด๋ฅผ ์ ์ ์๊ฒ ๋ฉ๋๋ค.
<provider android:authorities="com.example.contentprovider" android:name=".PersonProvider" android:exported="true" android:readPermission="com.example.contentprovider.READ_DATABASE" android:writePermission="com.example.contentprovider.WRITE_DATABASE"/>
application ํ๊ทธ ์์ provider ํ๊ทธ๋ฅผ ์ถ๊ฐํ๊ณ authorities์ ContentProvider๋ฅผ ์ ์ํ ๋ ์ฌ์ฉํ Authority ์ ๋ณด์ ๋์ผํ๊ฒ ์ ๋ ฅํฉ๋๋ค. name ์์ฑ ๊ฐ์ผ๋ก ContentProvider๋ฅผ ์์ํ ํด๋์ค ๋ช ์ ๋ฃ์ด์ค๋๋ค. ๋ค๋ฅธ ์ ํ๋ฆฌ์ผ์ด์ ์์ ํด๋น ContentProvider๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ exported ๊ฐ์ true๋ก ํด์ผ ํฉ๋๋ค. ์ดํ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์์ ContentProvider์ ์ฟผ๋ฆฌ๋ฅผ ํ๋๋ฐ ํ์ํ ์ปค์คํ ๊ถํ์ธ readPermission๊ณผ ContentProvider์์ ์ ๊ณตํ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ๊ธฐ ์ํ ๊ถํ์ธ writePermission ๊ถํ์ ์ง์ ํ๋ฉด ๋ฉ๋๋ค. ์์ธํ ์ ๋ณด๋ ์๋ ๊ณต์๋ฌธ์๋ฅผ ํ์ธํ์๋ฉด ๋ฉ๋๋ค.
https://developer.android.com/guide/topics/manifest/provider-element?hl=ko#rprmsn
<permission android:name="com.android.contentprovider.READ_DATABASE" android:protectionLevel="normal"/> <permission android:name="com.android.contentprovider.WRITE_DATABASE" android:protectionLevel="normal"/>
ContentProvider๋ฅผ ์ฌ์ฉํ๋ ค๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ค์๊ณผ ๊ฐ์ด permission ํ๊ทธ๋ฅผ ํตํด ๊ถํ์ ์ง์ ํด์ผ ํฉ๋๋ค. <permission> ํ๊ทธ๋ ๊ถํ์ ์๋ก ์ ์ํ ๋ ์ฌ์ฉ๋๋ ํ๊ทธ๋ก Provider์ readPemission, writePermission์ ์ ์๋ ๊ถํ์ ๋ชจ๋ ์์ฑํด์ผ ํฉ๋๋ค.
์ ๋ฆฌ
Do It ์๋๋ก์ด๋ ์ฑ ํ๋ก๊ทธ๋๋ฐ ์ฑ ์์ ContentProvider๋ฅผ ์ฌ์ฉํ๋ ์์ ๋ฅผ ๊ฐ์ง๊ณ kotlin์ผ๋ก ๋ฐ๊พธ์ด ์ค์ต์ ํด๋ณด์์ต๋๋ค. ContentProvider๋ ๋ค๋ฅธ ์ฑ๊ณผ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ๊ธฐ ์ํ ์ธํฐํ์ด์ค๋ก ContentResolver๋ฅผ ํตํด์ ContentProvider์ ํต์ ํ ์ ์์ต๋๋ค.
- ์ฐธ๊ณ (Do it ์๋๋ก์ด๋ ์ฑ ํ๋ก๊ทธ๋๋ฐ p552~p575
- https://developer.android.com/guide/topics/providers/content-provider-basics?hl=ko/
- https://codechacha.com/ko/android-contentprovider/
๋ฐ์ํ'Android' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Android] findViewById ์๋ฆฌ (0) 2022.03.13 [Android] Native Application(C/C++), NDK build ๋ฐ CMake ๊ตฌ์ฑ (0) 2022.02.14 [Android] Room ์ดํด ๋ฐ ํ์ฉ (0) 2022.01.01 [Android] SQLite, SQLiteOpenHelper, Local DB ์ดํด (0) 2021.11.30 [Android] Context, ContextWrapper, ContextImpl ์ดํด (0) 2021.11.21