Android: отримання URI файлу з URI вмісту?


133

У моїй програмі користувач повинен вибрати аудіофайл, який програма потім обробляє. Проблема полягає в тому, що для того, щоб програма робила те, що я хочу, щоб це робилося зі звуковими файлами, мені потрібен 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

Відповіді:


153

Просто використовуйте getContentResolver().openInputStream(uri)для отримання InputStreamURI.

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


12
Перевірте схему повернення URI, отриманого вами від вибору діяльності. Якщо uri.getScheme.equals ("вміст"), відкрийте його за допомогою роздільної здатності вмісту. Якщо uri.Scheme.equals ("файл"), відкрийте його, використовуючи звичайні файлові методи. У будь-якому випадку ви отримаєте InputStream, який ви можете обробити, використовуючи загальний код.
Джейсон ЛеБрун

16
Насправді я просто перечитував документи для getContentResolver (). OpenInputStream (), і він працює автоматично для схем "вмісту" або "файлу", тому вам не потрібно перевіряти схему ... якщо можете сміливо припустимо, що це буде завжди вміст: // або файл: // тоді openInputStream () завжди працюватиме.
Джейсон Лебрун

57
Чи є спосіб отримати файл замість InputStream (із вмісту: ...)?
AlikElzin-kilaka

4
@kilaka Ви можете отримати шлях до файлу, але це боляче. Дивіться stackoverflow.com/a/20559418/294855
Данял Айтеккін

7
Ця відповідь недостатня для того, хто використовує API із закритим кодом, який спирається на файли, а не на FileStreams, але все ж хоче використовувати ОС, щоб дозволити користувачеві вибрати файл. Відповідь @DanyalAytekin, на яку я посилався, була саме тим, що мені потрібно (і насправді мені вдалося обрізати багато жиру, оскільки я точно знаю, з якими типами файлів я працюю).
monkey0506

45

Це стара відповідь із застарілим і хитрим способом подолання певних больових точок, що вирішують контент. Візьміть його з величезними зернами солі та використовуйте належний API openInputStream, якщо це можливо.

Ви можете використовувати 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 Drive
bluewhile

1
Іноді працює, іноді повертає файл: /// storage / emulated / 0 / ... який не існує.
Реза Мохаммаді

1
Чи android.provider.MediaStore.Images.ImageColumns.DATAзавжди гарантія існування стовпця "_data" ( ), якщо схема є content://?
Едвард Фолк

2
Це основна антидіаграма. Деякі ContentProviders надають цей стовпчик, але вам не гарантується доступ до читання / запису, Fileколи ви намагаєтесь обійти ContentResolver. Використовуйте методи ContentResolver для роботи на content://урисі, це офіційний підхід, який заохочують інженери 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-карти за допомогою цього методу, лише читати чи відкривати d-зображення, використовуючи цей абсолютний шлях. Якщо ви знайшли рішення видалити за допомогою цього методу, будь ласка, поділіться. ФЛЕШ-КАРТКА

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

Якщо ви хочете отримати Урі file:///після використання шляху

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

я не думаю, що це правильний спосіб зробити це в додатку. на жаль, я використовую це у швидкому проекті
Бондакс,

@Bondax Так, вам слід працювати з Content Uris замість шляхів до файлів або File Uris. Таким чином введено рамку доступу до пам’яті. Але якщо ви хочете отримати файл uri, це найбільш правильний спосіб інших відповідей, оскільки ви використовуєте клас DocumentsContract. Якщо ви перевірите зразки Google у Github, ви побачите, що вони також використовують для цього класу для отримання підпапок папки.
фракійський

Клас DocumentFile також нове доповнення в api 19 і те, як ви використовуєте uris від SAF. Правильний спосіб - це використовувати стандартний шлях для вашої програми та попросити користувача дати дозвіл на папку через SAF ui, зберегти рядок Uri для спільних налаштувань, а коли потрібен доступ до папки з об’єктами DocumentFile
фракійський

7

Спроба обробити URI з контентом content: // за допомогою виклику ContentResolver.query()не є хорошим рішенням. У HTC Desire під керуванням 4.2.2 ви можете отримати NULL як результат запиту.

Чому б не використати ContentResolver замість цього? https://stackoverflow.com/a/29141800/3205334


Але іноді нам потрібен лише шлях. Нам не потрібно завантажувати файл у пам'ять.
Кімі Чіу

2
"Шлях" марний, якщо ви не маєте на нього прав доступу. Наприклад, якщо програма додає вам content://урі, відповідний файлу у його приватному внутрішньому каталозі, ви не зможете використовувати цей урі з FileAPI в нових версіях Android. ContentResolver призначений для подолання такого роду обмежень безпеки. Якщо ви отримали урі від ContentResolver, можна очікувати, що він просто спрацює.
user1643723

5

Натхненні відповіді - Джейсон Лабрун та Дарт Рейвен . Спроба вже відповідей підходів привела мене до рішення нижче, яке в основному може охоплювати нульові випадки курсора та перетворення з вмісту: // у файл: //

Щоб конвертувати файл, прочитайте та запишіть файл із отриманого урі

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

У наведеній вище відповіді я перетворив відео-урі в масив байтів, але це не пов’язано з питанням, я просто скопіював свій повний код, щоб показати використання FileInputStreamта InputStreamяк обидва працюють у своєму коді однаково.

Я використовував змінний контекст, який є getActivity () у своєму фрагменті, а в Activity це просто ActivityName.this

context=getActivity(); // у Фрагменті

context=ActivityName.this;// у діяльності


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