Android: получение URI файла из URI контента?


134

В моем приложении пользователь должен выбрать аудиофайл, который затем обрабатывает приложение. Проблема в том, что для того, чтобы приложение могло делать то, что я хочу, с аудиофайлами, мне нужно, чтобы URI был в формате файла. Когда я использую собственный музыкальный проигрыватель Android для поиска аудиофайла в приложении, URI представляет собой URI контента, который выглядит следующим образом:

content://media/external/audio/media/710

Однако, используя популярный файловый менеджер Astro, я получаю следующее:

file:///sdcard/media/audio/ringtones/GetupGetOut.mp3

Последнее гораздо более доступно для меня, но, конечно, я хочу, чтобы приложение имело функциональность с аудиофайлом, который выбирает пользователь, независимо от того, какую программу он использует для просмотра своей коллекции. Итак, мой вопрос: есть ли способ преобразовать content://URI стиля в file://URI? Иначе что бы вы мне посоветовали для решения этой проблемы? Вот код, который вызывает средство выбора для справки:

Intent ringIntent = new Intent();
ringIntent.setType("audio/mp3");
ringIntent.setAction(Intent.ACTION_GET_CONTENT);
ringIntent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(Intent.createChooser(ringIntent, "Select Ringtone"), SELECT_RINGTONE);

Я делаю следующее с URI контента:

m_ringerPath = m_ringtoneUri.getPath();
File file = new File(m_ringerPath);

Затем сделайте что-нибудь с FileInputStream с указанным файлом.


1
Какие вызовы вы используете, которые не любят URI контента?
Фил Лелло,

1
Существует много Uris контента, по которым вы не можете получить путь к файлу, потому что не все Uris контента имеют пути к файлу . Не используйте пути к файлам.
Mooing Duck

Ответы:


155

Просто используйте, getContentResolver().openInputStream(uri)чтобы получить InputStreamот URI.

http://developer.android.com/reference/android/content/ContentResolver.html#openInputStream(android.net.Uri)


13
Проверьте схему URI, возвращенного вам из действия выбора. Если uri.getScheme.equals ("content"), откройте его с помощью преобразователя содержимого. Если uri.Scheme.equals ("файл"), откройте его обычными файловыми методами. В любом случае вы получите InputStream, который можно будет обработать с помощью общего кода.
Джейсон ЛеБрун

16
На самом деле, я просто перечитал документы для getContentResolver (). OpenInputStream (), и он автоматически работает для схем «контент» или «файл», поэтому вам не нужно проверять схему ... если вы можете безопасно предположим, что он всегда будет content: // или file: //, тогда openInputStream () всегда будет работать.
Джейсон ЛеБрун,

57
Есть ли способ получить файл вместо InputStream (из содержимого: ...)?
AlikElzin-kilaka

4
@kilaka Можно получить путь к файлу, но это больно. См. Stackoverflow.com/a/20559418/294855
Даньял Айтекин

7
Этого ответа недостаточно для того, кто использует API с закрытым исходным кодом, который полагается на Files, а не на FileStreams, но все же хочет использовать ОС, чтобы позволить пользователю выбирать файл. Ответ, на который ссылается @DanyalAytekin, был именно тем, что мне было нужно (и на самом деле я смог избавиться от большого количества жира, потому что я точно знаю, с какими типами файлов я работаю).
monkey0506

45

Это старый ответ с устаревшим и хакерским способом преодоления некоторых конкретных болевых точек преобразователя контента. Возьмите это с некоторой долей скепсиса и используйте правильный API openInputStream, если это вообще возможно.

Вы можете использовать Content Resolver, чтобы получить file://путь из content://URI:

String filePath = null;
Uri _uri = data.getData();
Log.d("","URI = "+ _uri);                                       
if (_uri != null && "content".equals(_uri.getScheme())) {
    Cursor cursor = this.getContentResolver().query(_uri, new String[] { android.provider.MediaStore.Images.ImageColumns.DATA }, null, null, null);
    cursor.moveToFirst();   
    filePath = cursor.getString(0);
    cursor.close();
} else {
    filePath = _uri.getPath();
}
Log.d("","Chosen path = "+ filePath);

1
Спасибо, это сработало отлично. Я не мог использовать InputStream, как предполагает принятый ответ.
ldam

4
Это работает только для локальных файлов, например, не работает для Google Диска
bluewhile

1
Иногда работает, иногда возвращает file: /// storage / emulated / 0 / ... которого не существует.
Реза Мохаммади

1
Всегда ли столбец "_data" ( android.provider.MediaStore.Images.ImageColumns.DATA) гарантированно существует, если есть схема content://?
Эдвард Фальк

2
Это главный антипаттерн. Некоторые ContentProvider предоставляют этот столбец, но у вас не гарантируется доступ для чтения / записи Fileпри попытке обойти ContentResolver. Используйте методы ContentResolver для работы с content://uris, это официальный подход, рекомендованный инженерами Google.
user1643723

9

Если у вас есть Uri содержимого, content://com.externalstorage...вы можете использовать этот метод, чтобы получить абсолютный путь к папке или файлу на Android 19 или выше .

public static String getPath(final Context context, final Uri uri) {
    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

    // DocumentProvider
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
        System.out.println("getPath() uri: " + uri.toString());
        System.out.println("getPath() uri authority: " + uri.getAuthority());
        System.out.println("getPath() uri path: " + uri.getPath());

        // ExternalStorageProvider
        if ("com.android.externalstorage.documents".equals(uri.getAuthority())) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];
            System.out.println("getPath() docId: " + docId + ", split: " + split.length + ", type: " + type);

            // This is for checking Main Memory
            if ("primary".equalsIgnoreCase(type)) {
                if (split.length > 1) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1] + "/";
                } else {
                    return Environment.getExternalStorageDirectory() + "/";
                }
                // This is for checking SD Card
            } else {
                return "storage" + "/" + docId.replace(":", "/");
            }

        }
    }
    return null;
}

Вы можете проверить каждую часть Uri с помощью println. Возвращаемые значения для моей SD-карты и основной памяти устройства перечислены ниже. Вы можете получить доступ и удалить, если файл находится в памяти, но мне не удалось удалить файл с SD-карты с помощью этого метода, только чтение или открытие изображения с использованием этого абсолютного пути. Если вы нашли решение удалить с помощью этого метода, поделитесь. SD CARD

getPath() uri: content://com.android.externalstorage.documents/tree/612E-B7BF%3A/document/612E-B7BF%3A
getPath() uri authority: com.android.externalstorage.documents
getPath() uri path: /tree/612E-B7BF:/document/612E-B7BF:
getPath() docId: 612E-B7BF:, split: 1, type: 612E-B7BF

ОСНОВНАЯ ПАМЯТЬ

getPath() uri: content://com.android.externalstorage.documents/tree/primary%3A/document/primary%3A
getPath() uri authority: com.android.externalstorage.documents
getPath() uri path: /tree/primary:/document/primary:
getPath() docId: primary:, split: 1, type: primary

Если вы хотите получить Uri file:///после получения пути, используйте

DocumentFile documentFile = DocumentFile.fromFile(new File(path));
documentFile.getUri() // will return a Uri with file Uri

Я не думаю, что это правильный способ делать это в приложении. к сожалению, я использую его в быстром проекте
Bondax

@Bondax. Да, вы должны работать с Uris контента вместо путей к файлам или File Uris. Так вводится структура доступа к хранилищу. Но если вы хотите получить uri файла, это самый правильный способ других ответов, поскольку вы используете класс DocumentsContract. Если вы посмотрите образцы Google в Github, вы увидите, что они также используются в этом классе для получения подпапок папки.
Thracian

И класс DocumentFile также новое дополнение в api 19 и то, как вы используете uris из SAF. Правильный способ - использовать стандартный путь для вашего приложения и попросить пользователя предоставить разрешение для папки через пользовательский интерфейс SAF, сохранить строку Uri в общих настройках и, когда потребуется доступ к папке с объектами DocumentFile
Thracian

7

Попытка обработать URI с помощью схемы content: // путем вызова ContentResolver.query()- не лучшее решение. В HTC Desire под управлением 4.2.2 в результате запроса может быть получено NULL.

Почему бы вместо этого не использовать ContentResolver? https://stackoverflow.com/a/29141800/3205334


Но иногда нам нужен только путь. Нам действительно не нужно загружать файл в память.
Кими Чиу

2
«Путь» бесполезен, если у вас нет прав доступа к нему. Например, если приложение предоставляет вам content://uri, соответствующий файлу в его частном внутреннем каталоге, вы не сможете использовать этот uri с FileAPI в новых версиях Android. ContentResolver предназначен для преодоления такого рода ограничений безопасности. Если у вас есть uri от ContentResolver, вы можете ожидать, что он просто работает.
user1643723

5

Вдохновленные ответы - Джейсон ЛаБрун и Дарт Рэйвен . Попытка уже ответивших подходов привела меня к приведенному ниже решению, которое в основном может охватывать нулевые случаи курсора и преобразование из content: // в file: //

Чтобы преобразовать файл, прочтите и запишите файл из полученного uri

public static Uri getFilePathFromUri(Uri uri) throws IOException {
    String fileName = getFileName(uri);
    File file = new File(myContext.getExternalCacheDir(), fileName);
    file.createNewFile();
    try (OutputStream outputStream = new FileOutputStream(file);
         InputStream inputStream = myContext.getContentResolver().openInputStream(uri)) {
        FileUtil.copyStream(inputStream, outputStream); //Simply reads input to output stream
        outputStream.flush();
    }
    return Uri.fromFile(file);
}

Чтобы использовать имя файла, он будет охватывать нулевой регистр курсора

public static String getFileName(Uri uri) {
    String fileName = getFileNameFromCursor(uri);
    if (fileName == null) {
        String fileExtension = getFileExtension(uri);
        fileName = "temp_file" + (fileExtension != null ? "." + fileExtension : "");
    } else if (!fileName.contains(".")) {
        String fileExtension = getFileExtension(uri);
        fileName = fileName + "." + fileExtension;
    }
    return fileName;
}

Есть хороший вариант преобразования из типа MIME в расширение файла

 public static String getFileExtension(Uri uri) {
    String fileType = myContext.getContentResolver().getType(uri);
    return MimeTypeMap.getSingleton().getExtensionFromMimeType(fileType);
}

Курсор для получения имени файла

public static String getFileNameFromCursor(Uri uri) {
    Cursor fileCursor = myContext.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null);
    String fileName = null;
    if (fileCursor != null && fileCursor.moveToFirst()) {
        int cIndex = fileCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        if (cIndex != -1) {
            fileName = fileCursor.getString(cIndex);
        }
    }
    return fileName;
}

Спасибо, смотрю на это неделю. Не люблю копировать для этого файл, но это работает.
justdan0227

3

Я немного опоздал с ответом, но мой код проверен

проверить схему из uri:

 byte[] videoBytes;

if (uri.getScheme().equals("content")){
        InputStream iStream =   context.getContentResolver().openInputStream(uri);
            videoBytes = getBytes(iStream);
        }else{
            File file = new File(uri.getPath());
            FileInputStream fileInputStream = new FileInputStream(file);     
            videoBytes = getBytes(fileInputStream);
        }

В приведенном выше ответе я преобразовал URI видео в массив байтов, но это не связано с вопросом, я просто скопировал свой полный код, чтобы показать использование, FileInputStreamи InputStreamпоскольку оба работают одинаково в моем коде.

Я использовал переменный контекст, который является getActivity () в моем фрагменте, а в Activity это просто ActivityName. Это

context=getActivity(); // во фрагменте

context=ActivityName.this;// в действии


Я знаю, что это не связано с вопросом, но как бы вы тогда использовали byte[] videoBytes;? Большинство ответов показывают только, как использовать InputStreamс изображением.
KRK
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.