[안드로이드스튜디오_문화][Content Provider]

기말 지하기포·2023년 12월 8일
0

#ContentProvider AndroidDeveloper 공식문서 링크
=>https://developer.android.com/guide/topics/providers/content-provider-basics?hl=ko

ContentResolver

-Android Systemd에서 앱들이 데이터를 관리하고 다른 앱의 데이터에 접근 할 수 있게 도와준다. 앱~ContentProvider의 인터페이스 역할을 하기 때문에, 데이터를 캡슐화하고 데이터에 대한 접근을 관리하는 ContentProvider를 ContentResolver를 통해서만 접근 할 수 있다.

-ContentResolver는 데이터에 접근하기 위해 URI를 사용하는데 이는 각각의 ContentProvider가 고유한 URI를 가지고 있기 때문이다.

-query() getType() insert() delete() update() 메서드를 사용해서 데이터를 관리한다. ContentResolver는 Cursor 객체를 반환하는데, Cursor은 이 메서드들을 사용해서 데이터를 사용 할 수 있다.

Content Provider

공식문서 설명

-ContentProvider는 안드로이드 시스템에서 앱이 다른 앱과 데이터를 주고 받을 때 사용하는 것이다. 다른 앱의 파일 등의 특정 리소스에 대한 접근 할 수 있다는 것이야.

-구글에서 ContentProvider를 사용하도록 권장하는 케이스는 [첫째, 다른 앱에서 기존 앱의 ContentProvider에 접근하고 싶을 때]와 [둘째, 내 애플리케이션에서 새로운 ContentProvider를 생성하여 다른 애플리케이션과 데이터를 공유하고 싶을 때] 이렇게 두가지 케이스의 경우에 ContentProvider를 사용하도록 권장하고 있다.

-이 글에서는 다른 앱의 ContentProvider에 접근해서 데이터를 가져오는 것에 대해서 알아보도록 하겠다.

-아래의 사진처럼 ContentProvider는 다른 데이터 저장소들과 다른 컴포넌트 등을 연결해주는 역할을 해주고있다.

다른 앱애서 기존앱의 ContentProvider에 접근하고 싶을 때

-내 휴대폰의 갤러리 , 연락처 등등에는 ContentProvider가 있어서 이 ContentProvider에 접근해서 내 휴대폰에 설치되어 있는 앱이 안드로이드 기본 앱에 접근해서 데이터를 가져 올 수 있다.

1.가져오고자 하는 데이터에 대한 접근 권한을 허용하기

-가져오고자 하는 데이터에 접근 권한을 허용 해줘야 한다. 안드로이드 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";

2. contentResolver.query()

-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를 생성하여 다른 애플리케이션과 데이터를 공유하고 싶을 때

-내 앱에 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 사용법에 대해서 알아보았다.

profile
포기하지 말기

0개의 댓글