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 ของเนื้อหา
— Phil Lello

1
มี Uris เนื้อหาจำนวนมากที่คุณไม่สามารถหาพา ธ ของไฟล์ได้เนื่องจาก Uris เนื้อหาบางอย่างนั้นไม่มี filepath อย่าใช้ไฟล์พา ธ
— Mooing Duck

คำตอบ:


153

เพียงใช้getContentResolver().openInputStream(uri)เพื่อรับInputStreamจาก URI

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


12
ตรวจสอบรูปแบบของ URI ที่ส่งคืนให้คุณจากกิจกรรมตัวเลือก ถ้าหาก uri.getScheme.equals ("เนื้อหา") ให้เปิดด้วยตัวแก้ไขเนื้อหา หาก uri.Scheme.equals ("file") ให้เปิดโดยใช้วิธีการไฟล์ปกติ ไม่ว่าจะด้วยวิธีใดคุณจะพบกับ InputStream ซึ่งคุณสามารถประมวลผลโดยใช้รหัสทั่วไป
— Jason LeBrun

16
ที่จริงแล้วฉันแค่อ่านเอกสารสำหรับ getContentResolver () อีกครั้ง openInputStream () และมันทำงานโดยอัตโนมัติสำหรับโครงร่างของ "เนื้อหา" หรือ "ไฟล์" ดังนั้นคุณไม่จำเป็นต้องตรวจสอบรูปแบบ ... ถ้าคุณปลอดภัย สมมติว่ามันจะเป็นเนื้อหา: // หรือไฟล์: // จากนั้น openInputStream () จะใช้ได้เสมอ
— Jason LeBrun

57
มีวิธีการรับไฟล์แทน InputStream (จากเนื้อหา: ... ) หรือไม่
— AlikElzin-kilaka

4
@kilaka คุณสามารถหาพา ธ ของไฟล์ได้ แต่มันก็เจ็บปวด ดูstackoverflow.com/a/20559418/294855
— Danyal Aytekin

7
คำตอบนี้ไม่เพียงพอสำหรับคนที่ใช้ API แบบโอเพ่นซอร์สที่อาศัยไฟล์มากกว่า FileStreams แต่ยังต้องการใช้ระบบปฏิบัติการเพื่อให้ผู้ใช้สามารถเลือกไฟล์ได้ คำตอบ @ DanyalAytekin ที่อ้างถึงนั้นเป็นสิ่งที่ฉันต้องการ (และในความเป็นจริงฉันสามารถลดไขมันได้มากเพราะฉันรู้ว่าไฟล์ประเภทใดที่ฉันทำงานด้วย)
— monkey0506

45

นี่เป็นคำตอบเก่าที่เลิกใช้แล้วและวิธีแฮ็คสามารถเอาชนะจุดปวดของตัวแก้ไขเนื้อหาที่เฉพาะเจาะจงได้ นำไปกับเกลือเม็ดใหญ่และใช้ openInputStream API ที่เหมาะสมหากเป็นไปได้

คุณสามารถใช้ตัวแก้ไขเนื้อหาเพื่อรับ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
บางครั้งใช้งานได้บางครั้งส่งคืนไฟล์: /// ที่เก็บ / จำลอง / 0 / ... ซึ่งไม่มีอยู่
— Reza Mohammadi

1
เป็นคอลัมน์ "_data" ( android.provider.MediaStore.Images.ImageColumns.DATA) รับประกันเสมอว่ามีอยู่หากรูปแบบคือcontent://อะไร?
— Edward Falk

2
นี่คือรูปแบบการต่อต้านที่สำคัญ ContentProviders บางตัวมีคอลัมน์นั้น แต่คุณไม่รับประกันว่าจะมีสิทธิ์เข้าถึงแบบอ่าน / เขียนFileเมื่อคุณพยายามหลีกเลี่ยง ContentResolver ใช้วิธีการcontent://ContentResolver เพื่อดำเนินการกับ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

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 ใช่คุณควรทำงานกับ Content Uris แทนพา ธ ไฟล์หรือ File Uris นั่นเป็นวิธีที่นำเสนอกรอบการเข้าถึงที่เก็บข้อมูล แต่ถ้าคุณต้องการรับไฟล์ uri นี่เป็นวิธีที่ถูกต้องที่สุดของคำตอบอื่น ๆ เนื่องจากคุณใช้คลาส DocumentsContract หากคุณตรวจสอบตัวอย่าง Google ใน Github คุณจะเห็นว่าพวกเขาใช้คลาสนี้เพื่อรับโฟลเดอร์ย่อยของโฟลเดอร์
— ธราเซียน

และชั้น DocumentFile ยังเพิ่มใหม่ใน api 19 และวิธีการที่คุณใช้ uris จาก SAF วิธีที่ถูกต้องคือการใช้พา ธ มาตรฐานสำหรับแอพของคุณและขอให้ผู้ใช้อนุญาตสำหรับโฟลเดอร์ผ่าน SAF ui, บันทึก Uri string ให้เป็นค่ากำหนดที่ใช้ร่วมกันและเมื่อจำเป็นต้องเข้าถึงโฟลเดอร์ที่มีวัตถุ DocumentFile
— Thracian

7

การพยายามจัดการ URI ด้วย content: // scheme โดยการโทรContentResolver.query()ไม่ใช่วิธีแก้ปัญหาที่ดี ใน HTC Desire ที่ใช้ 4.2.2 คุณสามารถรับค่า NULL เป็นผลลัพธ์แบบสอบถาม

ทำไมไม่ใช้ ContentResolver แทน https://stackoverflow.com/a/29141800/3205334


แต่บางครั้งเราต้องการเส้นทางเท่านั้น เราไม่จำเป็นต้องโหลดไฟล์ลงในหน่วยความจำ
— Kimi Chiu

2
"เส้นทาง" นั้นไม่มีประโยชน์หากคุณไม่มีสิทธิ์การเข้าถึง ตัวอย่างเช่นหากแอปพลิเคชันมอบcontent://uri ให้คุณซึ่งสอดคล้องกับไฟล์ในไดเรกทอรีภายในส่วนตัวคุณจะไม่สามารถใช้ uri นั้นกับFileAPIs ใน Android เวอร์ชันใหม่ได้ ContentResolver ออกแบบมาเพื่อเอาชนะข้อ จำกัด ด้านความปลอดภัยประเภทนี้ หากคุณได้รับ uri จาก ContentResolver คุณสามารถคาดหวังให้มันใช้งานได้
— user1643723

5

คำตอบที่ได้แรงบันดาลใจเป็นเจสัน LaBrunและDarth กา การลองวิธีที่ได้รับคำตอบแล้วนำฉันไปสู่วิธีแก้ปัญหาด้านล่างซึ่งส่วนใหญ่อาจครอบคลุมกรณีเคอร์เซอร์ว่างและการแปลงจากเนื้อหา: // เป็นไฟล์: //

หากต้องการแปลงไฟล์ให้อ่านและเขียนไฟล์จาก 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 () ใน Fragment ของฉันและใน Activity มันก็เป็น ActivityName.this

context=getActivity(); // ในแฟรกเมนต์

context=ActivityName.this;// ในกิจกรรม


ฉันรู้ว่ามันไม่เกี่ยวข้องกับคำถาม แต่คุณจะใช้มันbyte[] videoBytes;อย่างไร คำตอบส่วนใหญ่จะแสดงวิธีใช้InputStreamกับภาพเท่านั้น
— KRK
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.