#ContentProvider AndroidDeveloper 공식문서 링크
=>https://developer.android.com/guide/topics/providers/content-provider-basics?hl=ko
-Android Systemd에서 앱들이 데이터를 관리하고 다른 앱의 데이터에 접근 할 수 있게 도와준다. 앱~ContentProvider의 인터페이스 역할을 하기 때문에, 데이터를 캡슐화하고 데이터에 대한 접근을 관리하는 ContentProvider를 ContentResolver를 통해서만 접근 할 수 있다.
-ContentResolver는 데이터에 접근하기 위해 URI를 사용하는데 이는 각각의 ContentProvider가 고유한 URI를 가지고 있기 때문이다.
-query() getType() insert() delete() update() 메서드를 사용해서 데이터를 관리한다. ContentResolver는 Cursor 객체를 반환하는데, Cursor은 이 메서드들을 사용해서 데이터를 사용 할 수 있다.
-ContentProvider는 안드로이드 시스템에서 앱이 다른 앱과 데이터를 주고 받을 때 사용하는 것이다. 다른 앱의 파일 등의 특정 리소스에 대한 접근 할 수 있다는 것이야.
-구글에서 ContentProvider를 사용하도록 권장하는 케이스는 [첫째, 다른 앱에서 기존 앱의 ContentProvider에 접근하고 싶을 때]와 [둘째, 내 애플리케이션에서 새로운 ContentProvider를 생성하여 다른 애플리케이션과 데이터를 공유하고 싶을 때] 이렇게 두가지 케이스의 경우에 ContentProvider를 사용하도록 권장하고 있다.
-이 글에서는 다른 앱의 ContentProvider에 접근해서 데이터를 가져오는 것에 대해서 알아보도록 하겠다.
-아래의 사진처럼 ContentProvider는 다른 데이터 저장소들과 다른 컴포넌트 등을 연결해주는 역할을 해주고있다.
-내 휴대폰의 갤러리 , 연락처 등등에는 ContentProvider가 있어서 이 ContentProvider에 접근해서 내 휴대폰에 설치되어 있는 앱이 안드로이드 기본 앱에 접근해서 데이터를 가져 올 수 있다.
-가져오고자 하는 데이터에 접근 권한을 허용 해줘야 한다. 안드로이드 13 이전을 타겟팅하는 앱은 단순히 "READ_EXTERNAL_STORAGE" 권한만 허용하면 되었지만 , 안드로이드 13 부터는 다른 앱에서 만든 미디어 파일에 엑세스해야 되는 경우에 아래와 같이 세분화된 미디어 권한을 개별적으로 요청해야 한다. 허용 하는 방법은 아래의 코드를 AndroidManifest.xml 파일에 작성해주어야 한다.
<uses-permission android:name="android.permission.요청 권한 이름" />
-아래 코드는 접근 권한에 대한 내용들이다.
public static final String READ_LOGS = "android.permission.READ_LOGS";
public static final String READ_MEDIA_AUDIO = "android.permission.READ_MEDIA_AUDIO";
public static final String READ_MEDIA_IMAGES = "android.permission.READ_MEDIA_IMAGES";
public static final String READ_MEDIA_VIDEO = "android.permission.READ_MEDIA_VIDEO";
public static final String READ_MEDIA_VISUAL_USER_SELECTED = "android.permission.READ_MEDIA_VISUAL_USER_SELECTED";
public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY";
public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
public static final String READ_PRECISE_PHONE_STATE = "android.permission.READ_PRECISE_PHONE_STATE";
public static final String READ_SMS = "android.permission.READ_SMS";
public static final String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS";
public static final String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS";
public static final String READ_VOICEMAIL = "com.android.voicemail.permission.READ_VOICEMAIL";
-contentResolver.query()의 parameter에 있는 [uri , projection , selection , selectionArgs , sortOrder] 이 다섯가지를 설정해주어야 한다.
-uri : contentProvider가 관리하는 데이터의 위치를 나타낸다.
-projection : 조회할 컬럼의 배열을 의미한다. 아래처럼 다양한 것들에 접근 할 수 있다.
val projection = arrayOf(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
)
val projection = arrayOf(
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.DISPLAY_NAME,
MediaStore.Audio.Media.DURATION // 오디오 파일의 재생 시간
)
val projection = arrayOf(
// 이미지 파일에 할당된 ID
MediaStore.Images.Media._ID,
// 이미지 파일의 이름
MediaStore.Images.Media.DISPLAY_NAME,
)
-selection : SQL의 'WHERE' 절에 해당하는 문자열이며, 특정 조건에 맞는 데이터만 조회하고 싶을 때 사용한다.
-selectionArgs : selection 문자열 내에 ?가 포함되어 있을 때 ?를 대체할 값들의 배열이다.
-sortOrder : 조회결과의 정렬 순서를 지정하는 문자열이다. SQL의 'ORDER BY' 절에 해당한다. 만약 이 parameter에 null 값을 넣은다면 정렬하지 않는다. (DESC : 내림차순) (ASC : 오름차순)
이렇게 조회된 결과는 Cursor 객체로 반환되며 이 Cusor을 사용하여 실제 데이터에 접근 할 수 있으며 , Cursor 객체를 사용한 후에는 반드시 Cursor를 닫아 리소스 해제 작업을 진행해주어야 한다.
-아래는 ContentProvider를 통해서 사진을 가져오는 예시 코드의 일부를 발췌한 것이다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 권한 요청
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.READ_MEDIA_IMAGES),
0
)
}
val projection = arrayOf(
// 이미지 파일에 할당된 ID
MediaStore.Images.Media._ID,
// 이미지 파일의 이름
MediaStore.Images.Media.DISPLAY_NAME,
)
val millisYesterday = Calendar.getInstance().apply {
add(Calendar.DAY_OF_YEAR, -2)
}.timeInMillis
// ?가 설정되어 있으므로 selectionArgs의 값으로 대체한다.
val selection = "${MediaStore.Images.Media.DATE_TAKEN} >= ?"
// selection에 들어갈 값이다.
val selectionArgs = arrayOf(millisYesterday.toString())
// 정렬 순서를 오름차순으로 설정
val sortOrder = "${MediaStore.Images.Media.DATE_TAKEN} ASC"
contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
selectionArgs,
sortOrder
)?.use { cursor ->
val idColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID)
val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
val images = mutableListOf<Image>()
while(cursor.moveToNext()) {
// 조회된 컬럼 인덱스를 사용하여 현재 행에서 이미지의 ID와 이름을 추출한다.
val id = cursor.getLong(idColumn)
val name = cursor.getString(nameColumn)
// 추출한 이미지 ID를 사용하여 해당 이미지의 완전한 URI를 생성한 후 이 URI를 이미지에 접근할 때 사용한다.
val uri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
)
images.add(Image(id , name , uri))
}
viewModel.updateImages(images)
}
-
-내 앱에 ContentProvider를 생성하여 다른 앱에서 내가 빌드한 앱의 데이터에 접근 할 수 있게 할 수 있다.
-내 앱의 AndroidManifest.xml 파일에 아래와 같이 선언을 해주어야 한다. 이렇게 구현을 하면 다른 앱에서 ContentResolver를 사용해서 MyContentProvider에 접근 할 수 있다.
<provider
android:name=".MyContentProvider"
android:authorities="com.company.velogworkmanager.mycontentprovider"
android:exported="true"/>
-아래는 ContentProvider()를 상속받은 후 implement한 코드이다.
class MyContentProvider : ContentProvider() {
override fun onCreate(): Boolean {
TODO("Not yet implemented")
}
override fun query(
p0: Uri,
p1: Array<out String>?,
p2: String?,
p3: Array<out String>?,
p4: String?
): Cursor? {
TODO("Not yet implemented")
}
override fun getType(p0: Uri): String? {
TODO("Not yet implemented")
}
override fun insert(p0: Uri, p1: ContentValues?): Uri? {
TODO("Not yet implemented")
}
override fun delete(p0: Uri, p1: String?, p2: Array<out String>?): Int {
TODO("Not yet implemented")
}
override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int {
TODO("Not yet implemented")
}
}
-그렇다면 어떻게 다른 앱에서 MyContentProvider에 접근 할 수 있을까? 아래코드를 보자. 아래 코드는 MyContentProvider에게 접근하고 싶어하는 앱에게 MycontentProvider가 관리하는 데이터의 위치를 알려주기 위해서 위치정보를 contentUri에게 넣어준것이다. 이 정보를 안드로이드 시스템의 ContentResolver가 사용해서 컨텐츠 프로바이더에 접근 할 수 있다. 이렇게 접근하면 .query()의 uri 자리에 넣을 수 있다. 그러면 다른 앱에서 MyContentProvider에 접근 할 수 있는 권한을 얻었다고 볼 수 있다.
val contentUri: Uri = Uri.parse("content://com.company.velogworkmanager.mycontentprovider")
-아래의 코드는 cursor를 활용해서 데이터를 조회해서 가져 올 수 있다. 보통 안드로이드 기본앱에서 정보를 가져오려는 것이 아니면 , 같은 회사에서 만든 앱에 있는 데이터를 가져올 것이므로 해당앱의 코드를 보고 use {}의 코드블락을 작성해주면 될 것 같다.
contentResolver.query(
contentUri,
projection,
selection,
selectionArgs,
sortOrder
)?.use { cursor ->
}
-이상으로 ContentProvider 사용법에 대해서 알아보았다.