안드로이드 개발을 한다면, 안드로이드 4대 컴포넌트에 대해서 그 중 하나라도 들어본 적이 있을 것이다.
안드로이드의 4대 컴포넌트는 다음과 같다.
Activity, Service, Content Provider, Broadcast Receiver
오늘은 안드로이드의 4대 컴포넌트 중 Content Provider에 대해서 공부해 볼 예정이다.
Content Provider는 어플리케이션 사이에서 파일 및 데이터를 공유하는 역할을 갖는다.
어떠한 앱이 다른 앱의 데이터에 접근할 때, Content Provider를 통해 접근해야 한다.
ex) 파일을 공유하는 경우 → SNS 앱을 사용해서 사진 파일을 업로드 할 때, 갤러리 앱에 접근해서 사진 파일을 가져오는 경우
Content Resolver 객체는 Content Provider 객체와 통신하며, 이 객체는 Content Provider 를 구현하는 클래스의 인스턴스입니다.
다른 앱의 데이터에 접근할 때, URI를 이용하여 Content Resolver를 통해 다른 앱의 Content Provider에게 데이터를 요청한다.
Content Provider는 요청받은 URI를 확인 후 내부 DB에 접근하여 조회한 데이터를 Content Resolver에게 전달한다.
Content Provider에서 Custom Method를 생성하여 다른 앱의 Content Resolver가 Custom Method를 호출하고, return 값을 Bundle로 받을 수 있는 방법이 있다.
AndroidManifest.xml 파일의 application 영역에 아래와 같이 정의한다.
<provider
android:name=".provider.MyContentProvider"
android:authorities="com.study.providera.MyContentProvider"
android:readPermission="com.study.providera.READ_DATABASE"
android:writePermission="com.study.providera.WRITE_DATABAS"
android:exported="true" />
authorities
: Content Provider를 식별하는 속성, Provider에 접근하기 위해 URI 생성 시, 해당 정보를 입력한다.
read / write Permission
: 외부에서 현재 앱 DB에 접근할 수 있는 권한을 설정한다.
exported
: 외부 접근 허용 여부 (외부에서 현재 앱의 DB를 참조하는 것이 주된 목적이기 때문에 true로 주는 것이 좋다)
<permission android:name="com.study.providera.READ_DATABASE" android:protectionLevel="normal" />
<permission android:name="com.study.providera.WRITE_DATABASE" android:protectionLevel="normal" />
다음과 같이 권한을 사용하려면 Provider를 선언한 앱의 Manifest에 다음과 같이 permission을 추가해줘야 한다.
<uses-permission android:name="com.study.providera.READ_DATABASE"/>
<uses-permission android:name="com.study.providera.WRITE_DATABASE"/>
<queries>
<package android:name="com.study.providera"/>
</queries>
구현하기 위해서는 ContentProvider를 상속받는 Provider 클래스를 생성해야 한다. 상속을 받게 되면 다음과 같은 메서드들을 오버라이드해야 한다.
onCreate()
lateinit var db: ItemDatabase
override fun onCreate(): Boolean {
context?.let { db = ItemDatabase.getInstance(it) }
return true
}
전역 변수에 있는 DB 객체를 초기화
query()
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?,
): Cursor? {
context?.let {
val cursor = db.itemDao().getAllItem()
cursor.setNotificationUri(it.contentResolver, uri)
return cursor
}
throw IllegalArgumentException("Failed to query row for uri $uri")
}
Cursor
값을 반환하는 함수
query() 인수 | SELECT 키워드/매개변수 | 참고 |
---|---|---|
Uri | FROM table_name | Uri는 table_name이라는 이름의 제공자 테이블에 매핑됩니다. |
projection | col, col, col, ... | projection은 각 행에 포함되어야 하는 열의 배열 |
selection | Where col = ? | selection은 행을 선택하는 기준을 지정한다. |
selectionArgs | ? 자리 표시자를 대체 | ?에 들어갈 값을 배열 형태로 넣는다. |
sortOrder | ORDER BY col, col, col,... | 반환된 cursor 내에 행이 나타나는 순서를 지정 |
insert()
override fun insert(uri: Uri, values: ContentValues?): Uri? {
context?.let {
val id = db.itemDao().insertItem(Item.fromContentValues(values))
if (id != -1L) {
it.contentResolver.notifyChange(uri, null)
return ContentUris.withAppendedId(uri, id)
}
}
throw IllegalArgumentException("Failed to insert row into $uri")
}
어떤 위치(Uri → AUTHORITY + TABLE_NAME)에 값 ContentValues 을 insert 하는 함수
update()
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
context?.let {
val count = db.itemDao().updateItem(Item.fromContentValues(values))
it.contentResolver.notifyChange(uri, null)
return count
}
throw IllegalArgumentException("Failed to update row into $uri")
}
query() 함수와 마찬가지로 selection을 통해 where 절을 작성하고, selectionArgs에 where절에 있는 ? 값에 대응시킨다.
DB Table을 update(수정) 하는 함수.
delete()
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
context?.let {
val count = db.itemDao().deleteItem(ContentUris.parseId(uri))
it.contentResolver.notifyChange(uri, null)
return count
}
throw IllegalArgumentException("Failed to delete row into $uri")
}
getType()
override fun getType(p0: Uri): String? {
return "${MyContract.AUTHORITY}.${MyContract.TABLE_NAME}"
}
URI 값을 받아서 어떤 식별의 어떤 테이블 위치인지 타입을 반환하는 함수
onCreate()와 getType()을 제외하면 내부 DB에 CRUD하는 메서드이다.
특정 동작을 하는 커스텀 메서드를 추가할 수 있다.
다른 앱에서 contenResolver.{커스텀 메서드명}() 으로 사용이 가능하다.
Java에서는 getContentResolver()를 통해 contentResolver 객체를 참조할 수 있다.
아래는 Content Resolver를 이용하여 CRUD 관련 query, insert 메서드 호출, 커스텀메서드 호출을 위한 call 메서드를 호출하는 예이다.
val contentURI = Uri.parse("content://com.study.providera.MyContentProvider")
// query
contentResolver.query(contentURI, null, null, null, null)
// insert
val values = ContentValues()
values.put("title", "제목")
values.put("content", "내용")
contentResolver.insert(contentURI, contentValues)
// Custom Method
val bundle: Bundle? = contentResolver.call(contentURI, "{메소드명}", null, null)
// Custom Method
val bundle: Bundle? = contentResolver.call(contentURI, "{메소드명}", null, null)
// query
contentResolver.query(contentURI, null, null, null, null)
// insert
val values = ContentValues()
values.put("title", "제목")
values.put("content", "내용")
contentResolver.insert(contentURI, contentValues)
CRUD 메서드 또한 contentResolver를 사용하여 다음과 같이 사용하면 된다.
ContentResolver는 URI를 이용하여 ContentProvider에 데이터를 요청한다고 했다. 이때 URI 구조에 대해서 알아보자.
위 ContentResolver 예시 코드에서 URI를 정의한 내용을 보면
val contentURI = Uri.parse("content://com.study.providera.MyContentProvider")
URI 예시 - content://com.study.providera.MyContentProvider/item/1(scheme) ( authority ) ( path/id )