마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

사진전송 멀티파트익셉션관련

0 추천

A액티비티에서 갤러리로 접근 registerforreuslt로 해서 사진을 받아서 아래의 코드를 이용,

사진 업로드하는 api를 콜하는데요 content://로 시작하는걸 멀티파트로 바꿔서 

정상적으로 호출됩니다 

suspend fun postUploadFile(uri: Uri): Flow<String> {
    return flow {
        val requestBody = ContentUriRequestBody(App.instance.contentResolver, uri)
        val body =
            MultipartBody.Part.createFormData("file", requestBody.getFileName(), requestBody)
        emit(service.awaitPostFile(body).string())
    }
}

그러고 나서 B액티비티에서 서버에 보냈던 사진을 다시 받아다가 리사이클러뷰에 뿌려주고

제가 갤러리에서 가져왔던 파일경로를 들고있다가 다시 저 메소드를 통해서 api를 호출하는데 

java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{96393e1 5944:com.패키지명/u0a590} (pid=5944, uid=10590) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs

다음과 같은 익셉션이 떨어집니다 왜그런걸까요.. uri는 content://로 시작하는거 확인했구요 

멀티파트로 변환해서 전송하는데 왜 익셉션이 떨어지는건지 모르겠습니다 

수원통학러 (3,570 포인트) 님이 2022년 5월 20일 질문

1개의 답변

0 추천
간략하게 말씀드리면, 안드로이드가 보안상의 이유로 변경사항이 많이 생겨서, 갤러리 앱같은 Content Provider의 파일 접근 권한이 제한되기 때문이예요. 개발자 문서에 나온 가이드대로 처리하셔야 해요. 이전 질문에서 달아드린 링크와 개발자 문서를 꼼꼼히 살펴보세요.

https://stackoverflow.com/questions/38839879/permission-denial-mediadocumentsprovider
https://developer.android.com/guide/topics/providers/content-provider-basics.html

Edit 님의 경우는 스토리지 접근 관련 문제로 보이구요. FileProvider를 처리해주셔야 할 것 같아요. 이 역시 안드로이드 지속적인 보완강화 사항 중의 하나예요.개발자문서를 참고하세요.

https://developer.android.com/reference/androidx/core/content/FileProvider
spark (224,800 포인트) 님이 2022년 5월 21일 답변
spark님이 2022년 5월 21일 수정
class ContentUriRequestBody(
    private val contentResolver: ContentResolver,
    private val contentUri: Uri
) : RequestBody() {

    private var fileName: String = ""
    fun getFileName() = fileName

    private var size = -1L

    init {
        contentResolver.query(
            contentUri,
            arrayOf(MediaStore.Images.Media.SIZE, MediaStore.Images.Media.DISPLAY_NAME),
            null,
            null,
            null
        )?.use { cursor ->
            if (cursor.moveToFirst()) {
                size = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.SIZE))
                fileName = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME))
            }
        }
    }

    override fun contentLength(): Long = size

    override fun contentType(): MediaType? =
        contentResolver.getType(contentUri)?.toMediaTypeOrNull()

    override fun writeTo(sink: BufferedSink) {
        contentResolver.openInputStream(contentUri)?.source()?.use { source ->
            sink.writeAll(source)
        }
    }

}
<provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths" />
        </provider>
위에 contenturirequestbody는 제가 질문글에 올린 코드구용
프로바이더도 이렇게되있습니다

처음에 즉 A액티비티에서는 제가 질문드린 익셉션이 나오지 않고 정상적으로
갤러리에서 content://로 가져온 uri로 디버깅찍어보면 저기 contentresolover.query가 정상적으로 동작하는데요 그렇게 정상적으로
동작한 content://를 리스트에 갖고있다가, B액티비티에서 다시 저기를 태우면
그때는 익셉션이 떨어지고 contentresolver.query가 null인건지 cursor -> 여기가 안타고 끝나버리더라구요 똑같은 content:// uri에 a액티비티에선 되는데,
갖고있다가 그 uri를 그대로 넘겨줘서 해봤는데 안되는이유가 뭔가요..?
구글링해보고 찾아봐도 스택오버플로에선 갤러리에서 가져왔을때 안된다는 글은 봤지만 저처럼 넘겨서 또 쓰려는 경우가안보여서.. 답글에는 다들 startregisterforresult관련해서만 있더라구요
...