问题描述
在 Android 4.4 (KitKat) 上,Google 已将对 SD 卡的访问非常受限.
On Android 4.4 (KitKat), Google has made access to the SD card quite restricted.
从 Android Lollipop (5.0) 开始,开发人员可以使用新的 API,要求用户确认允许访问特定文件夹,如 此 Google 网上论坛帖子 .
As of Android Lollipop (5.0), developers can use a new API that asks the user to confirm to allow access to specific folders, as written on the this Google-Groups post .
该帖子会引导您访问两个网站:
The post directs you to visit two websites:
这看起来像是一个内部示例(也许稍后会在 API 演示中展示),但很难理解发生了什么.
This looks like an inner example (perhaps to be shown on the API demos later), but it's quite hard to understand what's going on.
这是新 API 的官方文档,但没有详细说明如何使用它.
This is the official documentation of the new API, but it doesn't tell enough details about how to use it.
这是它告诉你的:
如果您确实需要完全访问整个文档子树,首先启动 ACTION_OPEN_DOCUMENT_TREE 让用户选择一个目录.然后将结果 getData() 传递给 fromTreeUri(Context,Uri) 开始处理用户选择的树.
当您浏览 DocumentFile 实例的树时,您始终可以使用getUri() 获取表示基础文档的 Uri该对象,用于 openInputStream(Uri) 等
As you navigate the tree of DocumentFile instances, you can always use getUri() to obtain the Uri representing the underlying document for that object, for use with openInputStream(Uri), etc.
要在运行 KITKAT 或更早版本的设备上简化代码,您可以使用 fromFile(File) 模拟 DocumentsProvider 的行为.
To simplify your code on devices running KITKAT or earlier, you can use fromFile(File) which emulates the behavior of a DocumentsProvider.
问题
我有几个关于新 API 的问题:
The questions
I have a few questions about the new API:
- 你是如何真正使用它的?
- 根据帖子,操作系统会记住该应用已获得访问文件/文件夹的权限.您如何检查是否可以访问文件/文件夹?是否有一个函数可以返回我可以访问的文件/文件夹列表?
- 你们如何处理 Kitkat 上的这个问题?它是支持库的一部分吗?
- 操作系统上是否有设置屏幕显示哪些应用可以访问哪些文件/文件夹?
- 如果在同一设备上为多个用户安装了一个应用,会发生什么情况?
- 是否有关于此新 API 的任何其他文档/教程?
- 可以撤销权限吗?如果是,是否有发送到应用的意图?
- 是否会在选定的文件夹上递归地请求权限?
- 使用该权限是否也可以让用户有机会根据用户的选择进行多项选择?或者应用是否需要明确告知 Intent 允许哪些文件/文件夹?
- 模拟器上有没有办法尝试新的 API?我的意思是,它有 SD 卡分区,但它用作主要的外部存储设备,因此已经授予了对它的所有访问权限(使用简单的权限).
- 当用户用另一张 SD 卡替换 SD 卡时会发生什么?
推荐答案
很多好问题,让我们深入挖掘.:)
Lots of good questions, let's dig in. :)
这是在 KitKat 中与存储访问框架交互的很棒的教程:
Here's a great tutorial for interacting with the Storage Access Framework in KitKat:
https://developer.android.com/guide/主题/providers/document-provider.html#client
与 Lollipop 中的新 API 交互非常相似.要提示用户选择目录树,您可以启动这样的意图:
Interacting with the new APIs in Lollipop is very similar. To prompt the user to pick a directory tree, you can launch an intent like this:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, 42);
然后在您的 onActivityResult() 中,您可以将用户选择的 Uri 传递给新的 DocumentFile 帮助程序类.这是一个简单的示例,它列出了所选目录中的文件,然后创建了一个新文件:
Then in your onActivityResult(), you can pass the user-picked Uri to the new DocumentFile helper class. Here's a quick example that lists the files in the picked directory, and then creates a new file:
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (resultCode == RESULT_OK) {
Uri treeUri = resultData.getData();
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
// List all existing files inside picked directory
for (DocumentFile file : pickedDir.listFiles()) {
Log.d(TAG, "Found file " + file.getName() + " with size " + file.length());
}
// Create a new file and write into it
DocumentFile newFile = pickedDir.createFile("text/plain", "My Novel");
OutputStream out = getContentResolver().openOutputStream(newFile.getUri());
out.write("A long time ago...".getBytes());
out.close();
}
}
DocumentFile.getUri()
返回的 Uri 足够灵活,可以与可能不同的平台 API 一起使用.例如,您可以使用 Intent.setData()
和 Intent.FLAG_GRANT_READ_URI_PERMISSION
共享它.
The Uri returned by DocumentFile.getUri()
is flexible enough to use with may different platform APIs. For example, you could share it using Intent.setData()
with Intent.FLAG_GRANT_READ_URI_PERMISSION
.
如果您想从本机代码访问该 Uri,您可以调用 ContentResolver.openFileDescriptor()
然后使用 ParcelFileDescriptor.getFd()
或 detachFd()
获取传统的 POSIX 文件描述符整数.
If you want to access that Uri from native code, you can call ContentResolver.openFileDescriptor()
and then use ParcelFileDescriptor.getFd()
or detachFd()
to obtain a traditional POSIX file descriptor integer.
默认情况下,通过存储访问框架意图返回的 Uris 不会在重新启动后保持不变.平台提供"了持久化权限的能力,但如果你愿意,你仍然需要获取"权限.在我们上面的示例中,您将调用:
By default, the Uris returned through Storage Access Frameworks intents are not persisted across reboots. The platform "offers" the ability to persist the permission, but you still need to "take" the permission if you want it. In our example above, you'd call:
getContentResolver().takePersistableUriPermission(treeUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
您始终可以通过 ContentResolver.getPersistedUriPermissions()
API 确定您的应用可以访问哪些持久授权.如果您不再需要访问持久化的 Uri,您可以使用 ContentResolver.releasePersistableUriPermission()
释放它.
You can always figure out what persisted grants your app has access to through the ContentResolver.getPersistedUriPermissions()
API. If you no longer need access to a persisted Uri, you can release it with ContentResolver.releasePersistableUriPermission()
.
不,我们不能向旧版本的平台追溯添加新功能.
No, we can't retroactively add new functionality to older versions of the platform.
目前没有显示此内容的 UI,但您可以在 adb shell dumpsys 活动提供程序
输出的授予的 Uri 权限"部分中找到详细信息.
There's currently no UI that shows this, but you can find the details in the "Granted Uri Permissions" section of adb shell dumpsys activity providers
output.
Uri 权限授予是基于每个用户的,就像所有其他多用户平台功能一样.也就是说,在两个不同用户下运行的同一个应用程序没有重叠或共享的 Uri 权限授予.
Uri permission grants are isolated on a per-user basis, just like all other multi-user platform functionality. That is, the same app running under two different users has no overlaping or shared Uri permission grants.
支持 DocumentProvider 可以随时撤销权限,例如删除基于云的文档时.发现这些撤销权限的最常见方法是当它们从上述 ContentResolver.getPersistedUriPermissions()
中消失时.
The backing DocumentProvider can revoke permission at any time, such as when a cloud-based document is deleted. The most common way to discover these revoked permissions is when they disappear from ContentResolver.getPersistedUriPermissions()
mentioned above.
每当涉及授权的任一应用的应用数据被清除时,权限也会被撤销.
Permissions are also revoked whenever app data is cleared for either app involved in the grant.
是的,ACTION_OPEN_DOCUMENT_TREE
意图使您可以递归访问现有和新创建的文件和目录.
Yep, the ACTION_OPEN_DOCUMENT_TREE
intent gives you recursive access to both existing and newly created files and directories.
是的,从 KitKat 开始支持多选,您可以通过在启动 ACTION_OPEN_DOCUMENT
意图时设置 EXTRA_ALLOW_MULTIPLE
来允许它.您可以使用 Intent.setType()
或 EXTRA_MIME_TYPES
来缩小可以选择的文件类型:
Yep, multiple selection has been supported since KitKat, and you can allow it by setting EXTRA_ALLOW_MULTIPLE
when starting your ACTION_OPEN_DOCUMENT
intent. You can use Intent.setType()
or EXTRA_MIME_TYPES
to narrow the types of files that can be picked:
http://developer.android.com/reference/android/content/Intent.html#ACTION_OPEN_DOCUMENT
是的,主要共享存储设备应该出现在选择器中,即使在模拟器上也是如此.如果您的应用仅使用存储访问框架来访问共享存储,则您不再需要READ/WRITE_EXTERNAL_STORAGE
权限,并且可以删除它们或使用 android:maxSdkVersion
功能仅在旧平台版本上请求它们.
Yep, the primary shared storage device should appear in the picker, even on the emulator. If your app only uses the Storage Access Framework for accessing shared storage, you no longer need the READ/WRITE_EXTERNAL_STORAGE
permissions at all and can remove them or use the android:maxSdkVersion
feature to only request them on older platform versions.
当涉及物理介质时,底层介质的UUID(如FAT序列号)总是被烧入返回的Uri中.系统使用它来将您连接到用户最初选择的媒体,即使用户在多个插槽之间交换媒体也是如此.
When physical media is involved, the UUID (such as FAT serial number) of the underlying media is always burned into the returned Uri. The system uses this to connect you to the media that the user originally selected, even if the user swaps the media around between multiple slots.
如果用户换入第二张卡,您需要提示获取新卡的使用权.由于系统会根据每个 UUID 记住授权,因此如果用户稍后重新插入,您将继续拥有先前授予的对原始卡的访问权限.
If the user swaps in a second card, you'll need to prompt to gain access to the new card. Since the system remembers grants on a per-UUID basis, you'll continue to have previously-granted access to the original card if the user reinserts it later.
http://en.wikipedia.org/wiki/Volume_serial_number
这篇关于如何使用为 Android 5.0 (Lollipop) 提供的新 SD 卡访问 API?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!