web-dev-qa-db-fra.com

Comment télécharger directement un fichier dans le répertoire de téléchargement sur Android Q (Android 10)

Disons que je développe une application de chat capable de partager avec d'autres N'IMPORTE QUEL type de fichiers (pas de restriction de type MIME): comme des images, des vidéos, des documents, mais aussi des fichiers compressés comme Zip, rar, apk ou encore des types de fichiers moins fréquents comme les fichiers photoshop ou autocad, par exemple.

Dans Android 9 ou inférieur, je télécharge directement ces fichiers dans le répertoire de téléchargement, mais cela est désormais impossible dans Android 10 sans montrer l'intention à l'utilisateur de demander où les télécharger ...

Impossible? mais alors pourquoi Google Chrome ou d'autres navigateurs sont capables de le faire? En fait, ils téléchargent toujours des fichiers dans le répertoire de téléchargement sans demander à l'utilisateur dans Android 10.

J'ai d'abord analysé Whatsapp pour voir comment ils y parviennent, mais ils utilisent l'attribut requestLegacyExternalStorage sur AndroidManifest. Mais ensuite j'ai analysé Chrome et il cible Android 10 sans utiliser requestLegacyExternalStorage. Comment est-ce possible?

Je recherche sur Google depuis quelques jours déjà comment les applications peuvent télécharger un fichier directement dans le répertoire de téléchargement sur Android 10 (Q) sans avoir à demander à l'utilisateur où le placer, de la même manière Chrome le fait.

J'ai lu Android pour la documentation des développeurs, beaucoup de questions sur Stackoverflow, des articles de blog sur Internet et Google Groupes mais je n'ai toujours pas trouvé un moyen de continuer à faire exactement la même chose que dans Android 9 ni même une solution qui me satisfait pleinement.

Ce que j'ai essayé jusqu'à présent:

  • Ouvrez SAF avec une ACTION_CREATE_DOCUMENT intention de demander la permission mais apparemment il n'y a aucun moyen de l'ouvrir silencieusement. Une activité est toujours ouverte pour demander à l'utilisateur où placer le fichier. Mais suis-je censé ouvrir cette intention sur chaque fichier? Mon application peut télécharger automatiquement les fichiers de discussion en arrière-plan. Pas une solution réalisable.

  • Obtenez un accès autorisé en utilisant SAF au début de l'application avec un uri pointant vers n'importe quel répertoire pour le contenu du téléchargement:

        StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
        i = sm.getPrimaryStorageVolume().createOpenDocumentTreeIntent();
    

    Quelle activité laide de demander la permission à l'utilisateur, n'est-ce pas? Même si ce n'est PAS ce que fait Google Chrome.

  • Ou encore en utilisant ACTION_CREATE_DOCUMENT, enregistrez l'URI que j'obtiens dans onActivityResult () et utilisez grantPermission () et getContentResolver (). TakePersistableUriPermission (). Mais cela ne crée pas un répertoire mais un fichier.

  • J'ai également essayé d'obtenir MediaStore.Downloads.INTERNAL_CONTENT_URI ou MediaStore.Downloads.EXTERNAL_CONTENT_URI et enregistrer un fichier en utilisant Context.getContentResolver.insert (), mais quelle coïncidence bien qu'ils soient annotés comme @NonNull, ils retournent en fait ... NUL

  • Ajout de requestLegacyExternalStorage = "false" comme attribut du libellé d'application de mon AndroidManifest.xml. Mais ce n'est qu'un patch pour les développeurs afin de gagner du temps jusqu'à ce qu'ils apportent des modifications et adaptent leur code. De plus, ce n'est pas ce que fait Google Chrome.

  • getFilesDir () et getExternalFilesDir () et getExternalFilesDirs () sont toujours disponibles mais les fichiers stockés dans ces répertoires sont supprimés lorsque mon application est désinstallée. Les utilisateurs s'attendent à conserver leurs fichiers lors de la désinstallation de mon application. Encore une fois, ce n'est pas une solution réalisable pour moi.

Ma solution temporaire:

J'ai trouvé une solution de contournement qui permet de télécharger où vous voulez sans ajouter requestLegacyExternalStorage = "false".

Elle consiste à obtenir un Uri à partir d'un objet File en utilisant:

val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(downloadDir, fileName)
val authority = "${context.packageName}.provider"
val accessibleUri = FileProvider.getUriForFile(context, authority, file)

Avoir un provider_paths.xml

<paths>
    <external-path name="external_files" path="."/>
</paths>

Et le paramétrer sur AndroidManifest.xml:

<provider
    Android:name="androidx.core.content.FileProvider"
    Android:authorities="${applicationId}.provider"
    Android:exported="false"
    Android:grantUriPermissions="true">
    <meta-data
        Android:name="Android.support.FILE_PROVIDER_PATHS"
        Android:resource="@xml/provider_paths" />
</provider>

Le problème:

Il utilise la méthode getExternalStoragePublicDirectory qui est obsolète à partir de Android Q et sera probablement supprimée sur Android 11. Vous pourriez penser que vous pouvez créer votre propre chemin manuellement car vous connaissez le vrai chemin (/ stockage/émulé/0/Télécharger /) et continuer à créer un objet Fichier, mais que se passe-t-il si Google décide de modifier le chemin du répertoire de téléchargement sur Android 11?

J'ai bien peur que ce ne soit pas une solution à long terme, donc

Ma question:

Comment puis-je y parvenir sans utiliser une méthode obsolète? Et une question bonus Comment diable Google Chrome parvient-il à accéder au répertoire de téléchargement?

6
Rubén Viguera

Vous pouvez utiliser le Android DownloadManager.Request. Il aura besoin du WRITE_EXTERNAL_STORAGE Persmission jusqu'à Android 9. À partir de Android 10/Q et au-dessus, il n'aura besoin d'aucune autorisation (il semble qu'il gère lui-même l'autorisation)).

Si vous souhaitez ouvrir le fichier par la suite, vous aurez besoin de l'autorisation de l'utilisateur à la place (également si vous ne souhaitez l'ouvrir que dans une application externe (par exemple, PDF-Reader).

Vous pouvez utiliser le gestionnaire de téléchargement comme ceci:

DownloadManager.Request request = new DownloadManager.Request(<download uri>);
request.addRequestHeader("Accept", "application/pdf");

// Save the file in the "Downloads" folder of SDCARD
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,filename);

DownloadManager downloadManager = (DownloadManager) mActivity.get()
                    .getSystemService(Context.DOWNLOAD_SERVICE);
downloadManager.enqueue(request);

Ce sont les références: https://developer.Android.com/reference/kotlin/Android/app/DownloadManager.Request?hl=en#setDestinationInExternalPublicDir (kotlin.String,% 20kotlin.String) - https://developer.Android.com/training/data-storage

2
beal