[Android] Content Provider

Delight Yoon·2023년 3월 13일
0

Android

목록 보기
12/17

안드로이드 개발을 한다면, 안드로이드 4대 컴포넌트에 대해서 그 중 하나라도 들어본 적이 있을 것이다.

안드로이드의 4대 컴포넌트는 다음과 같다.

Activity, Service, Content Provider, Broadcast Receiver

오늘은 안드로이드의 4대 컴포넌트 중 Content Provider에 대해서 공부해 볼 예정이다.

📌 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로 받을 수 있는 방법이 있다.

📌 Content Provider 선언 및 접근 제한

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로 주는 것이 좋다)

Provider가 선언된 앱 (A) 에서는

<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을 추가해줘야 한다.

데이터를 요청하고 전달받을 앱(B) 에서는

<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>

Content Provider 클래스 구현

구현하기 위해서는 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 키워드/매개변수참고
UriFROM table_nameUri는 table_name이라는 이름의 제공자 테이블에 매핑됩니다.
projectioncol, col, col, ...projection은 각 행에 포함되어야 하는 열의 배열
selectionWhere col = ?selection은 행을 선택하는 기준을 지정한다.
selectionArgs? 자리 표시자를 대체?에 들어갈 값을 배열 형태로 넣는다.
sortOrderORDER 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를 사용하여 다음과 같이 사용하면 된다.

Uri 구조

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 )

  • Scheme : 컨텐트 프로바이더를 사용 한다는 고정적인 스키마 (content)
  • Authority : Content Provider를 구분하는 용도로 사용. 보통 앱패키지명.provider명 으로 생성한다.
  • Path : 데이터의 경로, DB 테이블을 의미한다.
  • Id : DB에서 특정 레코드를 참조할 경우 사용
profile
Yoon's Dev Blog

0개의 댓글