安卓:从URI由存储访问架构获得的意向选择器打开文件 [英] Android: Open file with intent chooser from URI obtained by Storage Access Framework

查看:256
本文介绍了安卓:从URI由存储访问架构获得的意向选择器打开文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In the beginning the user can select files with the new Storage Access Framework (Assuming the app is API>19):

<一个href="https://developer.android.com/guide/topics/providers/document-provider.html">https://developer.android.com/guide/topics/providers/document-provider.html

然后我保存引用这些选择的文件通过保存它看起来像URI`s:

Then I save references to those chosen files by saving the URI`s which looks like:

content://com.android.providers.downloads.documments/document/745

(在此情况下,该文件是从默认的下载dir`)。

(in this case the file is from the default downloads dir`).

后来,欲让用户打开那些文件(例如,在UI中显示的列表中他们的名称,且用户选择一个)。

Later, I want let the user to open those files (For example they names displayed in UI list, and the user selects one).

我要的是Android著名的意图选择器功能做到这一点,我只有上面的URI对象...

I want to do this with the Android famous intent chooser feature, and all I have is the above URI object...

谢谢

推荐答案

编辑:我已经修改这个答案,包括方法的例子code我已初步被称为写专门的ContentProvider。这应完全满足问题的要求。也许使得答案太大,但它的内部code依赖现在,让我们把它作为一个整体。最主要的一点仍然是成立的:使用,如果你想下面的ContentPrvder,而是尽量给文件:// URI来的应用程序,支持它们,除非你想被指责有人的应用程序崩溃。

I have revised this answer to include the example code of approach I have initially referred to as "writing a specialized ContentProvider". This should fully satisfy requirements of the question. Probably makes the answer too big, but it has internal code dependencies now, so let's leave it as whole. The main point still holds true: use the ContentPrvder below if you want, but try to give file:// Uris to apps, that support them, unless you want to be blamed for someone's app crashing.

原来的答复

我会远离存储访问架构因为它是现在。它的不足由谷歌的支持,并在应用程序的支持是非常糟糕,使得它很难在这些应用程序和苏丹武装部队本身的错误时的判断。如果你有足够的信心(这实际上意味着可以使用try-catch块优于平均Android开发者),使用存储访问架构自己,但传递给其他人唯一的好,老文件:// 的路径。

I would stay away from Storage Access Framework as it is now. It's insufficiently backed by Google, and the support in apps is abysmal, making it is hard to tell between bugs in those apps and SAF itself. If you are confident enough (which really means "can use try-catch block better then average Android developer"), use Storage Access Framework yourself, but pass to others only good-old file:// paths.

您可以使用下面的技巧,以获得ParcelFileDescriptor文件系统路径(你可以从ContentResolver的得到它通过调用<一href="https://developer.android.com/reference/android/content/ContentResolver.html#openFileDescriptor%28android.net.Uri,%20java.lang.String%29"相对=nofollow> openFileDescriptor ):

You can use the following trick to get filesystem path from ParcelFileDescriptor (you can get it from ContentResolver by calling openFileDescriptor):

class FdCompat {
 public static String getFdPath(ParcelFileDescriptor fd) {
  final String resolved;

  try {
   final File procfsFdFile = new File("/proc/self/fd/" + fd.getFd());

   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    // Returned name may be empty or "pipe:", "socket:", "(deleted)" etc.
    resolved = Os.readlink(procfsFdFile.getAbsolutePath());
   } else {
    // Returned name is usually valid or empty, but may start from
    // funny prefix if the file does not have a name
    resolved = procfsFdFile.getCanonicalPath();
   }

  if (TextUtils.isEmpty(resolved) || resolved.charAt(0) != '/'
                || resolved.startsWith("/proc/") || resolved.startsWith("/fd/"))
   return null;
  } catch (IOException ioe) {
   // This exception means, that given file DID have some name, but it is 
   // too long, some of symlinks in the path were broken or, most
   // likely, one of it's directories is inaccessible for reading.
   // Either way, it is almost certainly not a pipe.
   return "";
  } catch (Exception errnoe) {
   // Actually ErrnoException, but base type avoids VerifyError on old versions
   // This exception should be VERY rare and means, that the descriptor
   // was made unavailable by some Unix magic.
   return null;
  }

  return resolved;
 }
}

您必须是prepared,上述方法将返回null(该文件是一个管道或插座,这是完全合法的)或空路径(以文件的父目录中没有读访问)。如果发生这种情况的的整个流拷贝到某个目录,您可以访问的。

You must be prepared, that the method above will return null (the file is a pipe or socket, which is perfectly legitimate) or an empty path (no read access to file's parent directory). If this happens copy the entire stream to some directory you can access.

完整的解决方案

如果你真的想坚持与内容提供商是URI,在这里你去。采取的ContentProvider以下的code。粘贴到您的应用程序(和AndroidManifest注册它)。下面使用 getShareableUri 方法来转换接收到的存储访问架构开放的到你自己的。传递URI来其他应用程序,而不是原来的URI。

If you really want to stick with content provider Uris, here you go. Take the code of ContentProvider below. Paste into your app (and register it in AndroidManifest). Use getShareableUri method below to convert received Storage Access Framework Uri into your own. Pass that Uri to other apps instead of the original Uri.

在code以下是不安全的(你可以很容易地使安全的,但解释说,将扩大超乎想象这个答案的长度)。如果你关心,使用文件:// 尤里斯-Linux文件系统被广泛认为是足够安全的。

The code below is insecure (you can easily make it secure, but explaining that would expand the length of this answer beyond imagination). If you care, use file:// Uris—Linux file systems are widely considered secure enough.

扩展低于溶液中以提供任意的文件描述符而没有相应的Uri留作练习读者

Extending the solution below to provide arbitrary file descriptors without corresponding Uri is left as exercise for reader.

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; }
}

这篇关于安卓:从URI由存储访问架构获得的意向选择器打开文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆