比方说我正在开发一个聊天应用程序,它可以与其他人共享任何类型的文件(没有 mime 类型限制):比如图像、视频、文档,还有压缩文件,比如 zip、rar、apk 或更不常见的文件类型例如,像 photoshop 或 autocad 文件。
在 Android 9 或更低版本中,我直接将这些文件下载到下载目录,但现在在 Android 10 中,如果不向用户显示 Intent 询问从哪里下载它们,这是不可能的……
不可能的?但为什么谷歌浏览器或其他浏览器能够做到这一点?他们实际上仍然在不询问 Android 10 中的用户的情况下将文件下载到下载目录。
我首先分析了 Whatsapp,看看他们是如何实现的,但他们使用了 AndroidManifest 上的 requestLegacyExternalStorage 属性。但后来我分析了 Chrome,它在不使用 requestLegacyExternalStorage 的情况下针对 Android 10。这怎么可能?
我已经在谷歌上搜索了几天 ,应用程序如何将文件直接下载到 Android 10 (Q) 上的下载目录, 而无需询问用户将文件放在哪里,就像 Chrome 一样。
我已经阅读了 android for developers 文档、关于 Stackoverflow 的许多问题、互联网上的博客文章和 Google Groups,但我仍然没有找到一种方法来继续做与 Android 9 完全相同的事情,甚至也没有找到让我满意的解决方案。
到目前为止我已经尝试过:
使用 ACTION_CREATE_DOCUMENT Intent 打开 SAF 以请求许可,但显然无法静默打开它。总是打开一个活动来询问用户将文件放在哪里。但是我应该在每个文件上打开这个 Intent 吗?我的应用程序可以在后台自动下载聊天文件。不是一个可行的解决方案。
在应用程序的开头使用 SAF 获取授权访问权限,其中 uri 指向下载内容的任何目录:
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
i = sm.getPrimaryStorageVolume().createOpenDocumentTreeIntent();
请求用户许可是多么丑陋的行为,不是吗?尽管这不是 Google Chrome 所做的。
或者再次使用 ACTION_CREATE_DOCUMENT,保存我在 onActivityResult() 中获得的 Uri 并使用 grantPermission() 和 getContentResolver().takePersistableUriPermission()。但这不会创建目录而是创建文件。
我还尝试获取 MediaStore.Downloads.INTERNAL_CONTENT_URI 或 MediaStore.Downloads.EXTERNAL_CONTENT_URI 并使用 Context.getContentResolver.insert() 保存文件,但多么巧合,尽管它们被注释为 @NonNull 它们实际上返回了……无效的
添加 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/ ) 并继续创建文件对象,但是如果 Google 决定更改 Android 11 上的下载目录路径怎么办?
恐怕这不是一个长期的解决方案,所以
我的问题:
如何在不使用已弃用的方法的情况下实现这一目标? 还有一个额外的问题,谷歌浏览器到底是如何获得对下载目录的访问权限的?
原文由 Rubén Viguera 发布,翻译遵循 CC BY-SA 4.0 许可协议
10个多月过去了,还没有给我满意的答复。所以我会回答我自己的问题。
正如@CommonsWare 在评论中所述,“获取 MediaStore.Downloads.INTERNAL_CONTENT_URI 或 MediaStore.Downloads.EXTERNAL_CONTENT_URI 并使用 Context.getContentResolver.insert() 保存文件” 应该是解决方案。我仔细检查了一下,发现这是真的,我说它不起作用是错误的。但…
我发现使用 ContentResolver 很棘手,而且我无法使其正常工作。我会用它提出一个单独的问题,但我一直在调查并找到了一个令人满意的解决方案。
我的解决方案:
基本上你必须 下载到你的应用程序拥有的任何目录 ,然后复制到 Downloads folder 。
配置您的应用程序:
准备将文件下载到您的应用程序拥有的任何目录,例如 getFilesDir()、getExternalFilesDir()、getCacheDir() 或 getExternalCacheDir()。
下载后,您可以根据需要对该文件进行任何威胁。
将其复制到下载文件夹:
优点:
下载的文件在应用程序卸载后仍然存在
还可以让您在下载时了解其进度
移动后您仍然可以从您的应用程序打开它们,因为该文件仍然属于您的应用程序。
Android Q+ 不需要 write_external_storage 权限,只是为了这个目的:
缺点:
如果这种方法对您来说足够了,请尝试一下。