반응형

이번 포스팅은 Android Build 오류에 대하여 알아보도록 하겠습니다.


< Error >


android.os.FileUriExposedException: file:///storage/emulated/0/Download/viewData.pdf exposed beyond app through ClipData.Item.getUri()


< Solution>

1. Android 7.0 이상부터 파일 공유 시 API 정책 변경

2. file:// URI 의 직접적인 노출이 아니라 content://URI 로 보내고 이에 대해서 임시 Access 권한을 부여하는 방식으로 변경이 되었습니다.

    아래는 Google Android OS 7.0 공식 문서 입니다.

   (링크 : https://developer.android.com/about/versions/nougat/android-7.0-changes.html#accessibility)


파일 시스템 권한 변경


개인 파일의 보안을 강화하기 위해, Android 7.0 이상을 대상으로 하는 앱의 개인 디렉터리는 액세스가 제한됩니다(0700). 이 설정은 크기 또는 존재 여부와 같은 개인 파일의 메타데이터 유출을 막아줍니다. 이러한 권한 변경은 여러 가지 부작용이 있습니다.

▶ 소유자가 개인 파일의 파일 권한을 더 이상 완화해서는 안 되며, MODE_WORLD_READABLE 및/또는 MODE_WORLD_WRITEABLE을 사용하여 권한을 완화하려고 시도하면 SecurityException이 트리거됩니다.

참고: 아직까지는 이 제한이 완전히 적용되지 않습니다. 앱이 여전히 기본 API 또는 File API를 사용하여 개인 디렉터리에 대한 권한을 수정할 수도 있습니다. 하지만 개인 디렉터리에 대한 권한은 부득이한 경우가 아니라면 완화하지 않는 것이 좋습니다.

▶ 패키지 도메인 외부에서 file:// URI를 전달하면 수신기가 액세스 불가능한 경로로 남아 있을 수 있습니다. 따라서 file:// URI를 전달하려고 시도하면 FileUriExposedException이 트리거됩니다. 개인 파일의 내용을 공유하기 위해 권장되는 방법은 FileProvider를 사용하는 것입니다.

 DownloadManager는 비공개로 저장된 파일을 더 이상 파일 이름별로 공유할 수 없습니다. 레거시 애플리케이션은 COLUMN_LOCAL_FILENAME에 액세스할 때 액세스가 불가능한 경로가 될 수 있습니다. Android 7.0 이상을 대상으로 하는 앱은 COLUMN_LOCAL_FILENAME에 액세스할 때 SecurityException을 트리거합니다. DownloadManager.Request.setDestinationInExternalFilesDir() 또는 DownloadManager.Request.setDestinationInExternalPublicDir()을 사용하여 다운로드위치를 공용 위치로 설정하는 레거시 애플리케이션은 COLUMN_LOCAL_FILENAME에 있는 경로에 여전히 액세스할 수 있지만, 이 메서드는 부득이한 경우가 아니라면 사용하지 않는 것이 좋습니다. DownloadManager에 의해 노출되는 파일에 액세스하는 좋은 방법은 ContentResolver.openFileDescriptor()를 사용하는 것입니다.


      앱 사이의 파일 공유


Android 7.0을 대상으로 하는 앱의 경우, Android 프레임워크는 앱 외부에서 file:// URI의 노출을 금지하는 StrictMode API 정책을 적용합니다. 파일 URI를 포함하는 인텐트가 앱을 떠나면 FileUriExposedException 예외와 함께 앱에 오류가 발생합니다.

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


3. AndroidMenifext.xml를 수정하여 fileprovider를 지정합니다.



<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:permission="android.permission.MANAGE_DOCUMENTS"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_path" />
</provider>


4.  res/xml/file_provider_path.xml 파일을 생성하여 아래와 같이 파일 경로를 지정합니다.


<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external"
path="." />
<external-files-path
name="external_files"
path="." />

<files-path
name="files"
path="." />

</paths>


반응형
반응형

이번 포스팅은 FileProvider를 사용하여 파일 공유하는 방법에 대하여 알아보도록 하겠습니다.


1. FileProvider 란 


ContentProvider는 데이터를 캡슐화하여 다른 응용 프로그램에 제공하는 Android 구성 요소입니다. 여러 응용 프로그램간에 데이터를 공유해야하는 경우에만 필요합니다. 예를 들어 연락처 데이터는 ContentProvider의 하위 클래스 인 ContactsProvider를 사용하여 다른 응용 프로그램과 공유됩니다.

FileProvider는 ContentProvider의 하위 클래스입니다. ContentProvider는 모든 종류의 데이터를 안전하게 공유 할 수있는 구성 요소이지만 FileProvider는 특히 앱의 내부 파일을 공유하는 데 사용됩니다. FileProvider 클래스는 v4 지원 라이브러리의 일부이므로 프로젝트에 포함시켜야합니다.

FileProvider가 작동하도록하려면 다음 세 단계를 수행하십시오.

    ▶ AndroidManifest 파일에서 FileProvider 정의

    ▶ FileProvider가 다른 응용 프로그램과 공유 할 모든 경로가 포함 된 XML 파일을 만듭니다.

    ▶ Intent에 유효한 URI를 번들로 묶어 활성화


2. FileProvider 정의


FileProvider 정의

AndroidManifest 내에서 FileProvider를 정의하려면 다음 속성 및 요소에 익숙해야합니다.

    ▶ android : authorities

    ▶ android : exported

    ▶ android : grantUriPermissions

    ▶ android : name

    ▶ <meta-data> subelement

이들 모두가 매우 친숙해 보인다면 FileProvider를 둘러 보는 방법이 약간 쉬울 것입니다. 그렇지 않으면 각 속성과 그 목적에 대한 자세한 설명을 준비했습니다.

android : authorities

적어도 하나의 고유 권한을 반드시 정의합니다. Android 시스템은 모든 제공자의 목록을 유지하며 권한별로 이를 구분합니다. 권한은 애플리케이션 ID가 Android 애플리케이션을 정의하는 것처럼 FileProvider를 정의합니다.

일반적으로 Android 시스템은 ContentProviders에 특정 URI 체계를 사용합니다. 체계는 content : // <authority> / <path>로 정의 되므로 시스템은 URI의 권한을 ContentProvider의 권한과 일치시켜 어떤 ContentProvider가 요청되는지 알 수 있습니다.


android : exported

이 속성은 이름이 잘못되어 쉽게 오용 될 수 있습니다. 이 속성을 이해하려면 FileProvider를 door가 잠긴 방으로 생각하십시오. 값을 true로 설정하면 기본적으로 모든 사람에게 문이 열립니다. 모든 것이 귀하의 관점에서 작동하지만 다른 모든 앱이 권한을 부여받지 않고 FileProvider를 사용할 수 있기 때문에 방금 큰 보안 문제가 발생했습니다.

이를 통해 우연의 일치로 프로그램하지 말고 항상 코드의 부작용을 인식 할 수 있습니다. 또한 SDK 16 이하의 기본값이 true 이므로 항상이 속성을 정의하십시오 .


android : grantUriPermissions

FileProvider를 잠긴 방으로 계속 생각하면이 속성은 외부 앱에 임시 일회성 키를 제공하는 데 사용됩니다. 이 속성을 사용하면 앱의 내부 저장소를 안전하게 공유 할 수 있습니다. FLAG_GRANT_READ_URI_PERMISSION 또는 FLAG_GRANT_WRITE_URI_PERMISSION을 구성 요소를 활성화하여 앱의 내부 파일을 여는 의도에 추가하기 만하면됩니다. 이 플래그를 사용하려면 해당 값을 true로 설정하십시오 .

<provider> 요소는 <grant-uri-permission> 하위 요소도 가질 수 있습니다. 유일한 차이점은이 속성을 사용하면 앱의 내부 저장소 내에서 무엇이든 공유 할 수 있고 하위 요소를 사용하면 공유 할 특정 데이터 하위 집합을 선택할 수 있다는 것입니다. 하위 요소를 대신 사용하려면 값을 false로 설정하십시오 .

<메타 데이터> 하위 요소

파일 경로 XML 생성

FileProvider를 사용할 때이 하위 요소를 정의해야합니다. FileProvider가 외부 앱과 공유 할 수있는 모든 데이터 경로가 포함 된 XML 파일의 경로를 정의해야합니다.

XML 파일에는 루트로 <paths> 요소가 있어야합니다. <paths> 요소에는 다음 중 하나 일 수있는 하나 이상의 하위 요소가 있어야합니다.

    <files-path />-내부 앱 스토리지, Context # getFilesDir ()

    <cache-path />-내부 앱 캐시 스토리지, Context # getCacheDir ()

    <external-path />-공용 외부 저장소, Environment.getExternalStorageDirectory ()

    <external-files-path />-외부 앱 스토리지, 컨텍스트 #getExternalFilesDir (null)

    <external-cache-path />-외부 앱 캐시 스토리지, 컨텍스트 #getExternalCacheDir ()

그것들은 그들이 정의한 앱의 디렉토리에 따라 다르다는 것을 알았을 것입니다.

각 요소에는 경로와 이름 속성이 있어야합니다. path 속성은 공유하려는 서브 디렉토리를 정의하며 와일드 카드를 지원 하지 않습니다 . name 속성은 보안상의 이유로 사용되며 하위 디렉토리 이름을 해당 값으로 대체합니다.

안드로이드 : 이름

이 값을 android.support.v4.content.FileProvider로 설정했습니다 .

번들 유효 URI

AndroidManifest 파일에 FileProvider를 정의하면 사용할 준비가 된 것입니다. 파일을 공유하려면 인 텐트를 작성하고 유효한 URI를 제공해야합니다. URI는 FileProvider 클래스를 사용하여 생성됩니다.

코드 구현

AndroidManifest.xml

<provider
    android:name="android.support.v4.content.FileProvider"
    android:grantUriPermissions="true"
    android:exported="false"
    android:authorities="${applicationId}">

    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_provider_paths"/>

</provider>

권한을 위해 앱 ID를 사용하고 있습니다. 프로젝트에 여러 가지 특징이 있으며 동시에 장치에 설치할 수 있기 때문입니다. Android 시스템에서는 동일한 FileProvider로 여러 응용 프로그램을 설치할 수 없으므로 각 기능마다 고유 한 권한이 필요합니다.

file_provider_paths.xml

<paths>
    <cache-path name="cache" path="/" />
    <files-path name=”files” path=”/” />
</paths>

이와 같은 경로를 정의함으로써 FileProvider가 앱의 내부 캐시 및 파일 디렉토리에있는 모든 파일을 공유 할 수 있습니다.

FileProvider 사용

// create new Intent
Intent intent = new Intent(Intent.ACTION_VIEW);

// set flag to give temporary permission to external app to use your FileProvider
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

// generate URI, I defined authority as the application ID in the Manifest, the last param is file I want to open
String uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, file);

// I am opening a PDF file so I give it a valid MIME type
intent.setDataAndType(uri, "application/pdf");

// validate that the device can open your File!
PackageManager pm = getActivity().getPackageManager();
if (intent.resolveActivity(pm) != null) {
    startActivity(intent);
}

작동 방식을 이해하면 고유 한 FileProvider를 구현하는 것이 매우 간단합니다.

문제의 복잡성은 코드 자체가 아니라 모든 것이 상호 연결되는 방법에 대한 문서와 이해입니다. 이 기사가 자신의 유스 케이스에 FileProvider를 구현하고 공식 문서를 읽기 쉽게 만드는 데 도움이되기를 바랍니다.


https://infinum.co/the-capsized-eight/share-files-using-fileprovider

반응형

+ Recent posts