사이드 프로젝트 진행 중 갤러리에서 이미지를 가져오는 기능을 구현해야했다. 권한 요청하고 갤러리 접근하는 앱은 몇번 만들어봤기 때문에 어렵지 않게 할 수 있을 것이라 생각했는데, 예상치 못하게 막혀 몇 시간을 삽질하고 기록해둬야겠다고 생각했다.
혹시 나와 같은 문제로 구글을 유랑하고 있는 분들을 위해 결론만 빠르게 스포하자면,
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 버전에 따라 다른 권한을 요청하기 위해 두개 다 추가한다.
<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
...
<?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>
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로 권한을 요청한다.
블로그를 찾아보며 권한을 요청하는 기능을 구현했는데 기능을 테스트할 때 에뮬레이터에서는 잘 되지만, 본인 실물 기기로 테스트를 하면 아무런 반응도 없었다. 동일 코드였기 때문에 버전문제일 것이라 생각해서 찾아보니 위와 같은 문제로 권한 요청이 안됐던 것이었다.
몇 시간을 삽질한게 아까워서 바로 글로 남긴다. 역시 안드로이드는 구글의 업데이트가 제일 무섭다...
진짜 정말 감사합니다 선생님........ 거의 3일동안 제자리에 머물러 있었는데 덕분에 탈출 성공했습니다ㅠㅠㅠㅠ