android - 如何在 Android 11 上获取给定树 URI 的文件路径?
问题描述
只有当用户使用存储访问框架选择文件夹时,我们才会获得树 URI。文档提到从 Android 11 开始,我们可以使用原始文件路径来访问文件。我需要这个,以便我可以使用现有的本机库。但是我找不到任何关于在给定树 URI 的情况下获取原始路径的文档。如何在 Android 11 上获取给定树 URI 的文件路径?
请注意,stackoverflow 上的所有现有答案都是 hack,它们现在甚至都不起作用。我需要一个官方的 API。
解决方案
对于 API >= 29,存储访问选项发生了巨大变化,由于限制、所需权限等,即使有文件路径也没有多大用处。
API 30 (Andorid 11) 的文档说明可以使用较旧的 File API,指的是这样的 API 现在可以用于访问/处理位于“共享存储”文件夹中的文件,因此 DCIM、音乐、等等。在这些位置之外,文件 API 将无法工作,在 Android 10 中也不会。
请注意,Android 11 和所有未来版本中的 File API 速度要慢得多,因为已被重写为包装器,因此不再是 File system API。Underneath 现在将所有操作委托给 MediaStore。如果性能很重要,那么最好直接使用 MediaStore API。
我建议仅通过ContentResolver使用Uris / File Descriptors,并远离真实的文件路径。对于每个新的 Android 版本,任何解决方法都将是暂时的并且更难维护。
下一个示例(有点 hacky)可以帮助解析 API 29 和 30 中的文件路径;未在 29 以下测试。不是官方的,因此无法保证它适用于所有用例,我建议不要将其用于生产目的。在我的测试中,解析的路径与MediaScannerConnection可以正常工作,但它可能需要调整以处理其他要求。
@Nullable
public static File getFile(@NonNull final Context context, @NonNull final DocumentFile document)
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
{
return null;
}
try
{
final List<StorageVolume> volumeList = context
.getSystemService(StorageManager.class)
.getStorageVolumes();
if ((volumeList == null) || volumeList.isEmpty())
{
return null;
}
// There must be a better way to get the document segment
final String documentId = DocumentsContract.getDocumentId(document.getUri());
final String documentSegment = documentId.substring(documentId.lastIndexOf(':') + 1);
for (final StorageVolume volume : volumeList)
{
final String volumePath;
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q)
{
final Class<?> class_StorageVolume = Class.forName("android.os.storage.StorageVolume");
@SuppressWarnings("JavaReflectionMemberAccess")
@SuppressLint("DiscouragedPrivateApi")
final Method method_getPath = class_StorageVolume.getDeclaredMethod("getPath");
volumePath = (String)method_getPath.invoke(volume);
}
else
{
// API 30
volumePath = volume.getDirectory().getPath();
}
final File storageFile = new File(volumePath + File.separator + documentSegment);
// Should improve with other checks, because there is the
// remote possibility that a copy could exist in a different
// volume (SD-card) under a matching path structure and even
// same file name, (maybe a user's backup in the SD-card).
// Checking for the volume Uuid could be an option but
// as per the documentation the Uuid can be empty.
final boolean isTarget = (storageFile.exists())
&& (storageFile.lastModified() == document.lastModified())
&& (storageFile.length() == document.length());
if (isTarget)
{
return storageFile;
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
推荐阅读
- batch-file - 无法使用批处理文件自动输入响应
- jmeter - jmeter中xml响应中第1行和第1列的非法字符
- azure - 通过用户名自定义 Azure B2C 密码重置流程
- c++ - 如何使用 Gradle MSBuild 插件运行 SonarQube 扫描仪?
- android - 无法在单个 dex 文件中容纳请求的类(# 方法:72443 > 65536)
- c# - 是否可以使 HashSet 成为 Dictionary 的子类?
- python - SQLCODE=-30081 无法使用 python 连接到 IBM DB2 数据库
- node.js - NPM Start 总是要求我安装 Expo CLI,而我已经在全局安装了
- javascript - 如何使用 API 将 lambda 函数的输出返回到 web-front 搜索
- spring-boot - Spring Boot 手动确认 kafka 消息不起作用