다른 앱과 콘텐츠를 공유하기 위해 지원 FileProvider를 사용하는 방법은 무엇입니까?


142

Android 지원 라이브러리의 FileProvider를 사용하여 외부 파일과 내부 파일을 올바르게 공유 (OPEN 아님)하는 방법을 찾고 있습니다.

문서의 예에 따라

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.example.android.supportv4.my_files"
    android:grantUriPermissions="true"
    android:exported="false">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/my_paths" />
</provider>

다음과 같이 ShareCompat을 사용하여 파일을 다른 앱과 공유하십시오.

ShareCompat.IntentBuilder.from(activity)
.setStream(uri) // uri from FileProvider
.setType("text/html")
.getIntent()
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

FLAG_GRANT_READ_URI_PERMISSION dataEXTRA_STREAM추가 값 (에 의해 설정 됨 setStream) 이 아니라 의도 에 지정된 Uri에 대한 권한 만 부여하므로 작동하지 않습니다 .

공급자 로 설정 android:exported하여 보안을 손상하려고 시도 true했지만 FileProvider내부적으로 자체 내보내기 여부를 내부적으로 확인하면 예외가 발생합니다.


7
+1 이것은 정말로 독창적 인 질문 인 것 같습니다 .Google 또는 StackOverflow 에는 내가 말할 수있는 한 아무것도 없습니다 .
Phil

다음은 최소한의 github 예제 프로젝트를 사용하여 기본 FileProvider 설정을 얻는 게시물입니다 : stackoverflow.com/a/48103567/2162226 . 파일을 로컬 독립 실행 형 프로젝트로 복사 (변경하지 않고)하는 단계를 제공합니다.
Gene Bo

답변:


159

FileProvider지원 라이브러리에서 사용하면 다른 앱이 특정 Uri를 읽을 수 있도록 런타임에 권한을 수동으로 부여하고 취소해야합니다. Context.grantUriPermissionContext.revokeUriPermission 메소드를 사용하십시오 .

예를 들면 다음과 같습니다.

//grant permision for app with package "packegeName", eg. before starting other app via intent
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

//revoke permisions
context.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

최후의 수단으로 패키지 이름을 제공 할 수없는 경우 특정 의도를 처리 할 수있는 모든 앱에 권한을 부여 할 수 있습니다.

//grant permisions for all apps that can handle given intent
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
...
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

설명서 에 따른 대체 방법 :

  • setData ()를 호출하여 컨텐츠 URI를 인 텐트에 넣습니다.
  • 다음으로 FLAG_GRANT_READ_URI_PERMISSION 또는 FLAG_GRANT_WRITE_URI_PERMISSION 또는 둘 다를 사용하여 Intent.setFlags () 메소드를 호출하십시오.
  • 마지막으로 인 텐트를 다른 앱으로 보냅니다. 대부분 setResult ()를 호출하여이 작업을 수행합니다.

    수신 활동의 스택이 활성화되어있는 동안 의도로 부여 된 권한은 그대로 유지됩니다. 스택이 완료되면
    권한이 자동으로 제거됩니다.
    클라이언트 앱에서 하나의 활동에 부여 된 권한
    은 해당 앱의 다른 구성 요소로 자동 확장됩니다 .

Btw. 필요한 경우 FileProvider의 소스를 복사 attachInfo하고 공급자가 내보냈는지 확인하지 못하도록 메서드를 변경할 수 있습니다 .


26
grantUriPermission방법을 사용하여 권한을 부여하려는 앱의 패키지 이름을 제공해야합니다. 그러나 공유 할 때 일반적으로 공유 대상이 어떤 응용 프로그램인지 알 수 없습니다.
랜디 Sugianto '유쿠'

패키지 이름을 모르고 다른 앱에서 열 수 있도록하려면
StuStirling

3
최신 솔루션 @Lecho에 대한 작업 코드를 제공 할 수 있습니까?
StErMi

2
지원 FileProvider가 setFlags와 작동하지 않지만 grantUriPermission과 작동하는 이유를 추적하는 버그가 있습니까? 아니면 이것이 버그가 아닌 경우 어떻게 버그가 아닌가?
nmr

1
이 외에도, shareCompat을 사용하여 생성 된 인 텐트에 대해서는 grantUriPermission 호출이 작동하지 않지만 인 텐트를 수동으로 생성 할 때 수행되었습니다.
StuStirling

33

완전히 작동하는 코드 샘플은 내부 앱 폴더에서 파일을 공유하는 방법입니다. Android 7 및 Android 5에서 테스트되었습니다.

AndroidManifest.xml

</application>
   ....
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="android.getqardio.com.gmslocationtest"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>
</application>

xml / provider_paths

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

코드 자체

    File imagePath = new File(getFilesDir(), "external_files");
    imagePath.mkdir();
    File imageFile = new File(imagePath.getPath(), "test.jpg");

    // Write data in your file

    Uri uri = FileProvider.getUriForFile(this, getPackageName(), imageFile);

    Intent intent = ShareCompat.IntentBuilder.from(this)
                .setStream(uri) // uri from FileProvider
                .setType("text/html")
                .getIntent()
                .setAction(Intent.ACTION_VIEW) //Change if needed
                .setDataAndType(uri, "image/*")
                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

   startActivity(intent);

1
그리고 ShareCompat.IntentBuilder읽기 URI 권한을 사용하면 마침내 나를 위해했습니다.
Cord Rehn

다른 버전의 Android를위한 휴식
Oliver Dixon

@OliverDixon : 어느 것? Android 6.0 및 9.0에서 테스트했습니다.
Violet Giraffe

1
이것이 FileProvider작동 하는 유일한 방법 입니다. 다른 답변은 의도 처리가 잘못되어 (사용하지 않음 ShareCompat.IntentBuilder) 외부 응용 프로그램에서 의도를 의미있는 방식으로 열 수 없습니다. 나는 마침내 당신의 대답을 찾을 때까지 말 그대로이 말도 안되는 일에 보냈습니다.
Violet Giraffe

1
@VioletGiraffe 나는 또한 ShareCompat을 사용하지 않지만 광산은 작동합니다. 내 문제는 실수로 내 선택 자의 의도에 대한 읽기 권한을 부여하는 것이 었습니다. 선택 자의 의도와 선택자에게 전달되기 전에 내 의도에 대한 읽기 권한을 부여해야 할 때였습니다. 나는 그것이 의미가 있기를 바랍니다. 올바른 의도에 대한 읽기 권한을 부여하십시오.
Ryan

23

이 솔루션은 OS 4.4부터 작동합니다. 모든 장치에서 작동하도록 이전 장치에 대한 해결 방법을 추가했습니다. 이렇게하면 항상 가장 안전한 솔루션이 사용됩니다.

Manifest.xml :

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

file_paths.xml :

<paths>
    <files-path name="app_directory" path="directory/"/>
</paths>

자바:

public static void sendFile(Context context) {
    Intent intent = new Intent(Intent.ACTION_SEND);
    intent.setType("text/plain");
    String dirpath = context.getFilesDir() + File.separator + "directory";
    File file = new File(dirpath + File.separator + "file.txt");
    Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file);
    intent.putExtra(Intent.EXTRA_STREAM, uri);
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    // Workaround for Android bug.
    // grantUriPermission also needed for KITKAT,
    // see https://code.google.com/p/android/issues/detail?id=76683
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
        List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            context.grantUriPermission(packageName, attachmentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
    }
    if (intent.resolveActivity(context.getPackageManager()) != null) {
        context.startActivity(intent);
    }
}

public static void revokeFileReadPermission(Context context) {
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
        String dirpath = context.getFilesDir() + File.separator + "directory";
        File file = new File(dirpath + File.separator + "file.txt");
        Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file);
        context.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }
}

Fragment 또는 Activity의 onResume 및 onDestroy () 메소드에서 revokeFileReadPermission ()을 사용하여 권한이 취소됩니다.


1
이 솔루션은 나를 위해 일했지만 intent.setDataAndType (uri, "video / *"); BTW 누락 첨부 파일 URI 변수는 uri
EdgarK

당신은 그 의미합니까 file에서 변경되지 않습니다 onResumeonDestroy?
CoolMind

16

Phil이 원래 질문에 대한 그의 의견에서 언급했듯이, 이것은 독특하고 Google에는 SO에 대한 다른 정보가 없으므로 결과를 공유해야한다고 생각했습니다.

내 응용 프로그램에서 FileProvider는 공유 의도를 사용하여 파일을 공유하기 위해 즉시 작동했습니다. FileProvider를 설정하기 위해 특별한 구성이나 코드가 필요하지 않았습니다. 내 manifest.xml에 다음을 배치했습니다.

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.my.apps.package.files"
        android:exported="false"
        android:grantUriPermissions="true" >
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/my_paths" />
    </provider>

my_paths.xml에는 다음이 있습니다.

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

내 코드에는 다음이 있습니다.

    Intent shareIntent = new Intent();
    shareIntent.setAction(Intent.ACTION_SEND);
    shareIntent.setType("application/xml");

    Uri uri = FileProvider.getUriForFile(this, "com.my.apps.package.files", fileToShare);
    shareIntent.putExtra(Intent.EXTRA_STREAM, uri);

    startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.share_file)));

그리고 내 앱 개인 저장소의 파일 저장소를 문제없이 Gmail 및 Google 드라이브와 같은 앱과 공유 할 수 있습니다.


9
슬프게도 이것은 작동하지 않습니다. 해당 변수 fileToShare는 파일이 SDcard 또는 외부 파일 시스템에 존재하는 경우에만 작동합니다. FileProvider를 사용하는 요점은 내부 시스템의 컨텐츠를 공유 할 수 있도록하는 것입니다.
Keith Connolly

이것은 로컬 스토리지 파일 (Android 5 이상)에서 제대로 작동하는 것 같습니다. 그러나 나는 안드로이드 4에 문제가 있습니다.
Peterdk

15

내가 알 수있는 한 이것은 최신 버전의 Android에서만 작동하므로 다른 방법을 찾아야 할 것입니다. 이 솔루션은 4.4에서는 작동하지만 4.0 또는 2.3.3에서는 작동하지 않으므로 모든 Android 기기에서 실행되는 앱의 콘텐츠를 공유하는 데 유용한 방법은 아닙니다.

manifest.xml에서 :

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.mydomain.myapp.SharingActivity"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

권한을 지정하는 방법에주의하십시오. URI를 작성하고 공유 의도를 시작하는 활동을 지정해야합니다.이 경우 활동을 SharingActivity라고합니다. 이 요구 사항은 Google 문서에서 분명하지 않습니다.

file_paths.xml :

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="just_a_name" path=""/>
</paths>

경로를 지정하는 방법에주의하십시오. 위의 기본값은 개인 내부 저장소의 루트입니다.

SharingActivity.java에서 :

Uri contentUri = FileProvider.getUriForFile(getActivity(),
"com.mydomain.myapp.SharingActivity", myFile);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(shareIntent, "Share with"));

이 예에서는 JPEG 이미지를 공유하고 있습니다.

마지막으로 파일을 올바르게 저장했으며 다음과 같은 방법으로 파일에 액세스 할 수 있는지 확인하는 것이 좋습니다.

File myFile = getActivity().getFileStreamPath("mySavedImage.jpeg");
if(myFile != null){
    Log.d(TAG, "File found, file description: "+myFile.toString());
}else{
    Log.w(TAG, "File not found!");
}

8

내 응용 프로그램에서 FileProvider는 정상적으로 작동하며 파일 디렉토리에 저장된 내부 파일을 Gmail, Yahoo 등의 이메일 클라이언트에 첨부 할 수 있습니다.

안드로이드 문서에 언급 된대로 내 매니페스트에서 다음을 배치했습니다.

<provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.package.name.fileprovider"
        android:grantUriPermissions="true"
        android:exported="false">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepaths" />
    </provider>

내 파일이 루트 파일 디렉토리에 저장되면 filepaths.xml은 다음과 같습니다.

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

이제 코드에서 :

 File file=new File(context.getFilesDir(),"test.txt");

 Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE);

 shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,
                                     "Test");

 shareIntent.setType("text/plain");

 shareIntent.putExtra(android.content.Intent.EXTRA_EMAIL,
                                 new String[] {"email-address you want to send the file to"});

   Uri uri = FileProvider.getUriForFile(context,"com.package.name.fileprovider",
                                                   file);

                ArrayList<Uri> uris = new ArrayList<Uri>();
                uris.add(uri);

                shareIntent .putParcelableArrayListExtra(Intent.EXTRA_STREAM,
                                                        uris);


                try {
                   context.startActivity(Intent.createChooser(shareIntent , "Email:").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));                                                      


                }
                catch(ActivityNotFoundException e) {
                    Toast.makeText(context,
                                   "Sorry No email Application was found",
                                   Toast.LENGTH_SHORT).show();
                }
            }

이것은 나를 위해 일했습니다. 이것이 도움이되기를 바랍니다 :)


1
paths = "를 게시 한 MVP입니다." 나를 위해 일했다
robsstackoverflow

"/를 포함하는 구성된 루트를 찾지 못했습니다"와 같은 오류가 발생합니다. 동일한 코드를 사용했습니다
Rakshith Kumar

5

카메라에서 이미지를 얻는 경우 이러한 솔루션 중 어느 것도 Android 4.4에서 작동하지 않습니다. 이 경우 버전을 확인하는 것이 좋습니다.

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getContext().getPackageManager()) != null) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        uri = Uri.fromFile(file);
    } else {
        uri = FileProvider.getUriForFile(getContext(), getContext().getPackageName() + ".provider", file);
    }
    intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
    startActivityForResult(intent, CAMERA_REQUEST);
}

1

위의 답변을 향상시키기 위해 : NullPointerEx를 얻는 경우 :

컨텍스트없이 getApplicationContext ()를 사용할 수도 있습니다.

                List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
                for (ResolveInfo resolveInfo : resInfoList) {
                    String packageName = resolveInfo.activityInfo.packageName;
                    grantUriPermission(packageName, photoURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }

1

며칠 동안 우리를 막 았던 무언가를 공유하고 싶습니다. 파일 제공자 코드 는 응용 프로그램 태그 사이에 삽입 해야 합니다. 사소한 것일 수도 있지만 결코 지정되지 않았으며 누군가를 도울 수 있다고 생각했습니다! (piolo94에 다시 감사드립니다)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.