Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/android/179.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Android:使用存储访问框架获得的URI中的意向选择器打开文件_Android_Android Intent_Android File_Storage Access Framework_Android Afilechooser - Fatal编程技术网

Android:使用存储访问框架获得的URI中的意向选择器打开文件

Android:使用存储访问框架获得的URI中的意向选择器打开文件,android,android-intent,android-file,storage-access-framework,android-afilechooser,Android,Android Intent,Android File,Storage Access Framework,Android Afilechooser,开始时,用户可以使用新的存储访问框架选择文件(假设应用程序API>19): 然后通过保存URI保存对这些选定文件的引用,该URI如下所示: content://com.android.providers.downloads.documments/document/745 (在本例中,该文件来自默认下载目录“” 稍后,我想让用户打开这些文件(例如,它们的名称显示在UI列表中,用户选择一个) 我想用Android著名的意图选择器功能来实现这一点,我所拥有的只是上面的URI对象 谢谢,编辑:我已经

开始时,用户可以使用新的存储访问框架选择文件(假设应用程序API>19):

然后通过保存URI保存对这些选定文件的引用,该URI如下所示:

content://com.android.providers.downloads.documments/document/745
(在本例中,该文件来自默认下载目录“”

稍后,我想让用户打开这些文件(例如,它们的名称显示在UI列表中,用户选择一个)

我想用Android著名的意图选择器功能来实现这一点,我所拥有的只是上面的URI对象


谢谢,

编辑:我已经修改了这个答案,加入了我最初提到的“编写专业内容提供者”方法的示例代码。这应该完全满足问题的要求。答案可能太大了,但它现在有内部代码依赖关系,所以让我们把它作为一个整体。要点仍然是正确的:如果你愿意,可以使用下面的ContentPrvder,但是尝试为支持它们的应用程序提供
文件://
URI,除非你想因为某人的应用程序崩溃而受到指责

原始答案


我会像现在这样远离存储访问框架。它没有得到谷歌足够的支持,应用程序的支持也非常糟糕,因此很难区分这些应用程序中的bug和SAF本身。如果您有足够的信心(这实际上意味着“可以比普通Android开发人员更好地使用try-catch block”),请自己使用Storage Access Framework,但只将好的
旧文件://
路径传递给其他人

您可以使用以下技巧从ParcelFileDescriptor获取文件系统路径(您可以通过调用从ContentResolver获取该路径):

您必须准备好,上面的方法将返回null(文件是完全合法的管道或套接字)或空路径(对文件的父目录没有读取权限)。如果发生这种情况,请将整个流复制到您可以访问的某个目录

完整解决方案


如果你真的想坚持使用内容提供商URI,那就来吧。以下面ContentProvider的代码为例。粘贴到应用程序中(并在AndroidManifest中注册)。使用下面的
getShareableUri
方法将收到的存储访问框架Uri转换为您自己的Uri。将该Uri传递给其他应用程序,而不是原始Uri

下面的代码是不安全的(您可以很容易地使其安全,但解释这一点会使这个答案的长度超出想象)。如果您愿意,请使用
file://
Uris Linux文件系统被广泛认为足够安全

扩展下面的解决方案以提供没有相应Uri的任意文件描述符,留给读者作为练习

public class FdProvider extends ContentProvider {
 private static final String ORIGINAL_URI = "o";
 private static final String FD = "fd";
 private static final String PATH = "p";

 private static final Uri BASE_URI = 
     Uri.parse("content://com.example.fdhelper/");

 // Create an Uri from some other Uri and (optionally) corresponding
 // file descriptor (if you don't plan to close it until your process is dead).
 public static Uri getShareableUri(@Nullable ParcelFileDescriptor fd,
                                   Uri trueUri) {
     String path = fd == null ? null : FdCompat.getFdPath(fd);
     String uri = trueUri.toString();

     Uri.Builder builder = BASE_URI.buildUpon();

     if (!TextUtils.isEmpty(uri))
         builder.appendQueryParameter(ORIGINAL_URI, uri);

     if (fd != null && !TextUtils.isEmpty(path))
         builder.appendQueryParameter(FD, String.valueOf(fd.getFd()))
                .appendQueryParameter(PATH, path);

     return builder.build();
 }

 public boolean onCreate() { return true; }

 public ParcelFileDescriptor openFile(Uri uri, String mode)
     throws FileNotFoundException {

     String o = uri.getQueryParameter(ORIGINAL_URI);
     String fd = uri.getQueryParameter(FD);
     String path = uri.getQueryParameter(PATH);

     if (TextUtils.isEmpty(o)) return null;

     // offer the descriptor directly, if our process still has it
     try {
         if (!TextUtils.isEmpty(fd) && !TextUtils.isEmpty(path)) {
             int intFd = Integer.parseInt(fd);

             ParcelFileDescriptor desc = ParcelFileDescriptor.fromFd(intFd);

             if (intFd >= 0 && path.equals(FdCompat.getFdPath(desc))) {
                 return desc;
             }
         }
     } catch (RuntimeException | IOException ignore) {}

     // otherwise just forward the call
     try {
         Uri trueUri = Uri.parse(o);

         return getContext().getContentResolver()
             .openFileDescriptor(trueUri, mode);
     }
     catch (RuntimeException ignore) {}

     throw new FileNotFoundException();
 }

 // all other calls are forwarded the same way as above
 public Cursor query(Uri uri, String[] projection, String selection,
     String[] selectionArgs, String sortOrder) {

     String o = uri.getQueryParameter(ORIGINAL_URI);

     if (TextUtils.isEmpty(o)) return null;

     try {
         Uri trueUri = Uri.parse(o);

         return getContext().getContentResolver().query(trueUri, projection,
             selection, selectionArgs, sortOrder);
     } catch (RuntimeException ignore) {}

     return null;
 }

 public String getType(Uri uri) {
     String o = uri.getQueryParameter(ORIGINAL_URI);

     if (TextUtils.isEmpty(o)) return "*/*";

     try {
         Uri trueUri = Uri.parse(o);

         return getContext().getContentResolver().getType(trueUri);
     } catch (RuntimeException e) { return null; }
 }

 public Uri insert(Uri uri, ContentValues values) {
     return null;
 }

 public int delete(Uri uri, String selection, String[] selectionArgs) {
     return 0;
 }

 public int update(Uri uri, ContentValues values, String selection,
     String[] selectionArgs) { return 0; }
}

解决方案已经在SO上提供了,您只需搜索它

这是最新的。他编写了一个实用程序类,该类返回此类内容路径的完整文件路径

他说:

这将从MediaProvider、DownloadsProvider、, 和ExternalStorageProvider,同时又回到了非官方 您提到的ContentProvider方法

这些是从我的开源库中获取的


是Paul Burke编写您正在寻找的方法的地方。

您是否尝试过类似
newintent(Intent.ACTION\u VIEW,uri)的方法我尝试对文件选择器从Storage Access Framework返回的视频URI使用视图意图。它会导致一个错误:“无法为打开fd。”content://com.android.providers.media.documents/document/video:15026“这是行不通的。您有权使用该
Uri
;其他应用程序没有使用该
Uri
的权限。我已经看到了这个答案,但要让另一个应用程序处理用户选择的文件,需要做很多工作。我使用存储访问提供商提供的文件选择器使事情变得更简单,并避免使用文件选择器库。此外,这是该方法的另一个问题:我不推荐使用文件选择器库,这可能与主题无关。我的答案,或者实际上是Paul Burke的答案,是如何从各种URI获取文件URI。是的,我很感激!我的观点是,这个练习使得使用存储访问框架的理由非常薄弱。如果获取实际文件路径是使操作_viewintent工作的唯一解决方案,那么使用直接提供文件路径的选择器可能会更好。FileUtils硬代码文件路径和其他几个现有文件提供程序的“解决方案”,并检查
\u path
列,这至少是不可靠的(另请参阅解释原因)。任何新的Android版本以及设备供应商的任何轻微修改都可能破坏这些。安装自定义文件提供商(如替代文件管理器)是插件友好型存储访问框架结构的关键,它将使这些“解决方案”成为可能同样失败。我的答案中的代码将始终可靠地确定路径。还要注意,文件系统路径可能不可用,因为使用此代码的应用程序可能没有对指定位置的读取(更不用说写入)权限。@Commonware不是真的。我始终可以检测文件是否位于外部存储器上(或自己将其复制到那里)并检查目标应用程序是否已读取外部存储,以确保它会像对待任何其他应用程序一样对待指向外部存储的
文件://
Uri。对于
内容://
,没有这样的运气。安卓系统内容提供商很幸运(他们通常将文件存储在可访问的位置,并在
\u路径中分发),但定制的应用程序很容易出错。并不是每个人都像Google Drive那样有影响力让第三方应用程序自爆。大家好,我面临一个问题。我创建了一个这样的存储客户端和客户端。我想在(word应用程序或任何其他第三方应用程序)中使用存储提供商打开word文档。pl
public class FdProvider extends ContentProvider {
 private static final String ORIGINAL_URI = "o";
 private static final String FD = "fd";
 private static final String PATH = "p";

 private static final Uri BASE_URI = 
     Uri.parse("content://com.example.fdhelper/");

 // Create an Uri from some other Uri and (optionally) corresponding
 // file descriptor (if you don't plan to close it until your process is dead).
 public static Uri getShareableUri(@Nullable ParcelFileDescriptor fd,
                                   Uri trueUri) {
     String path = fd == null ? null : FdCompat.getFdPath(fd);
     String uri = trueUri.toString();

     Uri.Builder builder = BASE_URI.buildUpon();

     if (!TextUtils.isEmpty(uri))
         builder.appendQueryParameter(ORIGINAL_URI, uri);

     if (fd != null && !TextUtils.isEmpty(path))
         builder.appendQueryParameter(FD, String.valueOf(fd.getFd()))
                .appendQueryParameter(PATH, path);

     return builder.build();
 }

 public boolean onCreate() { return true; }

 public ParcelFileDescriptor openFile(Uri uri, String mode)
     throws FileNotFoundException {

     String o = uri.getQueryParameter(ORIGINAL_URI);
     String fd = uri.getQueryParameter(FD);
     String path = uri.getQueryParameter(PATH);

     if (TextUtils.isEmpty(o)) return null;

     // offer the descriptor directly, if our process still has it
     try {
         if (!TextUtils.isEmpty(fd) && !TextUtils.isEmpty(path)) {
             int intFd = Integer.parseInt(fd);

             ParcelFileDescriptor desc = ParcelFileDescriptor.fromFd(intFd);

             if (intFd >= 0 && path.equals(FdCompat.getFdPath(desc))) {
                 return desc;
             }
         }
     } catch (RuntimeException | IOException ignore) {}

     // otherwise just forward the call
     try {
         Uri trueUri = Uri.parse(o);

         return getContext().getContentResolver()
             .openFileDescriptor(trueUri, mode);
     }
     catch (RuntimeException ignore) {}

     throw new FileNotFoundException();
 }

 // all other calls are forwarded the same way as above
 public Cursor query(Uri uri, String[] projection, String selection,
     String[] selectionArgs, String sortOrder) {

     String o = uri.getQueryParameter(ORIGINAL_URI);

     if (TextUtils.isEmpty(o)) return null;

     try {
         Uri trueUri = Uri.parse(o);

         return getContext().getContentResolver().query(trueUri, projection,
             selection, selectionArgs, sortOrder);
     } catch (RuntimeException ignore) {}

     return null;
 }

 public String getType(Uri uri) {
     String o = uri.getQueryParameter(ORIGINAL_URI);

     if (TextUtils.isEmpty(o)) return "*/*";

     try {
         Uri trueUri = Uri.parse(o);

         return getContext().getContentResolver().getType(trueUri);
     } catch (RuntimeException e) { return null; }
 }

 public Uri insert(Uri uri, ContentValues values) {
     return null;
 }

 public int delete(Uri uri, String selection, String[] selectionArgs) {
     return 0;
 }

 public int update(Uri uri, ContentValues values, String selection,
     String[] selectionArgs) { return 0; }
}