'2020/09'에 해당되는 글 2건

  1. 2020.09.08 파일공유 #2 Storage Access Framework 1
  2. 2020.09.08 파일공유 #1
Android2020. 9. 8. 11:09

Android 10(Q, 29 level)으로 TargetApi로 설정한 앱이 Android10이상의 단말에 설치된 경우 외부저장소에 대해 Scoped storage모드로 동작한다.

 

Scoped Stroage

 

Android10 타겟에 Android10 단말에서 동작한다.


외부저장소의 공용파일공간이 모두 사라지고 개별앱공간이 샌드박스로 격리되어 제공되며 다른앱이 접근 불가하다

 

MediaStore는 내가 추가한 파일을 읽거나 쓰는데 권한없이 사용 가능하나. 다른앱의 파일을 읽기 위해서는 권한이 필요하다.

 

파일경로(file:///) 만으로 읽고 쓰기가 불가(FileNotFound혹은 권한이 없다는 에러)하다. FileProvider 혹은 시스템 파일 선택기를 통해서 사용자가 직접 파일을 선택한 이후 에야 접근이 가능하다.


Environment.getExternalStorageDirectory()는 deprecated되었다.

 

타앱에 파일을 전달할때 파일경로로 전달불가하며 FileProvider로 ContentUri를 만들고 공유 받을 앱에 임시로 URI접근권한을 허용하는 방법을 사용한다(이전 포스팅 참조)

 

공용저장공간은 /sdcard자체가 공용공간이었으나 이제는 MediaStore와 StorageAccessFramework를 이용하여 공용저장공간에 읽고 쓸수 있다. 이제 이 영역만이 외부저장소라고 할수 있다.

 

사진, 동영상, 음악파일은 MediaStore를 통해 접근이 이미 가능했고 추가로 Download에 저장된 파일에 접근하기 위한 콜렉션을 제공한다. Download도 내가 생성한 파일을 제외하고는 사용자가 시스템파일선택기를 통해 사용자가 명시적으로 선택한 경우에만 접근이 가능하다.

 

파일접근 정리

파일위치 권한 접근 앱삭제시 제거
개별앱 공간 필요없음 getExternalFileDir() Y
미디어콜랙션(사진,비디오,오디오) 다른앱파일접근시만 읽기권한필요 MediaStore or SAF N
다운로드 필요없음 SAF N

느끼고 있겠지만 많은 앱개발자들은 이전 방식을 더 선호한다. 보안에 민감한 클라이언트들은 안그러겠지만...

어쨌든 이전방식을 아직은 사용가능하다.

 

AndroidManifest.xml의 application tag에 requestLegacyExternalStorage플래그를 true로 하면 임시로 이전 저장소정책을 사용할수 있다. 하지만 Target을 Android11로 하면 이 플래그도 지원하지 않을 것이라고 한다.

 

<!-- 임시로 Q(10)에서 opt-out함 -->
<uses-sdk android:targetSdkVersion="29" />
<application android:requestLegacyExternalStorage="true" ... >

문제는 참조하는 다른 라이브러리들이 대응이 안된 경우이다. 별수 없이 해당 라이브러리에서 대응이 되어야 한다.

코드내에서 정책을 확인하기 위해서 isExternalStorageLegacy()를 제공한다.

// storage mode 체크
if (Envirioment.isExternalStorageLegacy()) {
...
}

경우에 따라서는 isExternalStorageLegacy()로 분기하여 파일접근하는 로직을 구분하여 적용해야 할수도 있을것 같다.

Posted by 삼스
Android2020. 9. 8. 10:59

ContentProvider의 특별한 서브클래스로 file:/// Uri가 아니라 content:// Uri로 파일에 접근하고 공유할 수 있게 해준다.

 

file:/// Uri는 보안에 취약하지만 content:// Uri는 안드로이드의 보안인프라의 기반으로 보안문제를 해결해준다.

 

FileProvider를 저으이하고 유효한 파일을 지정하고 Content URI로 파일탐색하고 임시적으로 권한을 획득하고 타앱에 Content URI를 제공하는 것에 대해 정리하겠다.

 

FileProvider의 정의

 

코드를 작성할 필요가 없고 XML에 FileProvider를 기술한다.
android:authorities속성이 중요하며 당신만의 도메인으로 정의한다. android:export는 false로 하여 public하지 않게 설정한다. android:grantUriPermissions속성은 true로 파일에 대해 임시권한을 부여할수 있게 허용한다.

 

<manifest>
    ...
    <application>
        ...
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.mydomain.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            ...
        </provider>
        ...
    </application>
</manifest>

유효한 파일 기술하기

 

FileProvider는 사전에 기술한 폴더의 파일들에 대해서만 content uri를 생성한다. 이는 별도의 xml파일에 기술한다. 아래코드는 private 파일영역의 images/폴더를 정의한 예이다.

 

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="images/"/>
    ...
</paths>

<paths>엘리먼트는 하나이상의 엘리먼트를 포함해야 한다.

다음은 다양한 폴더경로에 대한 설정들이다.

 

<!-- cache는 앱의 내부저장소의 캐시폴더를 의미하며 getCacheDir()의 결과와 동일하다.-->
<cache-path name="name" path="path" />  
<!--external은 외부저장소의 루트폴더를 의미하며 Context.getExternalFilesDir(String)의 결과와 동일하다. -->
<external-path name="name" path="path" /> 
<!--외주저장소의 캐시폴더를 의미한다. Context.getExternalCacheDir() -->
<external-cache-path name="name" path="path" />
<!--외부저장소의 media폴더를 의미한다. Context.getExternalMediaDirs(). 이 폴더는 API 21+에서만 유효하다.-->
<external-media-path name="name" path="path" />

엘리먼트의 name속성은 URI path 세그먼트로 실제 경로를 숨겨주는 역할을 한다. 실제 경로는 path속성에 기술된다. path는 실제 서브폴더명이다.

 

<paths>엘리먼트에 여러개의 files-path를 기술하여 여러 폴더의 내용을 공유할 수 있다.

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="images/"/>
    <files-path name="my_docs" path="docs/"/>
</paths>

paths를 정의한 파일은 별도의 파일로 res/xml/폴더에 file_paths.xml로 저장하고 provider 엘리먼트의 mata-data에 기술해준다.

<provider
    android:name="androidx.core.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>

파일의 Content URI생성하기

 

타앱에 파일을 공유하려면 해당 파일에 대해 content URI를 생성해야 한다. File객체를 생성 후 getUriForFile()의 인자로 호출한다. 이렇게 만들어진 URI를 인텐트에 담아서 전달하고 전달받은 앱은 ContentResolver.openFileDescriptor로 ParcelFIleDescriptor를 얻어서 파일에 접근한다.

File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);

//위 코드의 결과로 생성된 URI는 다음과 같다.
content://com.mydomain.fileprovider/my_images/default_image.jpg

 

타앱에 ContentURI 제공

타앱에 특정파일의 content URI를 제공하는 여러가지 방법이 있는데 일반적으로 startActivityForResult()으로 당신의 앱에 인텐트를 전달하는 방법을 사용한다. 당신의 앱에서는 바로 응답하거나 사용자의 선택할 수 있는 UI를 제공하여 응답하는 방법이 있다. 두 방법다 선택한 파일의 content URI를 setResult()로 인텐트에 담아서 반환한다.
또한 ClipData 객체에 content URI를 담아서 반환할수 있으며 Intent.setClipData()를 호출하여 담으면 된다. 이 때는 여러개의 각각의 content URI를 갖는 ClipData객체를 담을수 있다.

Posted by 삼스