주소록 팀과제를 진행하며 제일 애를 먹었던 부분에 대해 트러블슈팅 경험을 정리해보겠다.
이번에 구현한 앱의 경우 갤러리에서 이미지를 불러와 이미지의 URI 재사용하는 부분이 있었는데, 처음에 갤러리에서 이미지를 불러와서 뷰에 세팅하고 이미지 URI를 저장하는 것까지는 문제가 없었다.
리스트에 이미지의 URI를 저장하여 해당 URI를 다른 화면에서 재사용하려고 하니 이미지뷰에 이미지가 뜨지 않고 아래와 같은 에러가 발생하는 것이다.
처음에는 코드 문제인줄 알고 코드를 계속 수정해봤지만 문제는 해결 되지 않았다. 해결법을 찾지 못해 튜터님께 여쭤보니보통 이미지를 불러오면 데이터베이스에 저장하고 데이터베이스에서 가져와서 사용하는 방식으로 진행하는게 맞는데, 우리의 경우 아직 데이터 베이스 사용을 안하고 앱 내에서 URI를 저장해서 재사용하다보니 이러한 문제가 발생하는 것 같다
고 하셨고, 구글에서 처음 URI를 가지고 오는 것은 허용하지만 그 다음부터는경로를 제어하지 못해 막아놓는 것 같다
고 하셨다.
그래서 해결 방법을 제시해 주셨는데,URI를 저장해서 재사용하지 말고 디바이스에 있는 파일을 직접 경로를 가지고 온다
던가 URI를 가지고 패스를 알 수 있는데 패스를 따서 저장하는 방식
으로 수정해보라고 하셔서 그렇게 진행 했더니 해당 문제를 해결할 수 있었다 ! 🎆
Load failed for content://com.google.android.apps.photos.contentprovider/-1/1/content%3A%2F%2Fmedia%2Fexternal%2Fimages%2Fmedia%2F1000000037/ORIGINAL/NONE/image%2Fjpeg/1874459411 with size [138x138]
class com.bumptech.glide.load.engine.GlideException: Failed to load resource
There was 1 root cause:
java.lang.SecurityException(Permission Denial: opening provider com.google.android.apps.photos.contentprovider.impl.MediaContentProvider from ProcessRecord{8f0c4bb 19464:com.example.colorcontacts/u0a169} (pid=19464, uid=10169) that is not exported from UID 10116)
call GlideException#logRootCauses(String) for more detail
Cause (1 of 1): class java.lang.SecurityException: Permission Denial: opening provider com.google.android.apps.photos.contentprovider.impl.MediaContentProvider from ProcessRecord{8f0c4bb 19464:com.example.colorcontacts/u0a169} (pid=19464, uid=10169) that is not exported from UID 10116
2024-01-19 10:17:25.559 19464-19464 Glide com.example.colorcontacts I Root cause (1 of 1)
java.lang.SecurityException: Permission Denial: opening provider com.google.android.apps.photos.contentprovider.impl.MediaContentProvider from ProcessRecord{8f0c4bb 19464:com.example.colorcontacts/u0a169} (pid=19464, uid=10169) that is not exported from UID 10116
at android.os.Parcel.createExceptionOrNull(Parcel.java:3011)
at android.os.Parcel.createException(Parcel.java:2995)
at android.os.Parcel.readException(Parcel.java:2978)
at android.os.Parcel.readException(Parcel.java:2920)
at android.app.IActivityManager$Stub$Proxy.getContentProvider(IActivityManager.java:5224)
at android.app.ActivityThread.acquireProvider(ActivityThread.java:7007)
at android.app.ContextImpl$ApplicationContentResolver.acquireUnstableProvider(ContextImpl.java:3420)
at android.content.ContentResolver.acquireUnstableProvider(ContentResolver.java:2526)
at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:2011)
at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1842)
at android.content.ContentResolver.openInputStream(ContentResolver.java:1518)
at com.bumptech.glide.load.data.StreamLocalUriFetcher.loadResourceFromUri(StreamLocalUriFetcher.java:74)
at com.bumptech.glide.load.data.StreamLocalUriFetcher.loadResource(StreamLocalUriFetcher.java:50)
at com.bumptech.glide.load.data.StreamLocalUriFetcher.loadResource(StreamLocalUriFetcher.java:13)
at com.bumptech.glide.load.data.LocalUriFetcher.loadData(LocalUriFetcher.java:44)
at com.bumptech.glide.load.engine.SourceGenerator.startNextLoad(SourceGenerator.java:70)
at com.bumptech.glide.load.engine.SourceGenerator.startNext(SourceGenerator.java:63)
at com.bumptech.glide.load.engine.DecodeJob.runGenerators(DecodeJob.java:311)
at com.bumptech.glide.load.engine.DecodeJob.runWrapped(DecodeJob.java:280)
at com.bumptech.glide.load.engine.DecodeJob.run(DecodeJob.java:235)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
at java.lang.Thread.run(Thread.java:1012)
at com.bumptech.glide.load.engine.executor.GlideExecutor$DefaultThreadFactory$1.run(GlideExecutor.java:393)
Caused by: android.os.RemoteException: Remote stack trace:
at com.android.server.am.ContentProviderHelper.checkAssociationAndPermissionLocked(ContentProviderHelper.java:651)
at com.android.server.am.ContentProviderHelper.getContentProviderImpl(ContentProviderHelper.java:264)
at com.android.server.am.ContentProviderHelper.getContentProvider(ContentProviderHelper.java:140)
at com.android.server.am.ActivityManagerService.getContentProvider(ActivityManagerService.java:6470)
at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:2395)
dependencies {
...
// add
implementation("com.github.bumptech.glide:glide:4.12.0")
annotationProcessor("com.github.bumptech.glide:compiler:4.12.0")
}
fun ImageView.setFavoriteTag(uri: Uri) {
clipToOutline = true
Glide.with(context)
.load(uri)
.into(this)
}
private fun initView() {
// 갤러리에서 이미지를 볼러오는 결과에 대한 처리
galleryResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
// 이미지 uri
selectedImage = data?.data!!
loadTagImage(selectedImage)
}
}
}
// 가져온 이미지 Uri를 Glide를 사용하여 이미지뷰에 세팅
private fun loadTagImage(uri: Uri) {
binding.ivRegisterTagImage.setFavoriteTag(file)
}
// 이미지 선택을 위해 갤러리를 불러오는 인텐트를 생성한다.
private fun openGallery() {
val intent =
Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
galleryResultLauncher.launch(intent)
}
dependencies {
...
// add
implementation("io.coil-kt:coil:1.4.0")
}
object FilePath {
fun Context.absolutelyPath(path: Uri): String? {
val proj: Array<String> = arrayOf(MediaStore.Images.Media.DATA)
val c: Cursor? = contentResolver.query(path, proj, null, null, null)
val index = c?.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
c?.moveToFirst()
if (index == null) return null
return c.getString(index)
}
}
private fun initView() {
// 갤러리에서 이미지를 볼러오는 결과에 대한 처리
galleryResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
// 이미지 uri
selectedImage = data?.data!!
// 이미지의 실제 경로를 저장하기 위한 처리
val path = requireActivity().absolutelyPath(selectedImage!!)
// 문자열 형식인 파일 경로를 파일 형식으로 변환
val file = File(path)
loadTagImage(file)
}
}
}
// 이미지 경로를 가지고 coil library를 사용하여 이미지뷰에 세팅
private fun loadTagImage(file: File) {
binding.ivRegisterTagImage.load(file)
}
// 이미지 선택을 위해 갤러리를 불러오는 인텐트를 생성한다.
private fun openGallery() {
val intent =
Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
galleryResultLauncher.launch(intent)
}