Android: Mendapatkan file URI dari konten URI?

133

Di aplikasi saya, pengguna harus memilih file audio yang kemudian ditangani oleh aplikasi. Masalahnya adalah agar aplikasi melakukan apa yang saya inginkan dengan file audio, saya perlu URI dalam format file. Ketika saya menggunakan pemutar musik asli Android untuk mencari file audio di aplikasi, URI adalah konten URI, yang terlihat seperti ini:

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

Namun, menggunakan aplikasi pengelola file populer Astro, saya mendapatkan yang berikut:

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

Yang terakhir ini jauh lebih mudah diakses untuk saya gunakan, tetapi tentu saja saya ingin aplikasi memiliki fungsi dengan file audio yang dipilih pengguna terlepas dari program yang mereka gunakan untuk menelusuri koleksi mereka. Jadi pertanyaan saya adalah, apakah ada cara untuk mengubah content://gaya URI menjadi file://URI? Kalau tidak, apa yang akan Anda rekomendasikan untuk saya untuk menyelesaikan masalah ini? Berikut adalah kode yang memanggil pemilih, untuk referensi:

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

Saya melakukan hal berikut dengan konten URI:

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

Kemudian lakukan beberapa hal FileInputStream dengan kata file.

JMRboosties
sumber
1
Panggilan apa yang Anda gunakan yang tidak suka URI konten?
Phil Lello
1
Ada banyak konten Uris di mana Anda tidak bisa mendapatkan jalur file, karena tidak semua konten Uris memiliki filepath. Jangan gunakan filepath.
Mooing Duck

Jawaban:

153

Cukup gunakan getContentResolver().openInputStream(uri)untuk mendapatkan InputStreamdari URI.

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

Jason LeBrun
sumber
12
Periksa skema URI yang dikembalikan kepada Anda dari aktivitas pemilih. Jika jika uri.getScheme.equals ("konten"), buka dengan pemecah konten. Jika uri.Scheme.equals ("file"), buka menggunakan metode file normal. Bagaimanapun, Anda akan berakhir dengan InputStream yang dapat Anda proses menggunakan kode umum.
Jason LeBrun
16
Sebenarnya, saya baru saja membaca ulang dokumen untuk getContentResolver (). OpenInputStream (), dan itu berfungsi secara otomatis untuk skema "konten" atau "file", jadi Anda tidak perlu memeriksa skema ... jika Anda dapat dengan aman berasumsi bahwa itu akan selalu menjadi konten: // atau file: // lalu openInputStream () akan selalu berfungsi.
Jason LeBrun
57
Apakah ada cara untuk mendapatkan File alih-alih InputStream (dari konten: ...)?
AlikElzin-kilaka
4
@ Kilaka Anda bisa mendapatkan path file tetapi menyakitkan. Lihat stackoverflow.com/a/20559418/294855
Danyal Aytekin
7
Jawaban ini tidak cukup untuk seseorang yang menggunakan API sumber tertutup yang bergantung pada File daripada FileStreams, tetapi ingin menggunakan OS untuk memungkinkan pengguna memilih file. Jawaban @DanyalAytekin direferensikan adalah persis apa yang saya butuhkan (dan pada kenyataannya, saya dapat memangkas banyak lemak karena saya tahu persis jenis file yang saya kerjakan).
monkey0506
45

Ini adalah jawaban lama dengan cara usang dan tidak rapi untuk mengatasi beberapa titik nyeri pemecah konten tertentu. Bawa dengan sejumlah besar garam dan gunakan API openInputStream yang tepat jika memungkinkan.

Anda dapat menggunakan Penyelesai Konten untuk mendapatkan file://lintasan dari 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);
Rafael Nobre
sumber
1
Terima kasih, ini bekerja dengan sempurna. Saya tidak bisa menggunakan InputStream seperti saran yang diterima.
ldam 8'13
4
Ini hanya berfungsi untuk file lokal, misalnya tidak berfungsi untuk Google Drive
bluewhile
1
Terkadang berfungsi, terkadang mengembalikan file: /// storage / emulated / 0 / ... yang tidak ada.
Reza Mohammadi
1
Apakah kolom "_data" ( android.provider.MediaStore.Images.ImageColumns.DATA) selalu dijamin ada jika skema itu content://?
Edward Falk
2
Ini adalah anti-pola utama. Beberapa ContentProviders menyediakan kolom itu, tetapi Anda tidak dijamin memiliki akses baca / tulis Fileketika Anda mencoba untuk memotong ContentResolver. Gunakan metode ContentResolver untuk beroperasi di content://uris, ini adalah pendekatan resmi, didorong oleh para insinyur Google.
user1643723
9

Jika Anda memiliki Uri konten, content://com.externalstorage...Anda dapat menggunakan metode ini untuk mendapatkan path absolut dari folder atau file di Android 19 atau lebih tinggi .

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

Anda dapat memeriksa setiap bagian dari Uri menggunakan println. Nilai yang dikembalikan untuk kartu SD dan memori utama perangkat saya tercantum di bawah ini. Anda dapat mengakses dan menghapus jika file ada di memori tetapi saya tidak dapat menghapus file dari kartu SD menggunakan metode ini, hanya membaca atau membuka gambar menggunakan jalur absolut ini. Jika Anda telah menemukan solusi untuk dihapus menggunakan metode ini, silakan bagikan. KARTU SD

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

MEMORI UTAMA

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

Jika Anda ingin mendapatkan Uri file:///setelah mendapatkan jalur gunakan

DocumentFile documentFile = DocumentFile.fromFile(new File(path));
documentFile.getUri() // will return a Uri with file Uri
Thracian
sumber
saya rasa itu bukan cara yang tepat untuk melakukannya dalam suatu aplikasi. sayangnya saya menggunakannya dalam proyek quicky
Bondax
@Bondax Ya, Anda harus bekerja dengan Content Uris sebagai ganti path file atau File Uris. Itulah cara kerangka akses penyimpanan diperkenalkan. Tetapi, jika Anda ingin mendapatkan file uri ini adalah cara paling benar dari jawaban lain karena Anda menggunakan kelas DocumentsContract. Jika Anda memeriksa sampel Google di Github, Anda akan melihat bahwa sampel itu juga digunakan untuk kelas ini untuk mendapatkan sub folder folder.
Thracian
Dan kelas DocumentFile juga tambahan baru di api 19 dan bagaimana Anda menggunakan uris dari SAF. Cara yang benar adalah dengan menggunakan jalur standar untuk aplikasi Anda dan meminta pengguna untuk memberikan izin untuk folder melalui SAF ui, menyimpan string Uri ke preferensi bersama, dan ketika dibutuhkan akses ke folder dengan objek DocumentFile
Thracian
7

Mencoba menangani URI dengan konten: // skema dengan menelepon ContentResolver.query()bukanlah solusi yang baik. Pada HTC Desire menjalankan 4.2.2 Anda bisa mendapatkan NULL sebagai hasil permintaan.

Mengapa tidak menggunakan ContentResolver saja? https://stackoverflow.com/a/29141800/3205334

Darth Raven
sumber
Tetapi kadang-kadang kita hanya membutuhkan jalan. Kami tidak benar-benar harus memuat file ke dalam memori.
Kimi Chiu
2
"Jalannya" tidak berguna jika Anda tidak memiliki hak akses untuk itu. Misalnya, jika aplikasi memberi Anda content://uri, yang sesuai dengan file di direktori internal pribadi, Anda tidak akan dapat menggunakan uri itu dengan FileAPI di versi Android yang baru. ContentResolver dirancang untuk mengatasi keterbatasan keamanan semacam ini. Jika Anda mendapatkan uri dari ContentResolver, Anda dapat mengharapkannya berfungsi.
user1643723
5

Jawaban yang diilhami adalah Jason LaBrun & Darth Raven . Mencoba pendekatan yang sudah dijawab membawa saya ke solusi di bawah ini yang sebagian besar dapat mencakup kursor nol kasus & konversi dari konten: // ke file: //

Untuk mengonversi file, baca & tulis file dari diperoleh 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);
}

Untuk menggunakan nama file, ini akan mencakup kursor null case

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

Ada opsi yang baik untuk mengkonversi dari tipe mime ke ekstensi file

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

Kursor untuk mendapatkan nama file

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;
}
Muhammed Yalçın Kuru
sumber
Terima kasih, sudah melihat ini selama seminggu. Tidak suka harus menyalin file untuk ini tetapi itu berfungsi.
justdan0227
3

Yah saya agak terlambat untuk menjawab, tetapi kode saya diuji

periksa skema dari 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);
        }

Dalam jawaban di atas saya mengonversi uri video ke array byte, tetapi itu tidak terkait dengan pertanyaan, saya hanya menyalin kode lengkap saya untuk menunjukkan penggunaan FileInputStreamdan InputStreamkarena keduanya bekerja sama dalam kode saya.

Saya menggunakan konteks variabel yang getActivity () di Fragmen saya dan di Activity itu hanya ActivityName.this

context=getActivity(); // dalam Fragmen

context=ActivityName.this;// dalam aktivitas

Umar Ata
sumber
Saya tahu ini tidak terkait dengan pertanyaan, tetapi bagaimana Anda kemudian akan menggunakan byte[] videoBytes;? Sebagian besar jawaban hanya menunjukkan cara menggunakan InputStreamdengan gambar.
KRK