Android: Lấy URI tệp từ URI nội dung?


133

Trong ứng dụng của tôi, người dùng sẽ chọn một tệp âm thanh mà ứng dụng sẽ xử lý. Vấn đề là để ứng dụng thực hiện những gì tôi muốn nó làm với các tệp âm thanh, tôi cần URI ở định dạng tệp. Khi tôi sử dụng trình phát nhạc gốc của Android để duyệt tệp âm thanh trong ứng dụng, URI là một URI nội dung, trông giống như sau:

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

Tuy nhiên, bằng cách sử dụng ứng dụng quản lý tập tin phổ biến Astro, tôi nhận được như sau:

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

Cái sau dễ truy cập hơn đối với tôi để làm việc, nhưng tất nhiên tôi muốn ứng dụng có chức năng với tệp âm thanh mà người dùng chọn bất kể chương trình họ sử dụng để duyệt bộ sưu tập của họ. Vì vậy, câu hỏi của tôi là, có cách nào để chuyển đổi content://URI kiểu thành file://URI không? Nếu không, những gì bạn sẽ đề nghị cho tôi để giải quyết vấn đề này? Đây là đoạn mã gọi trình chọn, để tham khảo:

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

Tôi làm như sau với URI nội dung:

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

Sau đó thực hiện một số nội dung FileInputStream với tệp đã nói.


1
Những cuộc gọi nào bạn đang sử dụng không thích URI nội dung?
Phil Lello

1
Có rất nhiều nội dung Uris mà bạn không thể có được đường dẫn tệp, bởi vì không phải tất cả Uris đều có filepath. Đừng sử dụng filepaths.
Vịt Mooing

Câu trả lời:


152

Chỉ cần sử dụng getContentResolver().openInputStream(uri)để có được một InputStreamtừ URI.

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


12
Kiểm tra sơ đồ của URI trả về cho bạn từ hoạt động của người chọn. Nếu nếu uri.getScheme.equals ("nội dung"), hãy mở nó bằng một trình giải quyết nội dung. Nếu uri.Scheme.equals ("tệp"), hãy mở nó bằng các phương thức tệp thông thường. Dù bằng cách nào, bạn sẽ kết thúc với InputStream mà bạn có thể xử lý bằng mã chung.
Jason LeBrun

16
Trên thực tế, tôi chỉ đọc lại các tài liệu cho getContentResolver (). OpenInputStream () và nó hoạt động tự động cho các lược đồ "nội dung" hoặc "tệp", vì vậy bạn không cần kiểm tra lược đồ ... nếu bạn có thể an toàn giả sử rằng nó sẽ luôn là nội dung: // hoặc tệp: // thì openInputStream () sẽ luôn hoạt động.
Jason LeBrun

57
Có cách nào để lấy Tệp thay vì InputStream (từ nội dung: ...) không?
AlikElzin-kilaka

4
@kilaka Bạn có thể lấy đường dẫn tệp nhưng thật đau. Xem stackoverflow.com/a/20559418/294855
Danyal Aytekin

7
Câu trả lời này là không đủ đối với người đang sử dụng API nguồn đóng dựa trên Tệp thay vì FileStreams, nhưng muốn sử dụng HĐH để cho phép người dùng chọn tệp. Câu trả lời @DanyalAytekin được tham chiếu là chính xác những gì tôi cần (và trên thực tế, tôi đã có thể cắt được rất nhiều chất béo vì tôi biết chính xác loại tệp nào tôi đang làm việc).
khỉ0506

45

Đây là một câu trả lời cũ với cách phản đối và hacky để khắc phục một số điểm đau giải quyết nội dung cụ thể. Mang nó với một số hạt muối khổng lồ và sử dụng API openInputStream thích hợp nếu có thể.

Bạn có thể sử dụng Trình giải quyết nội dung để nhận file://đường dẫn từ 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
Cảm ơn, điều này đã làm việc hoàn hảo. Tôi không thể sử dụng InputStream như câu trả lời được chấp nhận cho thấy.
ldam

4
Điều này chỉ hoạt động cho các tệp cục bộ, ví dụ: nó không hoạt động cho Google Drive
bluewhile

1
Đôi khi hoạt động, đôi khi trả về tệp: /// lưu trữ / mô phỏng / 0 / ... không tồn tại.
Reza Mohammadi

1
Có phải cột "_data" ( android.provider.MediaStore.Images.ImageColumns.DATA) luôn được đảm bảo tồn tại nếu lược đồ là content://?
Edward Falk

2
Đây là một mô hình chống chính. Một số ContentProviders cung cấp cột đó, nhưng bạn không được đảm bảo có quyền truy cập đọc / ghi Filekhi bạn cố gắng bỏ qua ContentResolver. Sử dụng các phương thức ContentResolver để hoạt động trên content://uris, đây là phương pháp chính thức, được các kỹ sư của Google khuyến khích.
dùng1643723

9

Nếu bạn có Uri nội dung, content://com.externalstorage...bạn có thể sử dụng phương pháp này để có đường dẫn tuyệt đối của thư mục hoặc tệp trên Android 19 trở lên .

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

Bạn có thể kiểm tra từng phần của Uri đang sử dụng println. Các giá trị được trả về cho thẻ SD và bộ nhớ chính của thiết bị được liệt kê bên dưới. Bạn có thể truy cập và xóa nếu tệp nằm trong bộ nhớ nhưng tôi không thể xóa tệp khỏi thẻ SD bằng phương pháp này, chỉ đọc hoặc mở hình ảnh d bằng đường dẫn tuyệt đối này. Nếu bạn đã tìm thấy một giải pháp để xóa bằng phương pháp này, xin vui lòng chia sẻ. THẺ 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

BỘ NHỚ CHÍNH

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

Nếu bạn muốn có được Uri file:///sau khi sử dụng đường dẫn

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

Tôi không nghĩ đó là cách đúng đắn để làm điều đó trong một ứng dụng. thật đáng buồn khi tôi đang sử dụng nó trong một dự án nhanh chóng
Bondax

@Bondax Có, bạn nên làm việc với Content Uris thay vì đường dẫn tệp hoặc Uris tệp. Đó là cách truy cập khung lưu trữ được giới thiệu. Nhưng, nếu bạn muốn lấy tệp uri thì đây là cách chính xác nhất cho các câu trả lời khác kể từ khi bạn sử dụng lớp DocumentsContract. Nếu bạn kiểm tra các mẫu của Google trong Github, bạn sẽ thấy rằng họ cũng sử dụng lớp này để lấy các thư mục con của một thư mục.
Thracian

Và lớp DocumentFile cũng bổ sung mới trong api 19 và cách bạn sử dụng uris từ SAF. Cách chính xác là sử dụng đường dẫn chuẩn cho ứng dụng của bạn và yêu cầu người dùng cấp phép cho thư mục thông qua SAF ui, lưu chuỗi Uri vào tùy chọn chia sẻ và khi cần truy cập vào thư mục với các đối tượng DocumentFile
Thracian

7

Cố gắng xử lý URI với nội dung: // lược đồ bằng cách gọi ContentResolver.query()không phải là một giải pháp tốt. Trên HTC Desire chạy 4.2.2, bạn có thể lấy NULL làm kết quả truy vấn.

Tại sao không sử dụng ContentResolver thay thế? https://stackoverflow.com/a/29141800/3205334


Nhưng đôi khi chúng ta chỉ cần con đường. Chúng tôi thực sự không phải tải tập tin vào bộ nhớ.
Kimi Chiu

2
"Con đường" là vô ích nếu bạn không có quyền truy cập cho nó. Ví dụ: nếu một ứng dụng cung cấp cho bạn một content://uri, tương ứng với tệp trong thư mục nội bộ riêng tư của nó, bạn sẽ không thể sử dụng uri đó với FileAPI trong các phiên bản Android mới. ContentResolver được thiết kế để khắc phục loại hạn chế bảo mật này. Nếu bạn đã nhận được uri từ ContentResolver, bạn có thể mong đợi nó chỉ hoạt động.
dùng1643723

5

Câu trả lời truyền cảm hứng là Jason LaBrun & Darth Raven . Việc thử các phương pháp đã được trả lời đã đưa tôi đến giải pháp bên dưới, phần lớn có thể bao gồm các trường hợp null con trỏ & chuyển đổi từ nội dung: // sang tệp: //

Để chuyển đổi tập tin, đọc và ghi tập tin từ uri thu được

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

Để có được tên tệp sử dụng, nó sẽ bao gồm trường hợp con trỏ null

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

Có tùy chọn tốt để chuyển đổi từ loại mime sang mở rộng tệp

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

Con trỏ để có được tên của tập tin

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

Cảm ơn, đã xem xét điều này trong một tuần. Đừng như phải sao chép một tập tin cho việc này nhưng nó vẫn hoạt động.
justdan0227

3

Vâng, tôi hơi muộn để trả lời, nhưng mã của tôi đã được kiểm tra

kế hoạch kiểm tra từ 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);
        }

Trong câu trả lời ở trên, tôi đã chuyển đổi video uri thành mảng byte, nhưng điều đó không liên quan đến câu hỏi, tôi chỉ sao chép toàn bộ mã của mình để hiển thị cách sử dụng FileInputStreamInputStreamvì cả hai đều hoạt động giống nhau trong mã của tôi.

Tôi đã sử dụng bối cảnh biến là getActivity () trong Fragment của tôi và trong Activity, nó chỉ đơn giản là ActivityName.this

context=getActivity(); // trong Fragment

context=ActivityName.this;// trong hoạt động


Tôi biết nó không liên quan đến câu hỏi, nhưng sau đó bạn sẽ sử dụng như byte[] videoBytes;thế nào? Hầu hết các câu trả lời chỉ cho thấy làm thế nào để sử dụng InputStreamvới một hình ảnh.
KRK
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.