Подтвердить что ты не робот

Android: открыть файл с выбором намерений из URI, полученный платформой доступа к хранилищу

Вначале пользователь может выбирать файлы с новой платформой доступа к хранилищу (предполагая, что приложение API > 19):

https://developer.android.com/guide/topics/providers/document-provider.html

Затем я сохраняю ссылки на эти выбранные файлы, сохраняя символы URI, которые выглядят следующим образом:

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

(в этом случае файл находится из файла загрузки по умолчанию).

Позже я хочу, чтобы пользователь открыл эти файлы (например, имена, отображаемые в списке пользовательского интерфейса, и пользователь выбирает один).

Я хочу сделать это с помощью функции выбора избранного Android, и все, что у меня есть, это выше URI-объект...

Спасибо,

4b9b3361

Ответ 1

Изменить: Я пересмотрел этот ответ, чтобы включить пример кода подхода, который я изначально называл "написанием специализированного ContentProvider". Это должно полностью удовлетворять требованиям этого вопроса. Вероятно, делает ответ слишком большим, но теперь он имеет внутренние кодовые зависимости, поэтому давайте оставим его целиком. Главное остается по-прежнему: используйте ContentPrvder ниже, если хотите, но попытайтесь предоставить file:// Uris приложениям, которые их поддерживают, если вы не хотите, чтобы вас обвиняли в сбое приложения.

Оригинальный ответ


Я бы держался подальше от Storage Access Framework, как сейчас. Он недостаточно подкреплен Google, и поддержка в приложениях ужасна, поэтому сложно сказать между ошибками в этих приложениях и самой SAF. Если вы достаточно уверены (что на самом деле означает "лучше использовать блок try-catch лучше среднего разработчика Android" ), используйте Storage Access Framework самостоятельно, но передавайте другим только добрые пути file://.

Вы можете использовать следующий трюк, чтобы получить путь к файловой системе из ParcelFileDescriptor (вы можете получить его из ContentResolver, вызвав 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 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;
 }
}

Вы должны быть готовы к тому, что приведенный выше метод вернет null (файл - это труба или сокет, который является совершенно законным) или пустой путь (нет доступа для чтения к родительскому каталогу файлов). Если это произойдет, скопируйте весь поток в какой-либо каталог, к которому вы можете получить доступ.

Полное решение


Если вы действительно хотите придерживаться поставщика контента Uris, здесь вы идете. Ниже приведен код ContentProvider. Вставьте в свое приложение (и зарегистрируйте его в AndroidManifest). Используйте метод getShareableUri ниже, чтобы преобразовать полученную инфраструктуру доступа к хранилищу 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; }
}

Ответ 2

Ну, решение уже было предоставлено на SO, и вам нужно было только его искать.

Это ответ Пола Берка. Он написал класс утилиты, который возвращает полный путь к файлу для такого пути содержимого.

Он говорит:

Это даст путь к файлу из MediaProvider, DownloadsProvider, и ExternalStorageProvider, возвращаясь к неофициальному ContentProvider, который вы упомянули.

и

Они взяты из моей библиотеки с открытым исходным кодом, aFileChooser.

FileUtils.java - это место, где Пол Берк написал метод, который вы ищете.