게시물에 첨부된 파일을 다운로드하고 실행할 때, FileUriExposeException이 발생하면서 앱이 죽는다.
안드로이드 7.0 변경된 사항들을 보니, 앱과 앱간의 파일 공유에 새로운 정책이 반영되었다.
앱 외부에서 file://URI를 참조하면 FileUriExposeException이 발생한다.
앱 사이에 파일을 공유하려면 반드시 content://URI를 사용해야하고, 이 URI에 대한 임시 접근권한을 줘야한다.
FileProvider가 그 권한을 가장 쉽게 부여하는 방법이다.
FileProvider란?
앱과 앱 사이의 안전한 파일 공유를 가능하게하는, ContentProvider의 특별한 하위클래스다!
file://URI 대신 content://URI를 사용한다.
content URI는 임시 접근권한을 이용하여 read && write 접근을 허용한다.
content URI를 포함한 인텐트를 생성할때, client 앱에 content URI를 전송하기 위해, 권한을 추가하는 Intent.setFlags()를 호출할 수 있다.
이러한 권한은, 액티비티가 위치한 스택이 활성화되어있는 동안, client 앱에서 사용가능하다.
content URI가 제공하는 보안 수준이 높아지면서 FileProvider는 안드로이드 보안 인프라의 핵심 요소로 자리잡았다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <manifest> ... <application> ... <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.mydomain.fileprovider" android:exported="false" android:grantUriPermissions="true"> ... </provider> ... </application> </manifest> | cs |
1 2 3 4 5 6 7 | <!-- xml/file_paths --> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="name" path="path"/> : 내부저장소 / Context.getFilesDir() <cache-path name="name" path="path" /> : 내부저장소 / getCacheDir() <external-path name="name" path="path" /> : 외부저장소 / Environment.getexternalStorageDirectory() <external-files-path name="name" path="path" /> : 외부저장소 / Context.getExternalFilesDir() <external-cache-path name="name" path="path" /> : 외부캐시영역 / Context.getExternalCacheDir() </paths> | cs |
1 2 3 4 5 6 7 8 9 | <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.mydomain.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> | cs |
3. Retrieving the Content URI for a File
content URI를 이용해서 다른 앱에게 파일을 공유하려면, content URI를 생성해야한다.
content URI를 생성하려면, 기존 파일에 대한 새로운 File을 생성하고, 그 File을 getUriForFile()에 넘긴다.
getUriForFile()이 return한 content URI를 인텐트에 싣어서 다른 앱에서 보낼 수 있다.
content URI를 받은 client 앱은, ParcelFileDescriptor를 얻기위해 ContentResolver.openFileDescripter를 호출함으로써, 그 파일을 열어볼 수 있고 접근할 수 있다.
4. Granting Temporary Permissions to a URI
getUriForFile()이 return한 content URI에 대한 접근권한을 승인하려면, 아래를 수행해야 한다.
Context.grantUriPermission() 호출하기
- setData()를 호출해서 인텐트에 content URI를 싣는다.
- 다음으로, Intent.setFlags()를 호출해서 mode_flags 파라미터 값을 설정한다.
- 마지막으로, (setResult()를 이용해서) 다른앱에 인텐트를 보낸다. 수신한 액티비티를 포함한 스택이 활성화되어 있는 동안 권한들이 승인된다. 그 스택이 finish되면 그 권한들도 자동으로 제거된다. client 앱에서 한 액티비티에 승인된 권한들은 자동으로 그 앱의 다른 컴포넌트들에도 확장된다.
5. Serving a Content URI to Another App
client 앱에 파일에 대한 content URI를 전달하는 다양한 방법이 존재한다. 한가지 일반적인 방법은 client 앱이 startActivityResult()를 호출하여 나의 앱을 실행하는것이다. 그러면 내 앱은 즉시 client 앱이나 유저가 선택한 파일을 허용하는 현재 UI에 content URI를 return할 수 있다.
유저가 선택한 파일을 허용하는 현재 UI에 content URI를 전달하는 경우, 유저가 파일을 선택하면 내 앱은 그 파일의 content URI를 return 할 수 있다. 위 두가지 경우에서 내 앱은 setResult()를 통해 전송된 인텐트의 content URI를 return 한다.
또한 ClipData 객체에 content URI를 싣을 수 있고, 그 객체를 인텐트에 추가하여 client 앱에 보낼 수 있다. 이렇게 하려면 Intent.setClipData()를 호출한다. 이러한 접근을 할 때 인텐트에 여러 ClipData 객체를 싣을 수 있다. Intent.setFlags()를 호출할 때 동일한 권한들이 모든 content URI에 적용된다.
'Android > Bottom-Up' 카테고리의 다른 글
Intent :: setFlags() VS addFlags() (0) | 2017.04.18 |
---|---|
Bitmap 또는 BitmapFactory.decodeFile 이 null을 리턴할 때 (0) | 2017.02.15 |
아마도 이건 자바 3바퀴 (0) | 2015.07.06 |
What is the "Gradle"? (0) | 2015.06.08 |