Общий доступ к файлу

После того как вы настроили свое приложение для обмена файлами с использованием URI контента, вы можете отвечать на запросы других приложений об этих файлах. Один из способов ответа на эти запросы — предоставить интерфейс выбора файлов из серверного приложения, который могут вызывать другие приложения. Этот подход позволяет клиентскому приложению позволить пользователям выбирать файл из серверного приложения, а затем получать URI содержимого выбранного файла.

В этом уроке показано, как создать в приложении Activity выбора файлов, которое отвечает на запросы файлов.

Получать запросы файлов

Чтобы получать запросы файлов от клиентских приложений и отвечать URI контента, ваше приложение должно предоставить Activity выбора файла. Клиентские приложения запускают это Activity , вызывая startActivityForResult() с Intent содержащим действие ACTION_PICK . Когда клиентское приложение вызывает startActivityForResult() , ваше приложение может вернуть результат клиентскому приложению в виде URI контента для файла, выбранного пользователем.

О том, как реализовать запрос файла в клиентском приложении, читайте в уроке Запрос общего файла .

Создание действия по выбору файлов

Чтобы настроить Activity выбора файла, начните с указания Activity в вашем манифесте вместе с фильтром намерений, соответствующим действию ACTION_PICK и категориям CATEGORY_DEFAULT и CATEGORY_OPENABLE . Также добавьте фильтры типов MIME для файлов, которые ваше приложение передает другим приложениям. В следующем фрагменте показано, как указать новый фильтр Activity и намерений:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...
       
<application>
        ...
           
<activity
               
android:name=".FileSelectActivity"
               
android:label="@File Selector" >
               
<intent-filter>
                   
<action
                       
android:name="android.intent.action.PICK"/>
                   
<category
                       
android:name="android.intent.category.DEFAULT"/>
                   
<category
                       
android:name="android.intent.category.OPENABLE"/>
                   
<data android:mimeType="text/plain"/>
                   
<data android:mimeType="image/*"/>
               
</intent-filter>
           
</activity>

Определите действие выбора файла в коде

Затем определите подкласс Activity , который отображает файлы, доступные из каталога files/images/ вашего приложения во внутренней памяти, и позволяет пользователю выбрать нужный файл. В следующем фрагменте показано, как определить это Activity и ответить на выбор пользователя:

class MainActivity : Activity() {

   
// The path to the root of this app's internal storage
   
private lateinit var privateRootDir: File
   
// The path to the "images" subdirectory
   
private lateinit var imagesDir: File
   
// Array of files in the images subdirectory
   
private lateinit var imageFiles: Array<File>
   
// Array of filenames corresponding to imageFiles
   
private lateinit var imageFilenames: Array<String>

   
// Initialize the Activity
   
override fun onCreate(savedInstanceState: Bundle?) {
       
...
       
// Set up an Intent to send back to apps that request a file
        resultIntent
= Intent("com.example.myapp.ACTION_RETURN_FILE")
       
// Get the files/ subdirectory of internal storage
        privateRootDir
= filesDir
       
// Get the files/images subdirectory;
        imagesDir
= File(privateRootDir, "images")
       
// Get the files in the images subdirectory
        imageFiles
= imagesDir.listFiles()
       
// Set the Activity's result to null to begin with
        setResult
(Activity.RESULT_CANCELED, null)
       
/*
         * Display the file names in the ListView fileListView.
         * Back the ListView with the array imageFilenames, which
         * you can create by iterating through imageFiles and
         * calling File.getAbsolutePath() for each File
         */

       
...
   
}
   
...
}
public class MainActivity extends Activity {
   
// The path to the root of this app's internal storage
   
private File privateRootDir;
   
// The path to the "images" subdirectory
   
private File imagesDir;
   
// Array of files in the images subdirectory
   
File[] imageFiles;
   
// Array of filenames corresponding to imageFiles
   
String[] imageFilenames;
   
// Initialize the Activity
   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
...
       
// Set up an Intent to send back to apps that request a file
        resultIntent
=
               
new Intent("com.example.myapp.ACTION_RETURN_FILE");
       
// Get the files/ subdirectory of internal storage
        privateRootDir
= getFilesDir();
       
// Get the files/images subdirectory;
        imagesDir
= new File(privateRootDir, "images");
       
// Get the files in the images subdirectory
        imageFiles
= imagesDir.listFiles();
       
// Set the Activity's result to null to begin with
        setResult
(Activity.RESULT_CANCELED, null);
       
/*
         * Display the file names in the ListView fileListView.
         * Back the ListView with the array imageFilenames, which
         * you can create by iterating through imageFiles and
         * calling File.getAbsolutePath() for each File
         */

         
...
   
}
   
...
}

Ответ на выбор файла

Как только пользователь выбирает общий файл, ваше приложение должно определить, какой файл был выбран, а затем сгенерировать URI контента для этого файла. Поскольку Activity отображает список доступных файлов в ListView , когда пользователь щелкает имя файла, система вызывает метод onItemClick() , в котором вы можете получить выбранный файл.

При использовании намерения отправить URI файла из одного приложения в другое вы должны быть осторожны, чтобы получить URI, который смогут прочитать другие приложения. Выполнение этого на устройствах под управлением Android 6.0 (уровень API 23) и более поздних версий требует особой осторожности из-за изменений в модели разрешений в этой версии Android, в частности, READ_EXTERNAL_STORAGE становится опасным разрешением , которого может не хватать принимающему приложению.

Учитывая эти соображения, мы рекомендуем избегать использования Uri.fromFile() , которое имеет несколько недостатков. Этот метод:

  • Не разрешает обмен файлами между профилями.
  • Требуется, чтобы у вашего приложения было разрешение WRITE_EXTERNAL_STORAGE на устройствах под управлением Android 4.4 (уровень API 19) или ниже.
  • Требуется, чтобы принимающие приложения имели разрешение READ_EXTERNAL_STORAGE , которое не будет работать на важных объектах общего доступа, таких как Gmail, у которых нет такого разрешения.

Вместо использования Uri.fromFile() вы можете использовать разрешения URI , чтобы предоставить другим приложениям доступ к определенным URI. Хотя разрешения URI не работают с file:// , созданными Uri.fromFile() , они работают с URI, связанными с поставщиками контента. API FileProvider может помочь вам создать такие URI. Этот подход также работает с файлами, которые находятся не во внешнем хранилище, а в локальном хранилище приложения, отправляющего намерение.

В onItemClick() получите объект File для имени выбранного файла и передайте его в качестве аргумента getUriForFile() вместе с полномочиями, указанными в элементе <provider> для FileProvider . Результирующий URI контента содержит полномочия, сегмент пути, соответствующий каталогу файла (как указано в метаданных XML), и имя файла, включая его расширение. Как FileProvider сопоставляет каталоги с сегментами пути на основе метаданных XML, описано в разделе «Указание общих каталогов» .

В следующем фрагменте показано, как обнаружить выбранный файл и получить для него URI содержимого:

    override fun onCreate(savedInstanceState: Bundle?) {
       
...
       
// Define a listener that responds to clicks on a file in the ListView
        fileListView
.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
           
/*
             * Get a File for the selected file name.
             * Assume that the file names are in the
             * imageFilename array.
             */

           
val requestFile = File(imageFilenames[position])
           
/*
             * Most file-related method calls need to be in
             * try-catch blocks.
             */

           
// Use the FileProvider to get a content URI
           
val fileUri: Uri? = try {
               
FileProvider.getUriForFile(
                       
this@MainActivity,
                       
"com.example.myapp.fileprovider",
                        requestFile
)
           
} catch (e: IllegalArgumentException) {
               
Log.e("File Selector",
                       
"The selected file can't be shared: $requestFile")
               
null
           
}
           
...
       
}
       
...
   
}
    protected void onCreate(Bundle savedInstanceState) {
       
...
       
// Define a listener that responds to clicks on a file in the ListView
        fileListView
.setOnItemClickListener(
               
new AdapterView.OnItemClickListener() {
           
@Override
           
/*
             * When a filename in the ListView is clicked, get its
             * content URI and send it to the requesting app
             */

           
public void onItemClick(AdapterView<?> adapterView,
                   
View view,
                   
int position,
                   
long rowId) {
               
/*
                 * Get a File for the selected file name.
                 * Assume that the file names are in the
                 * imageFilename array.
                 */

               
File requestFile = new File(imageFilename[position]);
               
/*
                 * Most file-related method calls need to be in
                 * try-catch blocks.
                 */

               
// Use the FileProvider to get a content URI
               
try {
                    fileUri
= FileProvider.getUriForFile(
                           
MainActivity.this,
                           
"com.example.myapp.fileprovider",
                            requestFile
);
               
} catch (IllegalArgumentException e) {
                   
Log.e("File Selector",
                         
"The selected file can't be shared: " + requestFile.toString());
               
}
               
...
           
}
       
});
       
...
   
}

Помните, что вы можете генерировать URI контента только для файлов, которые находятся в каталоге, указанном вами в файле метаданных, содержащем элемент <paths> , как описано в разделе «Указание общих каталогов» . Если вы вызываете getUriForFile() для File по пути, который вы не указали, вы получаете IllegalArgumentException .

Предоставить разрешения для файла

Теперь, когда у вас есть URI контента для файла, которым вы хотите поделиться с другим приложением, вам нужно разрешить клиентскому приложению доступ к файлу. Чтобы разрешить доступ, предоставьте разрешения клиентскому приложению, добавив URI контента в Intent , а затем установив флаги разрешений в Intent . Предоставляемые вами разрешения являются временными и истекают автоматически после завершения стека задач принимающего приложения.

В следующем фрагменте кода показано, как установить разрешение на чтение для файла:

    override fun onCreate(savedInstanceState: Bundle?) {
       
...
       
// Define a listener that responds to clicks on a file in the ListView
        fileListView
.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
           
...
           
if (fileUri != null) {
               
// Grant temporary read permission to the content URI
                resultIntent
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
               
...
           
}
           
...
       
}
       
...
   
}
    protected void onCreate(Bundle savedInstanceState) {
       
...
       
// Define a listener that responds to clicks in the ListView
        fileListView
.setOnItemClickListener(
               
new AdapterView.OnItemClickListener() {
           
@Override
           
public void onItemClick(AdapterView<?> adapterView,
                   
View view,
                   
int position,
                   
long rowId) {
               
...
               
if (fileUri != null) {
                   
// Grant temporary read permission to the content URI
                    resultIntent
.addFlags(
                       
Intent.FLAG_GRANT_READ_URI_PERMISSION);
               
}
               
...
             
}
             
...
       
});
   
...
   
}

Внимание: вызов setFlags() — единственный способ безопасно предоставить доступ к вашим файлам с использованием временных прав доступа. Избегайте вызова метода Context.grantUriPermission() для URI содержимого файла, поскольку этот метод предоставляет доступ, который вы можете отозвать, только вызвав Context.revokeUriPermission() .

Не используйте Uri.fromFile() . Он заставляет получать приложения иметь разрешение READ_EXTERNAL_STORAGE , вообще не будет работать, если вы пытаетесь поделиться информацией между пользователями, а в версиях Android ниже 4.4 (уровень API 19) вашему приложению потребуется WRITE_EXTERNAL_STORAGE . А действительно важные цели общего доступа, такие как приложение Gmail, не имеют READ_EXTERNAL_STORAGE , что приводит к сбою этого вызова. Вместо этого вы можете использовать разрешения URI, чтобы предоставить другим приложениям доступ к определенным URI. Хотя разрешения URI не работают с URI file://, генерируемыми Uri.fromFile() , они работают с URI, связанными с поставщиками контента. Вместо того, чтобы реализовывать свой собственный только для этого, вы можете и должны использовать FileProvider , как описано в разделе «Общий доступ к файлам» .

Поделитесь файлом с запрашивающим приложением

Чтобы поделиться файлом с приложением, которое его запросило, передайте Intent , содержащий URI контента и разрешения, в setResult() . Когда Activity которое вы только что определили, завершено, система отправляет Intent , содержащее URI контента, клиентскому приложению. Следующий фрагмент кода показывает, как это сделать:

    override fun onCreate(savedInstanceState: Bundle?) {
       
...
       
// Define a listener that responds to clicks on a file in the ListView
        fileListView
.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
           
...
           
if (fileUri != null) {
               
...
               
// Put the Uri and MIME type in the result Intent
                resultIntent
.setDataAndType(fileUri, contentResolver.getType(fileUri))
               
// Set the result
                setResult
(Activity.RESULT_OK, resultIntent)
           
} else {
                resultIntent
.setDataAndType(null, "")
                setResult
(RESULT_CANCELED, resultIntent)
           
}
       
}
   
}
    protected void onCreate(Bundle savedInstanceState) {
       
...
       
// Define a listener that responds to clicks on a file in the ListView
        fileListView
.setOnItemClickListener(
               
new AdapterView.OnItemClickListener() {
           
@Override
           
public void onItemClick(AdapterView<?> adapterView,
                   
View view,
                   
int position,
                   
long rowId) {
               
...
               
if (fileUri != null) {
                   
...
                   
// Put the Uri and MIME type in the result Intent
                    resultIntent
.setDataAndType(
                            fileUri
,
                            getContentResolver
().getType(fileUri));
                   
// Set the result
                   
MainActivity.this.setResult(Activity.RESULT_OK,
                            resultIntent
);
                   
} else {
                        resultIntent
.setDataAndType(null, "");
                       
MainActivity.this.setResult(RESULT_CANCELED,
                                resultIntent
);
                   
}
               
}
       
});

Предоставьте пользователям возможность немедленно вернуться в клиентское приложение после выбора файла. Один из способов сделать это — поставить галочку или кнопку «Готово» . Свяжите метод с кнопкой, используя атрибут кнопки android:onClick . В методе вызовите finish() . Например:

    fun onDoneClick(v: View) {
       
// Associate a method with the Done button
        finish
()
   
}
    public void onDoneClick(View v) {
       
// Associate a method with the Done button
        finish
();
   
}

Для получения дополнительной информации см.:

,

После того как вы настроили свое приложение для обмена файлами с использованием URI контента, вы можете отвечать на запросы других приложений об этих файлах. Один из способов ответа на эти запросы — предоставить интерфейс выбора файлов из серверного приложения, который могут вызывать другие приложения. Этот подход позволяет клиентскому приложению разрешить пользователям выбирать файл из серверного приложения, а затем получать URI содержимого выбранного файла.

В этом уроке показано, как создать в приложении Activity выбора файлов, которое отвечает на запросы файлов.

Получать запросы файлов

Чтобы получать запросы файлов от клиентских приложений и отвечать URI контента, ваше приложение должно предоставить Activity выбора файла. Клиентские приложения запускают это Activity , вызывая startActivityForResult() с Intent содержащим действие ACTION_PICK . Когда клиентское приложение вызывает startActivityForResult() , ваше приложение может вернуть результат клиентскому приложению в виде URI контента для файла, выбранного пользователем.

О том, как реализовать запрос файла в клиентском приложении, читайте в уроке Запрос общего файла .

Создание действия по выбору файлов

Чтобы настроить Activity выбора файла, начните с указания Activity в вашем манифесте вместе с фильтром намерений, соответствующим действию ACTION_PICK и категориям CATEGORY_DEFAULT и CATEGORY_OPENABLE . Также добавьте фильтры типов MIME для файлов, которые ваше приложение передает другим приложениям. В следующем фрагменте показано, как указать новый фильтр Activity и намерений:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...
       
<application>
        ...
           
<activity
               
android:name=".FileSelectActivity"
               
android:label="@File Selector" >
               
<intent-filter>
                   
<action
                       
android:name="android.intent.action.PICK"/>
                   
<category
                       
android:name="android.intent.category.DEFAULT"/>
                   
<category
                       
android:name="android.intent.category.OPENABLE"/>
                   
<data android:mimeType="text/plain"/>
                   
<data android:mimeType="image/*"/>
               
</intent-filter>
           
</activity>

Определить действие выбора файла в коде

Затем определите подкласс Activity , который отображает файлы, доступные из каталога files/images/ вашего приложения во внутренней памяти, и позволяет пользователю выбрать нужный файл. В следующем фрагменте показано, как определить это Activity и ответить на выбор пользователя:

class MainActivity : Activity() {

   
// The path to the root of this app's internal storage
   
private lateinit var privateRootDir: File
   
// The path to the "images" subdirectory
   
private lateinit var imagesDir: File
   
// Array of files in the images subdirectory
   
private lateinit var imageFiles: Array<File>
   
// Array of filenames corresponding to imageFiles
   
private lateinit var imageFilenames: Array<String>

   
// Initialize the Activity
   
override fun onCreate(savedInstanceState: Bundle?) {
       
...
       
// Set up an Intent to send back to apps that request a file
        resultIntent
= Intent("com.example.myapp.ACTION_RETURN_FILE")
       
// Get the files/ subdirectory of internal storage
        privateRootDir
= filesDir
       
// Get the files/images subdirectory;
        imagesDir
= File(privateRootDir, "images")
       
// Get the files in the images subdirectory
        imageFiles
= imagesDir.listFiles()
       
// Set the Activity's result to null to begin with
        setResult
(Activity.RESULT_CANCELED, null)
       
/*
         * Display the file names in the ListView fileListView.
         * Back the ListView with the array imageFilenames, which
         * you can create by iterating through imageFiles and
         * calling File.getAbsolutePath() for each File
         */

       
...
   
}
   
...
}
public class MainActivity extends Activity {
   
// The path to the root of this app's internal storage
   
private File privateRootDir;
   
// The path to the "images" subdirectory
   
private File imagesDir;
   
// Array of files in the images subdirectory
   
File[] imageFiles;
   
// Array of filenames corresponding to imageFiles
   
String[] imageFilenames;
   
// Initialize the Activity
   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
...
       
// Set up an Intent to send back to apps that request a file
        resultIntent
=
               
new Intent("com.example.myapp.ACTION_RETURN_FILE");
       
// Get the files/ subdirectory of internal storage
        privateRootDir
= getFilesDir();
       
// Get the files/images subdirectory;
        imagesDir
= new File(privateRootDir, "images");
       
// Get the files in the images subdirectory
        imageFiles
= imagesDir.listFiles();
       
// Set the Activity's result to null to begin with
        setResult
(Activity.RESULT_CANCELED, null);
       
/*
         * Display the file names in the ListView fileListView.
         * Back the ListView with the array imageFilenames, which
         * you can create by iterating through imageFiles and
         * calling File.getAbsolutePath() for each File
         */

         
...
   
}
   
...
}

Ответ на выбор файла

Как только пользователь выбирает общий файл, ваше приложение должно определить, какой файл был выбран, а затем сгенерировать URI контента для этого файла. Поскольку Activity отображает список доступных файлов в ListView , когда пользователь щелкает имя файла, система вызывает метод onItemClick() , в котором вы можете получить выбранный файл.

При использовании намерения отправить URI файла из одного приложения в другое вы должны быть осторожны, чтобы получить URI, который смогут прочитать другие приложения. Выполнение этого на устройствах под управлением Android 6.0 (уровень API 23) и более поздних версий требует особой осторожности из-за изменений в модели разрешений в этой версии Android, в частности, READ_EXTERNAL_STORAGE становится опасным разрешением , которого может не хватать принимающему приложению.

Учитывая эти соображения, мы рекомендуем избегать использования Uri.fromFile() , которое имеет несколько недостатков. Этот метод:

  • Не разрешает обмен файлами между профилями.
  • Требуется, чтобы у вашего приложения было разрешение WRITE_EXTERNAL_STORAGE на устройствах под управлением Android 4.4 (уровень API 19) или ниже.
  • Требуется, чтобы принимающие приложения имели разрешение READ_EXTERNAL_STORAGE , которое не будет работать на важных объектах общего доступа, таких как Gmail, у которых нет такого разрешения.

Вместо использования Uri.fromFile() вы можете использовать разрешения URI , чтобы предоставить другим приложениям доступ к определенным URI. Хотя разрешения URI не работают с file:// , созданными Uri.fromFile() , они работают с URI, связанными с поставщиками контента. API FileProvider может помочь вам создать такие URI. Этот подход также работает с файлами, которые находятся не во внешнем хранилище, а в локальном хранилище приложения, отправляющего намерение.

В onItemClick() получите объект File для имени выбранного файла и передайте его в качестве аргумента getUriForFile() вместе с полномочиями, указанными в элементе <provider> для FileProvider . Результирующий URI контента содержит полномочия, сегмент пути, соответствующий каталогу файла (как указано в метаданных XML), и имя файла, включая его расширение. Как FileProvider сопоставляет каталоги с сегментами пути на основе метаданных XML, описано в разделе «Указание общих каталогов» .

В следующем фрагменте показано, как обнаружить выбранный файл и получить для него URI содержимого:

    override fun onCreate(savedInstanceState: Bundle?) {
       
...
       
// Define a listener that responds to clicks on a file in the ListView
        fileListView
.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
           
/*
             * Get a File for the selected file name.
             * Assume that the file names are in the
             * imageFilename array.
             */

           
val requestFile = File(imageFilenames[position])
           
/*
             * Most file-related method calls need to be in
             * try-catch blocks.
             */

           
// Use the FileProvider to get a content URI
           
val fileUri: Uri? = try {
               
FileProvider.getUriForFile(
                       
this@MainActivity,
                       
"com.example.myapp.fileprovider",
                        requestFile
)
           
} catch (e: IllegalArgumentException) {
               
Log.e("File Selector",
                       
"The selected file can't be shared: $requestFile")
               
null
           
}
           
...
       
}
       
...
   
}
    protected void onCreate(Bundle savedInstanceState) {
       
...
       
// Define a listener that responds to clicks on a file in the ListView
        fileListView
.setOnItemClickListener(
               
new AdapterView.OnItemClickListener() {
           
@Override
           
/*
             * When a filename in the ListView is clicked, get its
             * content URI and send it to the requesting app
             */

           
public void onItemClick(AdapterView<?> adapterView,
                   
View view,
                   
int position,
                   
long rowId) {
               
/*
                 * Get a File for the selected file name.
                 * Assume that the file names are in the
                 * imageFilename array.
                 */

               
File requestFile = new File(imageFilename[position]);
               
/*
                 * Most file-related method calls need to be in
                 * try-catch blocks.
                 */

               
// Use the FileProvider to get a content URI
               
try {
                    fileUri
= FileProvider.getUriForFile(
                           
MainActivity.this,
                           
"com.example.myapp.fileprovider",
                            requestFile
);
               
} catch (IllegalArgumentException e) {
                   
Log.e("File Selector",
                         
"The selected file can't be shared: " + requestFile.toString());
               
}
               
...
           
}
       
});
       
...
   
}

Помните, что вы можете генерировать URI контента только для файлов, которые находятся в каталоге, указанном вами в файле метаданных, содержащем элемент <paths> , как описано в разделе «Указание общих каталогов» . Если вы вызываете getUriForFile() для File по пути, который вы не указали, вы получаете IllegalArgumentException .

Предоставить разрешения для файла

Теперь, когда у вас есть URI контента для файла, которым вы хотите поделиться с другим приложением, вам нужно разрешить клиентскому приложению доступ к файлу. Чтобы разрешить доступ, предоставьте разрешения клиентскому приложению, добавив URI контента в Intent , а затем установив флаги разрешений в Intent . Предоставляемые вами разрешения являются временными и истекают автоматически после завершения стека задач принимающего приложения.

В следующем фрагменте кода показано, как установить разрешение на чтение для файла:

    override fun onCreate(savedInstanceState: Bundle?) {
       
...
       
// Define a listener that responds to clicks on a file in the ListView
        fileListView
.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
           
...
           
if (fileUri != null) {
               
// Grant temporary read permission to the content URI
                resultIntent
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
               
...
           
}
           
...
       
}
       
...
   
}
    protected void onCreate(Bundle savedInstanceState) {
       
...
       
// Define a listener that responds to clicks in the ListView
        fileListView
.setOnItemClickListener(
               
new AdapterView.OnItemClickListener() {
           
@Override
           
public void onItemClick(AdapterView<?> adapterView,
                   
View view,
                   
int position,
                   
long rowId) {
               
...
               
if (fileUri != null) {
                   
// Grant temporary read permission to the content URI
                    resultIntent
.addFlags(
                       
Intent.FLAG_GRANT_READ_URI_PERMISSION);
               
}
               
...
             
}
             
...
       
});
   
...
   
}

Внимание: вызов setFlags() — единственный способ безопасно предоставить доступ к вашим файлам с использованием временных прав доступа. Избегайте вызова метода Context.grantUriPermission() для URI содержимого файла, поскольку этот метод предоставляет доступ, который вы можете отозвать только путем вызова Context.revokeUriPermission() .

Не используйте Uri.fromFile() . Он заставляет получать приложения иметь разрешение READ_EXTERNAL_STORAGE , вообще не будет работать, если вы пытаетесь поделиться информацией между пользователями, а в версиях Android ниже 4.4 (уровень API 19) вашему приложению потребуется WRITE_EXTERNAL_STORAGE . А действительно важные цели общего доступа, такие как приложение Gmail, не имеют READ_EXTERNAL_STORAGE , что приводит к сбою этого вызова. Вместо этого вы можете использовать разрешения URI, чтобы предоставить другим приложениям доступ к определенным URI. Хотя разрешения URI не работают с URI file://, генерируемыми Uri.fromFile() , они работают с URI, связанными с поставщиками контента. Вместо того, чтобы реализовывать свой собственный только для этого, вы можете и должны использовать FileProvider , как описано в разделе «Общий доступ к файлам» .

Поделитесь файлом с запрашивающим приложением

Чтобы поделиться файлом с приложением, которое его запросило, передайте Intent , содержащий URI контента и разрешения, в setResult() . Когда Activity которое вы только что определили, завершено, система отправляет Intent , содержащее URI контента, клиентскому приложению. Следующий фрагмент кода показывает, как это сделать:

    override fun onCreate(savedInstanceState: Bundle?) {
       
...
       
// Define a listener that responds to clicks on a file in the ListView
        fileListView
.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
           
...
           
if (fileUri != null) {
               
...
               
// Put the Uri and MIME type in the result Intent
                resultIntent
.setDataAndType(fileUri, contentResolver.getType(fileUri))
               
// Set the result
                setResult
(Activity.RESULT_OK, resultIntent)
           
} else {
                resultIntent
.setDataAndType(null, "")
                setResult
(RESULT_CANCELED, resultIntent)
           
}
       
}
   
}
    protected void onCreate(Bundle savedInstanceState) {
       
...
       
// Define a listener that responds to clicks on a file in the ListView
        fileListView
.setOnItemClickListener(
               
new AdapterView.OnItemClickListener() {
           
@Override
           
public void onItemClick(AdapterView<?> adapterView,
                   
View view,
                   
int position,
                   
long rowId) {
               
...
               
if (fileUri != null) {
                   
...
                   
// Put the Uri and MIME type in the result Intent
                    resultIntent
.setDataAndType(
                            fileUri
,
                            getContentResolver
().getType(fileUri));
                   
// Set the result
                   
MainActivity.this.setResult(Activity.RESULT_OK,
                            resultIntent
);
                   
} else {
                        resultIntent
.setDataAndType(null, "");
                       
MainActivity.this.setResult(RESULT_CANCELED,
                                resultIntent
);
                   
}
               
}
       
});

Предоставьте пользователям возможность немедленно вернуться в клиентское приложение после выбора файла. Один из способов сделать это — поставить галочку или кнопку «Готово» . Свяжите метод с кнопкой, используя атрибут кнопки android:onClick . В методе вызовите finish() . Например:

    fun onDoneClick(v: View) {
       
// Associate a method with the Done button
        finish
()
   
}
    public void onDoneClick(View v) {
       
// Associate a method with the Done button
        finish
();
   
}

Для получения дополнительной информации см.: