Як використовувати підтримку FileProvider для обміну вмістом з іншими програмами?


142

Я шукаю спосіб правильно поділитися (не ВІДКРИТИ) внутрішній файл із зовнішнім додатком за допомогою FileProvider бібліотеки підтримки Android .

Дотримуючись прикладу в документах,

<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 надає лише дозвіл на Uri, вказаний на dataнамірі, а не значення EXTRA_STREAMдодаткового (як було встановлено setStream).

Я намагався компромісною безпеки, встановивши android:exportedв trueдля постачальника, але FileProviderвнутрішньо перевіряє , є чи сам по собі йде на експорт, якщо це так, то кидає виняток.


7
+1 це здається справді оригінальним питанням - на Google чи StackOverflow немає нічого , наскільки я міг сказати.
Філ

Ось публікація, щоб отримати основні настройки FileProvider, використовуючи мінімальний приклад проекту github: stackoverflow.com/a/48103567/2162226 . Він надає кроки, за допомогою яких файли можна скопіювати (не вносячи змін) у локальний окремий проект
Gene Bo

Відповіді:


159

Використовуючи FileProviderз бібліотеки підтримки, ви повинні вручну надавати та скасовувати дозволи (під час виконання) для інших програм для читання конкретних Uri. Використовуйте методи Context.grantUriPermission та Context.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);
}

Альтернативний спосіб згідно з документацією :

  • Помістіть URI вмісту у наміри, зателефонувавши setData ().
  • Потім викличте метод Intent.setFlags () або FLAG_GRANT_READ_URI_PERMISSION, або FLAG_GRANT_WRITE_URI_PERMISSION або і тим і іншим.
  • Нарешті, надішліть Намір в інший додаток. Найчастіше ви робите це за допомогою виклику setResult ().

    Дозволи, надані в Намір, залишаються в силі, поки стек активності прийому активний. Коли стек закінчується,
    дозволи автоматично видаляються. Дозволи, надані одній
    діяльності в клієнтському додатку, автоматично поширюються на інші
    компоненти цього додатка.

Btw. якщо вам потрібно, ви можете скопіювати джерело FileProvider та змінити attachInfoметод, щоб запобігти постачальнику перевіряти, чи він експортується.


26
Використовуючи grantUriPermissionметод, нам потрібно вказати назву пакета програми, на яку ми хочемо надати дозвіл. Однак під час спільного використання ми зазвичай не знаємо, яке саме призначення призначене для спільного використання.
Ренді Сугіанто 'Юку'

Що робити, якщо ми не знаємо назву пакета та просто хочемо, щоб інші програми могли його відкрити
StuStirling

3
Чи можете ви надати робочий код для останнього рішення @Lecho?
StErMi

2
Чи існує помилка відкритого відстеження, чому підтримка FileProvider не працює з setFlags, але працює з grantUriPermission? Або це не помилка; в такому випадку як це не помилка?
nmr

1
На додаток до цього, я виявив, що виклик grantUriPermission не працює для наміру, створеного за допомогою ShareCompat, але це робиться під час створення вручну вручну.
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 нарешті зробив це для мене.
Корд Рен

Перерви для інших версій Android
Олівер Діксон

@OliverDixon: які з них? Я тестував його на Android 6.0 і 9.0.
Фіолетова жирафа

1
Це єдина відповідь на використання того, FileProviderщо працює. В інших відповідях неправильне керування намірами (вони не використовуються ShareCompat.IntentBuilder), і зовнішні додатки не можуть відкриватися жодним змістом. Я витратив буквально більшу частину дня на цю нісенітницю, поки нарешті не знайшов вашої відповіді.
Фіолетова жирафа

1
@VioletGiraffe Я також не використовую ShareCompat, але шахта працює. Моя проблема полягала в тому, що я надавав дозволи на читання у намірі мого обранця помилково, коли мені потрібно було надати дозволу на читання моєму наміру, перш ніж це було передано наміру вибору, а також вибору. Сподіваюся, це має сенс. Просто переконайтеся, що ви надаєте дозвіл на читання на правильний намір.
Райан

23

Це рішення працює для мене з ОС 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>

Java:

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);
    }
}

Дозвіл анулюється методом revokeFileReadPermission () у методах onResume та onDestroy () фрагмента чи активності.


1
Це рішення працювало для мене, але лише після того, як я додав intent.setDataAndType (uri, "video / *"); BTW відсутня вкладенняUri змінна
Uri

Ви маєте на увазі, що fileне зміниться з onResumeна onDestroy?
CoolMind

16

Оскільки, як каже Філ у коментарі до оригінального питання, це унікально, і в Google немає іншої інформації про SO on, я подумав, що я повинен також поділитися своїми результатами:

У моєму додатку 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 Drive.


9
На жаль, це не працює. Цей змінний fileToShare працює лише в тому випадку, якщо файл існує на SDcard або зовнішній файловій системі. Сенс використання FileProvider полягає в тому, щоб ви могли обмінюватися вмістом із внутрішньої системи.
Кіт Конноллі

Здається, це працює належним чином з локальними файлами пам’яті (на Android 5+). Але у мене з’являються проблеми на Android 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 тощо.

У своєму маніфесті, як згадується в документації на Android, я розмістив:

<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
Ви справжній MVP для публікації шляхів = "." Працював для мене
robsstackoverflow

Я отримую помилку на кшталт "Не вдалося знайти налаштований корінь, який містить /" я використав той самий код
Ракшит Кумар

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

Я хочу поділитися чимось, що заблокувало нас на пару днів: код fileprovider ОБОВ'ЯЗКОВО вставити між тегами програми, а не після нього. Це може бути тривіально, але це ніколи не вказується, і я думав, що я міг би комусь допомогти! (ще раз дякую piolo94)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.