mic*_*ael 14 android android-intent android-file storage-access-framework android-afilechooser
一开始,用户可以使用新的存储访问框架选择文件(假设应用程序是API> 19):
https://developer.android.com/guide/topics/providers/document-provider.html
然后通过保存URI来保存对所选文件的引用,如下所示:
content://com.android.providers.downloads.documments/document/745
Run Code Online (Sandbox Code Playgroud)
(在这种情况下,文件来自默认下载dir`).
稍后,我想让用户打开这些文件(例如,他们的名字显示在UI列表中,用户选择一个).
我想用Android着名的意图选择器功能做到这一点,我所拥有的只是上面的URI对象......
谢谢,
编辑:我修改了这个答案,以包含我最初称为“编写专门的 ContentProvider”的方法示例代码。这应该完全满足问题的要求。可能会使答案太大,但它现在具有内部代码依赖性,所以让我们将其保留为整体。主要观点仍然成立:如果需要,请使用下面的 ContentPrvder,但尝试将file://Uris 提供给支持它们的应用程序,除非您想因某人的应用程序崩溃而受到指责。
原答案
\n\n我会像现在一样远离存储访问框架。它没有得到 Google 的充分支持,而且对应用程序的支持也很糟糕,因此很难区分这些应用程序中的错误和 SAF 本身的错误。如果您有足够的信心(这实际上意味着“可以比普通 Android 开发人员更好地使用 try-catch 块”),请自己使用存储访问框架,但仅将良好的旧file://路径传递给其他人。
您可以使用以下技巧从 ParcelFileDescriptor 获取文件系统路径(您可以通过调用openFileDescriptor从 ContentResolver 获取它):
\n\nclass FdCompat {\n public static String getFdPath(ParcelFileDescriptor fd) {\n final String resolved;\n\n try {\n final File procfsFdFile = new File("/proc/self/fd/" + fd.getFd());\n\n if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n // Returned name may be empty or "pipe:", "socket:", "(deleted)" etc.\n resolved = Os.readlink(procfsFdFile.getAbsolutePath());\n } else {\n // Returned name is usually valid or empty, but may start from\n // funny prefix if the file does not have a name\n resolved = procfsFdFile.getCanonicalPath();\n }\n\n if (TextUtils.isEmpty(resolved) || resolved.charAt(0) != \'/\'\n || resolved.startsWith("/proc/") || resolved.startsWith("/fd/"))\n return null;\n } catch (IOException ioe) {\n // This exception means, that given file DID have some name, but it is \n // too long, some of symlinks in the path were broken or, most\n // likely, one of it\'s directories is inaccessible for reading.\n // Either way, it is almost certainly not a pipe.\n return "";\n } catch (Exception errnoe) {\n // Actually ErrnoException, but base type avoids VerifyError on old versions\n // This exception should be VERY rare and means, that the descriptor\n // was made unavailable by some Unix magic.\n return null;\n }\n\n return resolved;\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n您必须做好准备,上面的方法将返回 null(该文件是管道或套接字,这是完全合法的)或空路径(无法读取文件的父目录)。如果发生这种情况,请将整个流复制到您可以访问的某个目录。
\n\n完整的解决方案
\n\n如果您确实想继续使用内容提供商 Uris,那么您就来吧。拿下面ContentProvider的代码来说。粘贴到您的应用程序中(并在 AndroidManifest 中注册)。使用getShareableUri下面的方法将收到的存储访问框架 Uri 转换为您自己的。将该 Uri 传递给其他应用程序而不是原始 Uri。
下面的代码是不安全的(您可以轻松地使其安全,但解释这一点会导致这个答案的长度超出想象)。如果您介意的话,请使用file://Uris\xe2\x80\x94Linux 文件系统被广泛认为足够安全。
扩展下面的解决方案以提供没有相应 Uri 的任意文件描述符,留给读者作为练习。
\n\npublic class FdProvider extends ContentProvider {\n private static final String ORIGINAL_URI = "o";\n private static final String FD = "fd";\n private static final String PATH = "p";\n\n private static final Uri BASE_URI = \n Uri.parse("content://com.example.fdhelper/");\n\n // Create an Uri from some other Uri and (optionally) corresponding\n // file descriptor (if you don\'t plan to close it until your process is dead).\n public static Uri getShareableUri(@Nullable ParcelFileDescriptor fd,\n Uri trueUri) {\n String path = fd == null ? null : FdCompat.getFdPath(fd);\n String uri = trueUri.toString();\n\n Uri.Builder builder = BASE_URI.buildUpon();\n\n if (!TextUtils.isEmpty(uri))\n builder.appendQueryParameter(ORIGINAL_URI, uri);\n\n if (fd != null && !TextUtils.isEmpty(path))\n builder.appendQueryParameter(FD, String.valueOf(fd.getFd()))\n .appendQueryParameter(PATH, path);\n\n return builder.build();\n }\n\n public boolean onCreate() { return true; }\n\n public ParcelFileDescriptor openFile(Uri uri, String mode)\n throws FileNotFoundException {\n\n String o = uri.getQueryParameter(ORIGINAL_URI);\n String fd = uri.getQueryParameter(FD);\n String path = uri.getQueryParameter(PATH);\n\n if (TextUtils.isEmpty(o)) return null;\n\n // offer the descriptor directly, if our process still has it\n try {\n if (!TextUtils.isEmpty(fd) && !TextUtils.isEmpty(path)) {\n int intFd = Integer.parseInt(fd);\n\n ParcelFileDescriptor desc = ParcelFileDescriptor.fromFd(intFd);\n\n if (intFd >= 0 && path.equals(FdCompat.getFdPath(desc))) {\n return desc;\n }\n }\n } catch (RuntimeException | IOException ignore) {}\n\n // otherwise just forward the call\n try {\n Uri trueUri = Uri.parse(o);\n\n return getContext().getContentResolver()\n .openFileDescriptor(trueUri, mode);\n }\n catch (RuntimeException ignore) {}\n\n throw new FileNotFoundException();\n }\n\n // all other calls are forwarded the same way as above\n public Cursor query(Uri uri, String[] projection, String selection,\n String[] selectionArgs, String sortOrder) {\n\n String o = uri.getQueryParameter(ORIGINAL_URI);\n\n if (TextUtils.isEmpty(o)) return null;\n\n try {\n Uri trueUri = Uri.parse(o);\n\n return getContext().getContentResolver().query(trueUri, projection,\n selection, selectionArgs, sortOrder);\n } catch (RuntimeException ignore) {}\n\n return null;\n }\n\n public String getType(Uri uri) {\n String o = uri.getQueryParameter(ORIGINAL_URI);\n\n if (TextUtils.isEmpty(o)) return "*/*";\n\n try {\n Uri trueUri = Uri.parse(o);\n\n return getContext().getContentResolver().getType(trueUri);\n } catch (RuntimeException e) { return null; }\n }\n\n public Uri insert(Uri uri, ContentValues values) {\n return null;\n }\n\n public int delete(Uri uri, String selection, String[] selectionArgs) {\n return 0;\n }\n\n public int update(Uri uri, ContentValues values, String selection,\n String[] selectionArgs) { return 0; }\n}\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
7092 次 |
| 最近记录: |