본문 바로가기

Android/오류해결

[Android] android.os.FileUriExposedException

글 업데이트 날짜 21.07.19

 

문제상황

Intent 를 이용해 카메라 앱을 찾아 사진을 촬영하여 imageView 에 띄우는 예제를 하던 중에 문제가 발생했다. (인텐트에 파일을 저장할 경로가 Uri 그대로 노출되어있었음)

 

에러

android.os.FileUriExposedException: file:///storage/emulated/0/capture.jpg exposed beyond app through ClipData.Item.getUri() 

 

에러 발생 이유(공식 문서 한국어 번역)

Target SDK 가 android 7.0(24) 이상인 앱의 경우, Android 프레임워크는 앱 외부에서 file:// URI가 노출되는 것을 금지하는 StrictMode API 정책을 적용합니다. 파일 URI가 포함된 인텐트를 보내면 FileUriExposedException 예외와 함께 크래시가 발생합니다.

애플리케이션 간에 파일을 공유하려면 content:// URI를 보내고 이 URI에 대해 임시 액세스 권한을 부여해야 합니다. 이 권한을 가장 쉽게 부여하는 방법은 FileProvider 클래스를 사용하는 방법입니다. 권한과 파일 공유에 대한 자세한 내용은 파일 공유를 참조하세요.

https://developer.android.com/about/versions/nougat/android-7.0-changes#sharing-files

 

 

해결 방법

1. FileProvider 정의하기

/res 에 xml 이라는 디렉토리를 생성하고, file_paths.xml 파일을 추가한다.
file path 에는 위에 에러 구문에 굵은 활자로 표시한 부분처럼 원하는 경로를 넣어주면 된다.

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_image_dir" path="."/>
    <cache-path name="my_docs_dir" path="docs/"/>
    ...
    <external-path name="이름" path="원하는 상위 디렉토리 경로"/>
    <external-files-path name="이름" path="동일"/>
    <external-media-path name="이름" path="동일"/> <!-- API 21+ -->
    ...
</paths>

캐시 디렉토리, 외부 저장소 등등 여러가지 옵션이 있다. 참고

 

그리고 Manifest 에 가서 아래와 같이 Provider 를 추가한다.

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${APPLICATION_ID}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

 

 

2. Uri.fromFile() 코드 변경

val file = File(
	Context.getFilesDir(), // Uri 를 만들 파일이 위치한 디렉토리
    	"capture.png"
)

val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
	FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.fileProvider", file)
} else {
	Uri.fromFile(file)
}

 

이렇게 만들어진 uri 를 살펴보면 아래와 같다. 밑줄 친 부분이 해당 파일을 저장했던 경로와 파일명이다. (테스트는 external-path 로 설정)

content://com.myapplication.fileProvider/my_image_dir/Android/data/com.myapplication/cache/capture.png

 

path 선언할 때 file-path 로 만들어놓으면 아래와 같은 경로에 저장한 File 에서 uri 를 가져올 때 오류가 발생한다..

File(ContextCompat.getExternalCacheDirs(context)[0]), "capture.png")

java.lang.IllegalArgumentException: Failed to find configured root that contains
/storage/emulated/0/Android/data/myapplication/cache/capture.png

 

관련한 내용의 포스팅이 있었는데 해결책을 찾을 수 없었다. ㅜㅜ 파일프로바이더를 생성한 경로가 다르다는 차이점 뿐인데 왜그런지 잘 모르겠다. (file-path VS external-file-path)