Partager un fichier

Une fois que vous avez configuré votre application pour partager des fichiers à l'aide d'URI de contenu, vous pouvez répondre aux demandes d'autres applications concernant ces fichiers. Une façon de répondre à ces requêtes consiste à fournir à partir de l'application serveur une interface de sélection de fichiers que d'autres applications peuvent appeler. Cette approche permet à une application cliente de permettre aux utilisateurs de sélectionner un fichier sur l'application serveur, puis de recevoir l'URI de contenu du fichier sélectionné.

Cette leçon vous explique comment créer une sélection de fichiers Activity dans votre application qui répond aux requêtes de fichiers.

Recevoir des demandes de fichiers

Pour recevoir des requêtes de fichiers provenant d'applications clientes et répondre avec un URI de contenu, votre application doit fournir une sélection de fichiers Activity. Les applications clientes démarrent ce Activity en appelant startActivityForResult() avec un Intent contenant l'action ACTION_PICK. Lorsque l'application cliente appelle startActivityForResult(), elle peut renvoyer un résultat à l'application cliente, sous la forme d'un URI de contenu pour le fichier sélectionné par l'utilisateur.

Pour découvrir comment implémenter une requête pour un fichier dans une application cliente, consultez la leçon Demander un fichier partagé.

Créer une activité de sélection de fichiers

Pour configurer la sélection de fichiers Activity, commencez par spécifier Activity dans votre fichier manifeste, ainsi qu'un filtre d'intent qui correspond à l'action ACTION_PICK et aux catégories CATEGORY_DEFAULT et CATEGORY_OPENABLE. Ajoutez également des filtres de type MIME pour les fichiers diffusés par votre application vers d'autres applications. L'extrait de code suivant vous montre comment spécifier le nouveau Activity et le nouveau filtre d'intent:

<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>

Définir l'activité de sélection de fichiers dans le code

Ensuite, définissez une sous-classe Activity qui affiche les fichiers disponibles dans le répertoire files/images/ de votre application dans la mémoire de stockage interne et permet à l'utilisateur de choisir le fichier souhaité. L'extrait de code suivant montre comment définir ce Activity et répondre à la sélection de l'utilisateur:

Kotlin

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
         */
        ...
    }
    ...
}

Java

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
         */
         ...
    }
    ...
}

Répondre à une sélection de fichiers

Une fois qu'un utilisateur a sélectionné un fichier partagé, votre application doit déterminer quel fichier a été sélectionné, puis générer un URI de contenu pour ce fichier. Étant donné que Activity affiche la liste des fichiers disponibles dans un ListView, lorsque l'utilisateur clique sur un nom de fichier, le système appelle la méthode onItemClick(), dans laquelle vous pouvez obtenir le fichier sélectionné.

Lorsque vous utilisez un intent pour envoyer l'URI d'un fichier d'une application à une autre, vous devez veiller à obtenir un URI que les autres applications peuvent lire. Cette opération sur les appareils équipés d'Android 6.0 (niveau d'API 23) ou version ultérieure nécessite une attention particulière, en raison des modifications apportées au modèle d'autorisations dans cette version d'Android. En effet, READ_EXTERNAL_STORAGE devient une autorisation dangereuse que l'application réceptrice pourrait manquer.

En tenant compte de ces considérations, nous vous recommandons d'éviter d'utiliser Uri.fromFile(), qui présente plusieurs inconvénients. Cette méthode permet d'effectuer les opérations suivantes:

  • Interdit le partage de fichiers entre les profils.
  • Votre application doit disposer de l'autorisation WRITE_EXTERNAL_STORAGE sur les appareils équipés d'Android 4.4 (niveau d'API 19) ou version antérieure.
  • Nécessite que les applications réceptrices disposent de l'autorisation READ_EXTERNAL_STORAGE, qui échouera sur les cibles de partage importantes, telles que Gmail, qui ne disposent pas de cette autorisation.

Au lieu d'utiliser Uri.fromFile(), vous pouvez utiliser les autorisations d'URI pour accorder à d'autres applications l'accès à des URI spécifiques. Bien que les autorisations d'URI ne fonctionnent pas sur les URI file:// générés par Uri.fromFile(), elles fonctionnent sur les URI associés aux fournisseurs de contenu. L'API FileProvider peut vous aider à créer ces URI. Cette approche fonctionne également avec les fichiers qui ne se trouvent pas dans le stockage externe, mais dans le stockage local de l'application qui envoie l'intent.

Dans onItemClick(), récupérez un objet File pour le nom du fichier sélectionné et transmettez-le en tant qu'argument à getUriForFile(), ainsi que l'autorité que vous avez spécifiée dans l'élément <provider> pour FileProvider. L'URI de contenu obtenu contient l'autorité, un segment de chemin correspondant au répertoire du fichier (comme spécifié dans les métadonnées XML) et le nom du fichier, y compris son extension. La section Spécifier des répertoires partageables explique comment FileProvider mappe des répertoires avec des segments de chemin d'accès basés sur des métadonnées XML.

L'extrait de code suivant vous montre comment détecter le fichier sélectionné et obtenir un URI de contenu pour celui-ci:

Kotlin

    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
            }
            ...
        }
        ...
    }

Java

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

N'oubliez pas que vous ne pouvez générer des URI de contenu que pour les fichiers qui se trouvent dans un répertoire que vous avez spécifié dans le fichier de métadonnées contenant l'élément <paths>, comme décrit dans la section Spécifier des répertoires partageables. Si vous appelez getUriForFile() pour une File dans un chemin que vous n'avez pas spécifié, vous recevez une IllegalArgumentException.

Accorder des autorisations sur le fichier

Maintenant que vous disposez d'un URI de contenu pour le fichier que vous souhaitez partager avec une autre application, vous devez autoriser l'application cliente à y accéder. Pour accorder l'accès, accordez des autorisations à l'application cliente en ajoutant l'URI de contenu à un Intent, puis en définissant des indicateurs d'autorisation sur le Intent. Les autorisations que vous accordez sont temporaires et expirent automatiquement lorsque la pile de tâches de l'application réceptrice est terminée.

L'extrait de code suivant vous montre comment définir l'autorisation de lecture pour le fichier:

Kotlin

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

Java

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

Attention:Appeler setFlags() est le seul moyen d'accorder un accès sécurisé à vos fichiers à l'aide d'autorisations d'accès temporaires. Évitez d'appeler la méthode Context.grantUriPermission() pour l'URI de contenu d'un fichier, car elle accorde un accès que vous ne pouvez révoquer qu'en appelant Context.revokeUriPermission().

N'utilisez pas Uri.fromFile(). Elle oblige les applications reçues à avoir l'autorisation READ_EXTERNAL_STORAGE. Elle ne fonctionnera pas du tout si vous essayez de partager des éléments entre plusieurs utilisateurs. De plus, dans les versions d'Android antérieures à la version 4.4 (niveau d'API 19), votre application devra disposer de WRITE_EXTERNAL_STORAGE. De plus, les cibles de partage très importantes, telles que l'application Gmail, ne disposent pas de READ_EXTERNAL_STORAGE, ce qui entraîne l'échec de cet appel. À la place, vous pouvez utiliser des autorisations d'URI pour autoriser d'autres applications à accéder à des URI spécifiques. Bien que les autorisations d'URI ne fonctionnent pas sur les URI file:// générés par Uri.fromFile(), elles fonctionnent avec les URI associés aux fournisseurs de contenu. Plutôt que d'implémenter votre propre outil uniquement pour cela, vous pouvez et devriez utiliser FileProvider, comme expliqué dans la section Partage de fichiers.

Partager le fichier avec l'application à l'origine de la demande

Pour partager le fichier avec l'application qui l'a demandé, transmettez le Intent contenant l'URI du contenu et les autorisations à setResult(). Lorsque le Activity que vous venez de définir est terminé, le système envoie à l'application cliente le Intent contenant l'URI du contenu. L'extrait de code suivant vous montre comment procéder:

Kotlin

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

Java

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

Offrez aux utilisateurs la possibilité de revenir immédiatement à l'application cliente une fois qu'ils ont choisi un fichier. Pour ce faire, vous pouvez cocher une case ou cliquer sur le bouton Done (OK). Associez une méthode au bouton à l'aide de l'attribut android:onClick du bouton. Dans la méthode, appelez finish(). Par exemple :

Kotlin

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

Java

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

Pour en savoir plus, consultez les ressources suivantes: