-
FileProvider 는 ContentProvider 의 subclass 로 secure 한 file share 를 관장한다.
이를 통하면 file:/// 형태의 uri 대신 content:// 형태의 uri 를 사용하게 된다.
-
content URI 는 read, write access 를 임시 permission 으로 부여할 수 있다.
content uri 를 가진 Intent 를 통해 타앱에 정보를 보낼 때에도 Intent.setFlags() 를 통해 permission 을 부여할 수 있다.
이 permission 은 전달받는 Activity 가 stack 에 존재할 때 가능하다.
Intent 가 Service 로 전달될 때에는 해당 Service 가 running 하는 동안만 부여된 권한이 유지된다.
-
file:/// uri 를 사용할 때에는 file system 에서 해당 permission 을 바꾸어야 한다.
이렇게 되면 모든 app 에 permission 을 주는 것과 같고, 이를 원복시킬 때까지 누구든 이 파일에 접근할 수 있기 때문에 보안상 좋은 방법이 아니다.
(그래서 file provider 라는 녀석이 나왔다.)
Defining a FileProvider
-
FileProvider 의 기본 기능은 file 에 대해 content URI 를 생성하는 것이다.
이는 FileProvider 를 app 의 manifest 에 xml 형태로 포함시키면 된다.
android:name 에는 androidx.core.content.FileProvider 를 사용하면 된다.
android:authorities 에는 <youtDomain>.fileprovider 의 형태로 주면 된다.
android:exported 는 false 로 준다. public 일 필요가 없다.
android:grantUriPermissions 에는 true 를 준다. 그래야 temp 권한을 획득할 수 있다.
<manifest>
...
<application>
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.mydomain.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
...
</provider>
...
</application>
</manifest>
-
FileProvider 의 기본 기능을 확장하고 싶다면, FileProvider 를 상속하고 android:name 에 fully qualified name 을 적어주면 된다.
Specifying Available Files
-
FileProvider 는 미리 설정한 폴더에 있는 file 들에 대해서만 URI 를 생성할 수 있다.
폴더 설정은 storage area 와 path 를 xml 에 적어주는 것으로 가능하다. 이는 <paths> element 를 통해 한다.
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="images/"/>
...
</paths>
name attribute 는 Uri path segment 로 실제 path 를 hide 하는 효과를 지닌다.
path attribute 는 실제 path 를 명시해준다. 이 녀석은 directory 이지 특정 파일이 아니다.
위 xml 은 private file 영역(Context.getFilesDir())의 images/ 라는 디렉토리에 한정이 된다.
paths 태그 안의 files-path 는 복수개가 올 수 있다.
-
<files-path …> 는 Context.getFilesDir() 에 해당되는 영역을 말한다.
<cache-path …> 는 Context.getCacheDir() 에 해당되는 영역을 말한다.
<external-path …> 는 Environment.getExternalStorageDirectory() 에 해당되는 영역을 말한다.
<external-files-path …> 는 Context.getExternalFilesDir(String) 에 해당되는 영역이다.
<external-cache-path …> 는 Context.getExternalCacheDir() 에 해당되는 영역이다.
<external-media-path …> 는 Context.getExternalMediaDirs() 에 해당되는 영역이다. 이건 API 21+ 단말에만 유효하다.
-
이렇게 정의한 file path 를 FileProvider 의 meta-data 에 넣어준다.
android:name 은 android.support.FILE_PROVIDER_PATHS 로 정의해주고,
android:resource 에는 정의한 xml 파일을 연결해준다.
<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>
Generating the Content URI for a File
-
다른 앱과 content URI 를 통해 file 을 share 하기 위해서는, app 은 content URI 를 생성해야만 한다.
먼저 공유하려는 File 객체를 만든다. 그리고 FileProvider.getUriForFile() 에 File 을 넘기고 Uri 를 받는다. 이 Uri 가 content URI 이다.
이 녀석을 Intent 에 실어서 보내면 된다.
File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
return 된 uri 는 content://authority/pathName/fileName 의 형태가 된다.
-
이 FileProvider 를 통한 contentUri 를 받은 앱에서는 ContentResolver.openFileDescriptor 를 통해 ParcelFileDescriptor 를 받아올 수 있다.
Granting Temporary Permissions to a URI
-
getUriForFile() 을 통해 얻어진 contentURI 에 접근 권한을 부여하기 위해서는 다음 중 하나를 만족하면 된다.
1. Context.grantUriPermission(package, Uri, mode_flags) 를 호출한다.
이 임시 접근 권한은 package 를 한정시킬 수 있다.
flag 에는 FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION 둘 중 하나 혹은 둘 다 들어갈 수 있다.
이 permission 은 revokeUriPermission() 이 호출되거나 단말이 reboot 되기 전까지 유효하다.
2. Intent 에 setData 를 통해 uri 를 설정하고, Intent.setFlags() 를 통해 권한을 부여한다.
권한에는 FLAG_GRANT_READ_URI_PERMISSION 이나 FLAG_GRANT_WRITE_URI_PERMISSION 둘 중 하나 혹은 둘 다 넣어준다.
Intent 의 전달은 많은 경우 setResult() 를 통해 한다.
Intent 를 통해 전달된 권한 획득은 받는 Activity 가 stack 에 active 로 있는 동안 유효하다.
stack 이 종료되면, permission 은 자동으로 제거된다.
permission grant 된 activity 가 다른 component 를 호출하여 해당 URI 를 넘기는 경우 자동으로 권한이 승계된다.
Serving a Content URI to Another App
-
file 에 대한 content URI 를 다른 앱에 제공하는 경우는 여러 가지가 있다.
일반적인 방법은 중 하나는 startActivityForResult() 를 호출하여, FileProvider 로 생성된 URI 를 담은 intent 를 setResult() 를 통해 받는 것이다.
-
contentURI 를 ClipData 에 넣고, 이를 Intent 를 통해 전달하는 방법도 있다.
Intent.setClipData() API 가 활용되며, 이 경우 여러개의 ClipData 를 Intent 에 넣을 수 있다는 장점이 있다.
Intent.setFlags() 를 호출할 때 모든 content URI 에 권한이 적용된다.
( Intent.setClipData 는 API 16 부터 제공된다. )
cf)
setClipData(ClipData clip)
-
ClipData 는 Intent 에 들어가는 정보이며, intent matching 등에는 쓰이지 않는다.
이 녀석은 의미적으로는 extra 와 같다고 보면 된다.
extra 대신 이 녀석을 쓰는 주된 이유는 FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION 이 clip data 에 들어있는 uri 와 호환되기 때문이다.
여러개의 content:// scheme 을 가진 uri 를 공유할 때 유효하다.
-
만약 ClipData 가 포함하고 있는 정보 자체가 Intent 라면, 그곳에 정의된 grant flag 는 무시된다.
오직 top-level 의 main intent flag 만이 적용된다.
-
ClipData 안에 있는 MIME type, label, icon 은 Intent 에 의해 직접적으로 사용되지 않는다.
App 판단시 일반적으로 main intent 자체의 MIME type 에 의존하며, ClipData 의 정보를 참조하지 않는다.
일반적인 관례는, ClipData 를 사용하는 경우 MIME type 을 "*/*" 를 사용한다.
cf2)
https://developer.android.com/reference/android/content/ClipData
ClipData
-
Parcelable 하며, clipboard 의 clipped data 를 나타낸다.
ClipData 는 1개 이상의 item 을 가지고 있는 복잡한 type 이며, 각각은 data item 을 가지고 있다.
user 에게 보여주기 위해 label 도 가지고 있다.
-
ClipData 는 ClipDescription 이라는 것을 가지고 있는데, 이는 clip 에 대한 meta data 를 가지고 있다.
getDescription, getMimeType(int) 은 반드시 올바른 MIME type 을 전달해야 한다.
제대로 된 MIME type 을 가진 clip 을 만들기 위해 newPlainText(CharSequence, CharSequence), newUri(ContentResolver, CharSequence, Uri), newIntent(ChrarSequence, Intent) 를 사용하는 것이 추천된다.
각각의 item 은 다음 3개의 data class 중 하나에 속한다.
simple text CharSequence, single Intent object, Uri
참고자료
'프로그래밍 놀이터 > 안드로이드, Java' 카테고리의 다른 글
[android] Create views into a database - Room 에 대해 알아보자 (0) | 2021.05.02 |
---|---|
[android] Define relationships between objects - Room 에 대해 알아보자 (0) | 2021.05.01 |
[android] WebChromeClient 의 file upload (1) | 2021.01.29 |
[android] 국제화 text style 입히기 (0) | 2021.01.27 |
[android] finishAffinity() 와 finishAndRemoveTask() 에 대하여 with 실험 (0) | 2021.01.27 |
댓글