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.