【发布时间】:2020-03-24 22:53:06
【问题描述】:
假设我正在开发一个聊天应用程序,它能够与他人共享任何类型的文件(没有 mimetype 限制):如图像、视频、文档,还包括压缩文件,如 zip、rar、apk 甚至不常见的文件例如 Photoshop 或 Autocad 文件等文件类型。
在 Android 9 或更低版本中,我直接将这些文件下载到下载目录,但现在在 Android 10 中,如果不向用户显示 Intent 以询问在哪里下载它们...
不可能?但是为什么谷歌浏览器或其他浏览器能够做到这一点呢?实际上,他们仍然在 Android 10 中无需询问用户即可将文件下载到下载目录。
我首先分析了 Whatsapp 以了解他们是如何实现的,但他们利用了 AndroidManifest 上的 requestLegacyExternalStorage 属性。但后来我分析了 Chrome,它针对 Android 10 而不使用 requestLegacyExternalStorage。这怎么可能?
我已经在谷歌上搜索了几天应用程序如何将文件直接下载到 Android 10 (Q) 上的下载目录,而无需询问用户将文件放在哪里,就像 Chrome 一样。
我已经阅读了面向开发人员的 android 文档、关于 Stackoverflow 的大量问题、互联网上的博客文章和 Google 群组,但我仍然没有找到一种方法来保持与 Android 9 中完全相同的方式,甚至没有找到足够多的解决方案满足我。
到目前为止我所做的尝试:
使用 ACTION_CREATE_DOCUMENT 意图打开 SAF 以请求许可,但显然无法静默打开它。始终打开一个 Activity 以询问用户将文件放在哪里。但是我应该在每个文件上打开这个 Intent 吗?我的应用程序可以在后台自动下载聊天文件。不是一个可行的解决方案。
-
在应用的开头使用 SAF 获取授权访问权限,其中 uri 指向下载内容的任何目录:
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); i = sm.getPrimaryStorageVolume().createOpenDocumentTreeIntent();请求用户许可是多么丑陋的行为,不是吗?尽管这不是谷歌浏览器所做的。
或者再次使用 ACTION_CREATE_DOCUMENT,保存我在 onActivityResult() 中获得的 Uri,并使用 grantPermission() 和 getContentResolver().takePersistableUriPermission()。但这不会创建目录而是创建文件。
我也尝试过获取 MediaStore.Downloads.INTERNAL_CONTENT_URI 或 MediaStore.Downloads.EXTERNAL_CONTENT_URI 并使用 Context.getContentResolver.insert() 保存文件,但是尽管它们被注释为 @NonNull,但真是巧合实际上返回... NULL
添加 requestLegacyExternalStorage="false" 作为我的 AndroidManifest.xml 的应用程序标签的属性。但这只是开发人员的一个补丁,以便在他们进行更改和调整代码之前获得时间。此外,这不是谷歌浏览器所做的。
getFilesDir() 和 getExternalFilesDir() 和 getExternalFilesDirs() 仍然可用,但卸载我的应用程序时,存储在这些目录中的文件会被删除。用户希望在卸载我的应用程序时保留他们的文件。对我来说再次不是一个可行的解决方案。
我的临时解决方案:
我找到了一种解决方法,无需添加 requestLegacyExternalStorage="false" 即可在任何地方下载。
它包括使用以下方法从 File 对象中获取 Uri:
val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(downloadDir, fileName)
val authority = "${context.packageName}.provider"
val accessibleUri = FileProvider.getUriForFile(context, authority, file)
有一个provider_paths.xml
<paths>
<external-path name="external_files" path="."/>
</paths>
并将其设置在 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>
问题:
它使用 getExternalStoragePublicDirectory 方法,该方法自 Android Q 起已弃用,极有可能在 Android 11 上被删除。您可以认为您可以手动创建自己的路径,因为您知道真实路径 (/storage/emulated/0 /Download/) 并继续创建 File 对象,但如果 Google 决定更改 Android 11 上的下载目录路径怎么办?
恐怕这不是一个长期的解决方案,所以
我的问题:
如何在不使用已弃用的方法的情况下实现此目的?还有一个额外的问题 Google Chrome 到底是如何实现访问下载目录的?
【问题讨论】:
-
这里的弃用说明中有一些替代方案:developer.android.com/reference/android/os/…
-
but that's now impossible in Android 10 without showing an Intent to the user to ask where to download them...不,一点也不。您仍然可以使用 getFilesDir() 和 getExternalFilesDir() 和 getExternalFilesDirs()。 -
在你的列表中我想念 ACTION_OPEN_DOCUMENT_TREE。
-
how apps can download a file directly to Download directory on Android 10 (Q) without having to ask user where to place it,。未经测试:DownloadManager 不会默认下载到该文件夹吗? -
"获取 MediaStore.Downloads.INTERNAL_CONTENT_URI 或 MediaStore.Downloads.EXTERNAL_CONTENT_URI 并使用 Context.getContentResolver.insert() 保存文件"应该是解决方案。您可以考虑使用 minimal reproducible example 提出一个单独的 Stack Overflow 问题,以展示您是如何尝试的。
标签: android google-chrome android-10.0