[Android | Kotlin] 갤러리 권한 요청 및 갤러리에서 이미지 불러오기(feat. READ_EXTERNAL_STORAGE로 권한 요청이 안될 때!)

한시삼십사분·2023년 4월 9일
1

Android

목록 보기
3/10
post-thumbnail

🧐 도입

 사이드 프로젝트 진행 중 갤러리에서 이미지를 가져오는 기능을 구현해야했다. 권한 요청하고 갤러리 접근하는 앱은 몇번 만들어봤기 때문에 어렵지 않게 할 수 있을 것이라 생각했는데, 예상치 못하게 막혀 몇 시간을 삽질하고 기록해둬야겠다고 생각했다.

 혹시 나와 같은 문제로 구글을 유랑하고 있는 분들을 위해 결론만 빠르게 스포하자면,
Android 13(API 33)부터 permission.READ_EXTERNAL_STORAGE를
permission.READ_MEDIA_IMAGES
permission.READ_MEDIA_VIDEO
permission.AUDIO
로 파일 형식별 세분화 하여 권한을 요청하는 방식으로 바뀌었다.

https://developer.android.com/about/versions/13/behavior-changes-13?hl=ko
자세한 내용은 해당 글에서 확인 가능

 즉 Android 13 이상의 기기에서는 기존의 방식으로는 권한 요청이 불가능 하다는 것이다. 기기의 Android SDK VERSION이 13(TIRAMISU) 이상 이면 READ__MEDIA_로 그 이하면 READ_EXTERNAL_STORAGE로 권한을 요청하면 해결된다!

💻 설명 및 코드

✅모든 기능과 디자인은 최소화하여 진행했습니다.
✅공식문서와 타 블로그 글을 많이 참고했지만 본인의 판단으로 작성한 코드가 포함되어있기 때문에 틀리거나 비효율적으로 작성된 코드가 있을 수 있습니다.

우선 Manifest에 권한을 추가해준다.
API 버전에 따라 다른 권한을 요청하기 위해 두개 다 추가한다.

  • AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>

    <application
    ...
  • activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btnGallery"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Open Gallery"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

  • MainActivity.kt

import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.activity.result.contract.ActivityResultContracts
import android.Manifest
import android.content.Intent
import android.net.Uri
import android.provider.MediaStore
import java.io.File

class MainActivity : AppCompatActivity() {


    private val TAG = "testTAG"
    private var imageFile = File("")
    private val galleryPermissionLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()){
        if (it){
            val intent = Intent(Intent.ACTION_PICK)
            intent.setDataAndType(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                "image/*"
            )
            imageLauncher.launch(intent)
        }else
            Log.d(TAG, "deny")
    }

    private val imageLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ result ->
            Log.d(TAG, "imageLauncher enter!!")
            if (result.resultCode == RESULT_OK){
                val imageUri = result.data?.data
                imageUri?.let {
                    imageFile = File(getRealPathFromUri(it))
                    Log.d(TAG, imageFile.toString())
                }
            }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.d(TAG, imageFile.toString())
        val btn = findViewById<Button>(R.id.btnGallery)
        btn.setOnClickListener {

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
                galleryPermissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES)
            else
                galleryPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
        }
    }
    
	...
    
}

권한을 요청하는 launcher와 갤러리를 열어 사진을 선택받는 launcher 2개를 선언한다.
이 글에서 가장 중요한 부분은 onCreate 내부에서

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
        galleryPermissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES)
else
        galleryPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)

이부분이다.
SDK 버전을 확인해서 TIRAMISU(SDK 33) 이상일 때는 Manifest.permission.READ_MEDIA_IMAGES로
하위 버전의 SDK일 때는 Manifest.permission.READ_EXTERNAL_STORAGE로 권한을 요청한다.

✍️정리

 블로그를 찾아보며 권한을 요청하는 기능을 구현했는데 기능을 테스트할 때 에뮬레이터에서는 잘 되지만, 본인 실물 기기로 테스트를 하면 아무런 반응도 없었다. 동일 코드였기 때문에 버전문제일 것이라 생각해서 찾아보니 위와 같은 문제로 권한 요청이 안됐던 것이었다.
 몇 시간을 삽질한게 아까워서 바로 글로 남긴다. 역시 안드로이드는 구글의 업데이트가 제일 무섭다...

profile
인간은 망각의 동물이라지만 이건 너무한 거 아니냐고

1개의 댓글

comment-user-thumbnail
2023년 8월 10일

진짜 정말 감사합니다 선생님........ 거의 3일동안 제자리에 머물러 있었는데 덕분에 탈출 성공했습니다ㅠㅠㅠㅠ

답글 달기