Thư viện Android trên Android 4.4 (KitKat) trả về URI khác nhau cho Intent.ACTION_GET_CONTENT


214

Trước KitKat (hoặc trước Thư viện mới), Intent.ACTION_GET_CONTENTURI đã trả về như thế này

nội dung: // phương tiện / bên ngoài / hình ảnh / phương tiện / 3951.

Sử dụng ContentResolvervà truy vấn để MediaStore.Images.Media.DATAtrả về URL tệp.

Tuy nhiên, trong KitKat, Thư viện trả về một URI (thông qua "Lần cuối") như thế này:

nội dung: //com.android.providers.media.document/document/image: 3951

Làm thế nào để tôi xử lý này?


21
Tắt còng, tôi sẽ tìm cách sử dụng nội dung không yêu cầu truy cập trực tiếp vào tệp. Ví dụ: có Urithể mở được dưới dạng luồng qua ContentResolver. Tôi từ lâu đã lo lắng về các ứng dụng cho rằng content:// Urimột tệp đại diện cho một tệp luôn có thể được chuyển đổi thành một File.
CommonsWare

1
@ CommonsWare, Nếu tôi muốn lưu đường dẫn hình ảnh trong db sqlite để tôi có thể mở nó sau, tôi nên lưu URI hoặc đường dẫn tệp tuyệt đối?
Rắn

2
@CommonsWare Tôi đồng ý với sự lo lắng của bạn. :-) Tuy nhiên, tôi cần có thể chuyển tên tệp (cho hình ảnh) sang mã gốc. Một giải pháp là để sao chép các dữ liệu thu được sử dụng InputStreamtrên ContentResolverđến một nơi trước được vì vậy nó có một tên tập tin được biết đến. Tuy nhiên, điều này nghe có vẻ lãng phí đối với tôi. Bất cứ một đề nghị nào khác?
darrenp

2
@darrenp: Ummm ..., viết lại mã gốc để làm việc với InputStreamJNI hơn? Thật không may, có rất nhiều lựa chọn cho bạn, thật không may.
CommonsWare

1
Điều đó hữu ích để biết. Cám ơn phản hồi của bạn. Kể từ đó tôi đã phát hiện ra rằng chúng ta hiện đang truyền hình ảnh vào C ++ trong bộ nhớ thay vì thông qua một tệp để bây giờ chúng ta có thể sử dụng InputStreamthay vì một tệp (rất tuyệt). Chỉ đọc thẻ EXIF ​​là hơi khó và yêu cầu thư viện của Drew Noakes . Rất cám ơn ý kiến ​​của bạn.
darrenp

Câu trả lời:


108

Thử cái này:

if (Build.VERSION.SDK_INT <19){
    Intent intent = new Intent(); 
    intent.setType("image/jpeg");
    intent.setAction(Intent.ACTION_GET_CONTENT);
    startActivityForResult(Intent.createChooser(intent, getResources().getString(R.string.select_picture)),GALLERY_INTENT_CALLED);
} else {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("image/jpeg");
    startActivityForResult(intent, GALLERY_KITKAT_INTENT_CALLED);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode != Activity.RESULT_OK) return;
    if (null == data) return;
    Uri originalUri = null;
    if (requestCode == GALLERY_INTENT_CALLED) {
        originalUri = data.getData();
    } else if (requestCode == GALLERY_KITKAT_INTENT_CALLED) {
        originalUri = data.getData();
        final int takeFlags = data.getFlags()
                & (Intent.FLAG_GRANT_READ_URI_PERMISSION
                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        // Check for the freshest data.
        getContentResolver().takePersistableUriPermission(originalUri, takeFlags);
    }

    loadSomeStreamAsynkTask(originalUri);

}

Có lẽ cần

@SuppressLint ("NewApi")

cho

TakePersistableUriPermission


1
Bạn có muốn giải thích những gì mã KitKat đang làm không? Điều này có cần bất kỳ sự cho phép mới? Mã KitKat trước cũng hoạt động với tôi trên KitKat. Vậy tại sao tôi lại chọn sử dụng một mã khác cho KitKat? Cảm ơn.
Michael Greifeneder

67
có vẻ như chúng ta không thể có được đường dẫn từ urks sdks mới. Ngoài ra, thật xấu hổ khi google thực hiện loại thay đổi này mà không có thông báo và thông báo chính xác.
dùng65721

1
Bạn có thể giải thích làm thế nào để có được URL tập tin? Tôi muốn có được đường dẫn thực trong sdcard. Ví dụ: nếu đó là hình ảnh, tôi muốn lấy đường dẫn này / st Storage / sdcard0 / DCIM /Camera / IMG_20131118_153817_119.jpg thay vì tài liệu Uri.
Álvaro

4
Dựa trên tài liệu KitKat ( developer.android.com/about/versions/, ) đây có thể không phải là thứ OP cần trừ khi anh ta có ý định sử dụng / chỉnh sửa tài liệu thuộc sở hữu của (các) ứng dụng khác. Nếu OP muốn có một bản sao hoặc thực hiện mọi thứ theo cách phù hợp với các phiên bản cũ hơn, câu trả lời của @voytez sẽ phù hợp hơn.
Colin M.

8
Điều này không làm việc cho tôi. Tôi nhận được ngoại lệ sau (trên stock 4.4.2): E / AndroidR.78 (29204): Nguyên nhân bởi: java.lang.SecurityException: Yêu cầu cờ 0x1, nhưng chỉ cho phép 0x0
Russell Stewart

177

Điều này không yêu cầu quyền đặc biệt và hoạt động với Khung truy cập lưu trữ, cũng như ContentProvidermẫu không chính thức (đường dẫn tệp trong _datatrường).

/**
 * Get a file path from a Uri. This will get the the path for Storage Access
 * Framework Documents, as well as the _data field for the MediaStore and
 * other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @author paulburke
 */
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)) {
        // ExternalStorageProvider
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            if ("primary".equalsIgnoreCase(type)) {
                return Environment.getExternalStorageDirectory() + "/" + split[1];
            }

            // TODO handle non-primary volumes
        }
        // DownloadsProvider
        else if (isDownloadsDocument(uri)) {

            final String id = DocumentsContract.getDocumentId(uri);
            final Uri contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

            return getDataColumn(context, contentUri, null, null);
        }
        // MediaProvider
        else if (isMediaDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            Uri contentUri = null;
            if ("image".equals(type)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if ("video".equals(type)) {
                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else if ("audio".equals(type)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }

            final String selection = "_id=?";
            final String[] selectionArgs = new String[] {
                    split[1]
            };

            return getDataColumn(context, contentUri, selection, selectionArgs);
        }
    }
    // MediaStore (and general)
    else if ("content".equalsIgnoreCase(uri.getScheme())) {

        // Return the remote address
        if (isGooglePhotosUri(uri))
            return uri.getLastPathSegment();

        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    return null;
}

/**
 * Get the value of the data column for this Uri. This is useful for
 * MediaStore Uris, and other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @param selection (Optional) Filter used in the query.
 * @param selectionArgs (Optional) Selection arguments used in the query.
 * @return The value of the _data column, which is typically a file path.
 */
public static String getDataColumn(Context context, Uri uri, String selection,
        String[] selectionArgs) {

    Cursor cursor = null;
    final String column = "_data";
    final String[] projection = {
            column
    };

    try {
        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                null);
        if (cursor != null && cursor.moveToFirst()) {
            final int index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(index);
        }
    } finally {
        if (cursor != null)
            cursor.close();
    }
    return null;
}


/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is ExternalStorageProvider.
 */
public static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is DownloadsProvider.
 */
public static boolean isDownloadsDocument(Uri uri) {
    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is MediaProvider.
 */
public static boolean isMediaDocument(Uri uri) {
    return "com.android.providers.media.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is Google Photos.
 */
public static boolean isGooglePhotosUri(Uri uri) {
    return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}

Xem phiên bản cập nhật của phương pháp này tại đây .


2
Điều này đã hoạt động tuyệt vời trên Giao diện người dùng Tài liệu Nexus 5 và một số thiết bị KitKat trước khác sử dụng các ứng dụng thư viện tiêu chuẩn, cảm ơn Paul!
Josh

1
Cảm ơn vì điều này, tôi đã mất nhiều thời gian để đạt được điều này với sdk 19 !! Vấn đề của tôi là thiết bị của tôi đang sử dụng Google drive làm trình duyệt tệp. Nếu tệp nằm trên đường dẫn hình ảnh của thiết bị sẽ ổn nhưng nếu tệp nằm trên ổ đĩa thì nó không mở. Có lẽ tôi chỉ cần xem xét xử lý việc mở hình ảnh từ google drive. Điều đó là ứng dụng của tôi được viết để sử dụng đường dẫn tệp và nhận hình ảnh bằng cách sử dụng nội dung ...
RuAware 17/12/13

2
@RuAware Khi bạn chọn tệp Drive, nó sẽ trả lại Authority: com.google.android.apps.docs.storageSegments: [document, acc=1;doc=667]. Tôi không chắc chắn, nhưng giả sử rằng docgiá trị là UriID mà bạn có thể truy vấn. Bạn có thể sẽ cần các quyền để được thiết lập chi tiết trong "Cấp quyền cho ứng dụng của bạn trên Android" tại đây: developers.google.com/drive/integrate-android-ui . Vui lòng cập nhật ở đây nếu bạn tìm ra nó.
Paul Burke

30
Điều này là hoàn toàn khủng khiếp! bạn không nên tiếp tục tuyên truyền mã "gian lận" như thế này. nó chỉ hỗ trợ các ứng dụng nguồn mà bạn biết mẫu đó và toàn bộ quan điểm của mô hình nhà cung cấp tài liệu là hỗ trợ các nguồn tùy ý
j__m

2
Các _datasẽ không hoạt động khi ContentProvider không hỗ trợ nó. Bạn nên làm theo hướng dẫn @CommonsWare và không sử dụng đường dẫn tệp đầy đủ nữa, vì đó có thể là tệp trong đám mây Dropbox thay vì tệp thực.
soshial

67

Có cùng một vấn đề, đã thử giải pháp ở trên nhưng mặc dù nó hoạt động chung, vì một số lý do tôi đã bị từ chối cấp phép đối với nhà cung cấp nội dung Uri cho một số hình ảnh mặc dù tôi đã có android.permission.MANAGE_DOCUMENTSquyền được thêm đúng.

Dù sao cũng tìm thấy giải pháp khác đó là buộc mở bộ sưu tập hình ảnh thay vì xem tài liệu KITKAT bằng:

// KITKAT

i = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(i, CHOOSE_IMAGE_REQUEST);

và sau đó tải hình ảnh:

Uri selectedImageURI = data.getData();
input = c.getContentResolver().openInputStream(selectedImageURI);
                BitmapFactory.decodeStream(input , null, opts);

BIÊN TẬP

ACTION_OPEN_DOCUMENT có thể yêu cầu bạn duy trì cờ cấp phép, v.v. và thường dẫn đến Ngoại lệ bảo mật ...

Giải pháp khác là sử dụng ACTION_GET_CONTENTkết hợp với c.getContentResolver().openInputStream(selectedImageURI)nó sẽ hoạt động cả trên KK trước và KK. Kitkat sẽ sử dụng chế độ xem tài liệu mới sau đó và giải pháp này sẽ hoạt động với tất cả các ứng dụng như Photos, Gallery, File Explorer, Dropbox, Google Drive, v.v.) nhưng hãy nhớ rằng khi sử dụng giải pháp này, bạn phải tạo hình ảnh trong onActivityResult()và lưu trữ trên đó Thẻ SD chẳng hạn. Tái tạo hình ảnh này từ uri đã lưu trong lần khởi chạy ứng dụng tiếp theo sẽ ném Bảo mật ngoại lệ vào trình phân giải nội dung ngay cả khi bạn thêm cờ cấp phép như được mô tả trong tài liệu Google API (đó là điều đã xảy ra khi tôi thực hiện một số thử nghiệm)

Ngoài ra, Nguyên tắc API dành cho nhà phát triển Android đề xuất:

ACTION_OPEN_DOCUMENT không có ý định thay thế cho ACTION_GET_CONTENT. Ứng dụng bạn nên sử dụng tùy thuộc vào nhu cầu của ứng dụng của bạn:

Sử dụng ACTION_GET_CONTENT nếu bạn muốn ứng dụng của mình chỉ cần đọc / nhập dữ liệu. Với phương pháp này, ứng dụng nhập một bản sao của dữ liệu, chẳng hạn như tệp hình ảnh.

Sử dụng ACTION_OPEN_DOCUMENT nếu bạn muốn ứng dụng của mình có quyền truy cập lâu dài, liên tục vào các tài liệu thuộc sở hữu của nhà cung cấp tài liệu. Một ví dụ sẽ là một ứng dụng chỉnh sửa ảnh cho phép người dùng chỉnh sửa hình ảnh được lưu trữ trong một nhà cung cấp tài liệu.


1
Câu trả lời này chứa thông tin đúng cho mục đích của tôi. Điều kiện sử dụng ACTION_PICK và EXTERNAL_CONTENT_URI trên KitKat cung cấp khả năng tương tự để có được dữ liệu meta về Hình ảnh trong thư viện thông qua ContentResolver như có thể có trên các phiên bản cũ hơn bằng cách sử dụng đơn giản ACTION_GET_CONTENT.
Colin M.

@voytez, URI này có thể được trả lại qua tin nhắn của bạn được chuyển đổi thành đường dẫn thực của hình ảnh không?
Rắn

Tôi tin là như vậy, nó sẽ hoạt động giống như trước KitKat vì mã này buộc mở bộ sưu tập hình ảnh thay vì xem tài liệu KK. Nhưng nếu bạn có ý định sử dụng nó để tạo hình ảnh thì giải pháp này tốt hơn vì chuyển đổi sang đường dẫn thực là một bước không cần thiết thêm.
Journeytez

4
Làm việc cho tôi quá, thay vì Intent.ACTION_GET_CONTENT. Dù sao, tôi vẫn giữ Intent.createChooser()trình bao bọc mới Intent, để cho phép người dùng chọn ứng dụng để duyệt và hoạt động như mong đợi. Ai đó có thể thấy nhược điểm cho giải pháp này?
lorenzo-s

1
Đối với bất cứ ai thắc mắc về trích dẫn đến từ developer.android.com/guide/topics/providers/,
snark

38

Giống như Commonsware đã đề cập, bạn không nên cho rằng luồng bạn nhận được thông qua ContentResolver chuyển đổi thành tệp.

Những gì bạn thực sự nên làm là mở InputStreamtừ ContentProvider, sau đó tạo Bitmap từ đó. Và nó cũng hoạt động trên phiên bản 4.4 trở về trước, không cần phản ánh.

    //cxt -> current context

    InputStream input;
    Bitmap bmp;
    try {
        input = cxt.getContentResolver().openInputStream(fileUri);
        bmp = BitmapFactory.decodeStream(input);
    } catch (FileNotFoundException e1) {

    }

Tất nhiên, nếu bạn xử lý các hình ảnh lớn, bạn nên tải chúng với thích hợp inSampleSize: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html . Nhưng đó là một chủ đề khác.


Điều này không hiệu quả với tôi với Nexus 4 chạy Kitkat nhưng với Galaxy S3 chạy 4.1.2
Dan2552

@ Dan2552 phần nào không hoạt động? Bạn có nhận được bất kỳ ngoại lệ?
Michał K

Hóa ra tôi đã sử dụng sai mục đích gọi đến phòng trưng bày. Tôi đã sử dụng một tài liệu dành cho bất kỳ loại tài liệu nào, nhưng với bộ lọc mở rộng tệp.
Dan2552

2
Thật là một câu trả lời đơn giản, cảm ơn bạn! Đối với những người khác theo câu trả lời này, 'cxt' đề cập đến bối cảnh hiện tại và thường sẽ là 'cái này'.
thomasforth

1
Điều này có thể có nghĩa là tập tin không có ở đó. URI có vẻ ổn.
Michał K

33

Tôi tin rằng các phản hồi đã được đăng sẽ giúp mọi người đi đúng hướng. Tuy nhiên đây là những gì tôi đã làm có ý nghĩa cho mã kế thừa mà tôi đang cập nhật. Mã kế thừa đã sử dụng URI từ thư viện để thay đổi và sau đó lưu hình ảnh.

Trước 4.4 (và ổ google), các URI sẽ trông như thế này: nội dung: // media / bên ngoài / hình ảnh / media / 41

Như đã nêu trong câu hỏi, chúng thường trông như thế này: content: //com.android.providers.media.document/document/image: 3951

Vì tôi cần khả năng lưu hình ảnh và không làm phiền mã đã có, tôi chỉ sao chép URI từ thư viện vào thư mục dữ liệu của ứng dụng. Sau đó, khởi tạo một URI mới từ tệp hình ảnh đã lưu trong thư mục dữ liệu.

Đây là ý tưởng:

Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent), CHOOSE_IMAGE_REQUEST);

public void onActivityResult(int requestCode, int resultCode, Intent data) {

    super.onActivityResult(requestCode, resultCode, data);

    File tempFile = new File(this.getFilesDir().getAbsolutePath(), "temp_image");

    //Copy URI contents into temporary file.
    try {
        tempFile.createNewFile();
        copyAndClose(this.getContentResolver().openInputStream(data.getData()),new FileOutputStream(tempFile));
    }
    catch (IOException e) {
        //Log Error
    }

    //Now fetch the new URI
    Uri newUri = Uri.fromFile(tempFile);

    /* Use new URI object just like you used to */
 }

Lưu ý - copyAndClose () chỉ thực hiện tệp I / O để sao chép InputStream vào FileOutputStream. Mã không được đăng.


khá thông minh. tôi cũng cần tập tin thực tế uri
lớn hơn

bạn là anh hùng của tôi, đây chính xác là những gì tôi cần! cũng hoạt động tốt cho các tệp Google Drive
mishkin

Nghĩ ra khỏi hộp, phải không? : D Mã này hoạt động chính xác như tôi mong đợi.
WeirdElfB0y

2
gửi mã cho copyAndC Đóng, câu trả lời chưa đầy đủ
Bartek Pacia

24

Chỉ muốn nói rằng câu trả lời này là tuyệt vời và tôi đang sử dụng nó trong một thời gian dài mà không có vấn đề. Nhưng một thời gian trước, tôi đã tình cờ phát hiện ra một vấn đề là DownloaderProvider trả về các URI ở định dạng content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdoc.pdfvà do đó ứng dụng bị sậpNumberFormatException vì không thể phân tích các phân đoạn uri của nó bao lâu. Nhưng raw:phân đoạn chứa uri trực tiếp có thể được sử dụng để truy xuất tệp được tham chiếu. Vì vậy, tôi đã sửa nó bằng cách thay thế isDownloadsDocument(uri) ifnội dung bằng cách sau:

final String id = DocumentsContract.getDocumentId(uri);
if (!TextUtils.isEmpty(id)) {
if (id.startsWith("raw:")) {
    return id.replaceFirst("raw:", "");
}
try {
    final Uri contentUri = ContentUris.withAppendedId(
            Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
    return getDataColumn(context, contentUri, null, null);
} catch (NumberFormatException e) {
    Log.e("FileUtils", "Downloads provider returned unexpected uri " + uri.toString(), e);
    return null;
}
}

2
Hoạt động hoàn hảo! Cảm ơn bạn
mikemike394

8

Tôi đã kết hợp nhiều câu trả lời vào một giải pháp làm việc có kết quả với đường dẫn tệp

Loại Mime không liên quan cho mục đích ví dụ.

            Intent intent;
            if(Build.VERSION.SDK_INT >= 19){
                intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
                intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
                intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
            }else{
                intent = new Intent(Intent.ACTION_GET_CONTENT);
            }
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setType("application/octet-stream");
            if(isAdded()){
                startActivityForResult(intent, RESULT_CODE);
            }

Xử lý kết quả

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if( requestCode == RESULT_CODE && resultCode == Activity.RESULT_OK) {
        Uri uri = data.getData();
        if (uri != null && !uri.toString().isEmpty()) {
            if(Build.VERSION.SDK_INT >= 19){
                final int takeFlags = data.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION;
                //noinspection ResourceType
                getActivity().getContentResolver()
                        .takePersistableUriPermission(uri, takeFlags);
            }
            String filePath = FilePickUtils.getSmartFilePath(getActivity(), uri);
            // do what you need with it...
        }
    }
}

FilePickUtils

import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;

public class FilePickUtils {
    private static String getPathDeprecated(Context ctx, Uri uri) {
        if( uri == null ) {
            return null;
        }
        String[] projection = { MediaStore.Images.Media.DATA };
        Cursor cursor = ctx.getContentResolver().query(uri, projection, null, null, null);
        if( cursor != null ){
            int column_index = cursor
                    .getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            cursor.moveToFirst();
            return cursor.getString(column_index);
        }
        return uri.getPath();
    }

    public static String getSmartFilePath(Context ctx, Uri uri){

        if (Build.VERSION.SDK_INT < 19) {
            return getPathDeprecated(ctx, uri);
        }
        return  FilePickUtils.getPath(ctx, uri);
    }

    @SuppressLint("NewApi")
    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)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }

                // TODO handle non-primary volumes
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {
                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                return getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }

                final String selection = "_id=?";
                final String[] selectionArgs = new String[] {
                        split[1]
                };

                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {
            return getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }

        return null;
    }

    /**
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri The Uri to query.
     * @param selection (Optional) Filter used in the query.
     * @param selectionArgs (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     */
    public static String getDataColumn(Context context, Uri uri, String selection,
                                       String[] selectionArgs) {
        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {
                column
        };

        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                    null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }


    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

}

tôi đã phải đối mặt với vấn đề .... uri.getPath () đã trả lại uri với / bên ngoài và nó không phải là đường dẫn trở lại. tôi đã thêm cái này nếu khối ("nội dung" .equalsIgnoreCase (uri.getScheme ())) và điều này hoạt động tốt ngay bây giờ .. bạn có thể giải thích nó làm gì không
FaisalAhmed

filePath đang bị null .. Trong uri: content: //com.android.externalst Storage.document/document/799B
Bhavesh Moradiya

7

Câu hỏi

Cách nhận đường dẫn tệp thực tế từ URI

Câu trả lời

Theo hiểu biết của tôi, chúng tôi không cần lấy đường dẫn tệp từ URI vì trong hầu hết các trường hợp, chúng tôi có thể trực tiếp sử dụng URI để hoàn thành công việc của mình (như 1. nhận bitmap 2. Gửi tệp đến máy chủ, v.v. .)

1. Gửi đến máy chủ

Chúng tôi có thể trực tiếp gửi tệp đến máy chủ chỉ bằng URI.

Sử dụng URI, chúng ta có thể nhận InputStream, mà chúng ta có thể gửi trực tiếp đến máy chủ bằng MultiPartEntity.

Thí dụ

/**
 * Used to form Multi Entity for a URI (URI pointing to some file, which we got from other application).
 *
 * @param uri     URI.
 * @param context Context.
 * @return Multi Part Entity.
 */
public MultipartEntity formMultiPartEntityForUri(final Uri uri, final Context context) {
    MultipartEntity multipartEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, null, Charset.forName("UTF-8"));
    try {
        InputStream inputStream = mContext.getContentResolver().openInputStream(uri);
        if (inputStream != null) {
            ContentBody contentBody = new InputStreamBody(inputStream, getFileNameFromUri(uri, context));
            multipartEntity.addPart("[YOUR_KEY]", contentBody);
        }
    }
    catch (Exception exp) {
        Log.e("TAG", exp.getMessage());
    }
    return multipartEntity;
}

/**
 * Used to get a file name from a URI.
 *
 * @param uri     URI.
 * @param context Context.
 * @return File name from URI.
 */
public String getFileNameFromUri(final Uri uri, final Context context) {

    String fileName = null;
    if (uri != null) {
        // Get file name.
        // File Scheme.
        if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
            File file = new File(uri.getPath());
            fileName = file.getName();
        }
        // Content Scheme.
        else if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
            Cursor returnCursor =
                    context.getContentResolver().query(uri, null, null, null, null);
            if (returnCursor != null && returnCursor.moveToFirst()) {
                int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                fileName = returnCursor.getString(nameIndex);
                returnCursor.close();
            }
        }
    }
    return fileName;
}

2. Lấy BitMap từ một URI

Nếu URI đang trỏ đến hình ảnh thì chúng ta sẽ nhận được bitmap, khác null:

/**
 * Used to create bitmap for the given URI.
 * <p>
 * 1. Convert the given URI to bitmap.
 * 2. Calculate ratio (depending on bitmap size) on how much we need to subSample the original bitmap.
 * 3. Create bitmap bitmap depending on the ration from URI.
 * 4. Reference - http://stackoverflow.com/questions/3879992/how-to-get-bitmap-from-an-uri
 *
 * @param context       Context.
 * @param uri           URI to the file.
 * @param bitmapSize Bitmap size required in PX.
 * @return Bitmap bitmap created for the given URI.
 * @throws IOException
 */
public static Bitmap createBitmapFromUri(final Context context, Uri uri, final int bitmapSize) throws IOException {

    // 1. Convert the given URI to bitmap.
    InputStream input = context.getContentResolver().openInputStream(uri);
    BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
    onlyBoundsOptions.inJustDecodeBounds = true;
    onlyBoundsOptions.inDither = true;//optional
    onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
    BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
    input.close();
    if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) {
        return null;
    }

    // 2. Calculate ratio.
    int originalSize = (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth) ? onlyBoundsOptions.outHeight : onlyBoundsOptions.outWidth;
    double ratio = (originalSize > bitmapSize) ? (originalSize / bitmapSize) : 1.0;

    // 3. Create bitmap.
    BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
    bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);
    bitmapOptions.inDither = true;//optional
    bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
    input = context.getContentResolver().openInputStream(uri);
    Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
    input.close();

    return bitmap;
}

/**
 * For Bitmap option inSampleSize - We need to give value in power of two.
 *
 * @param ratio Ratio to be rounded of to power of two.
 * @return Ratio rounded of to nearest power of two.
 */
private static int getPowerOfTwoForSampleRatio(final double ratio) {
    int k = Integer.highestOneBit((int) Math.floor(ratio));
    if (k == 0) return 1;
    else return k;
}

Bình luận

  1. Android không cung cấp bất kỳ phương pháp nào để nhận đường dẫn tệp từ URI và trong hầu hết các câu trả lời ở trên, chúng tôi đã mã hóa cứng một số hằng số, có thể bị hỏng trong bản phát hành tính năng (xin lỗi, tôi có thể sai).
  2. Trước khi trực tiếp đi đến một giải pháp về đường dẫn nhận tệp từ URI, hãy thử xem bạn có thể giải quyết trường hợp sử dụng của mình bằng các phương thức mặc định của URI và Android không.

Tài liệu tham khảo

  1. https://developer.android.com/guide/topics/providers/content-provider-basics.html
  2. https://developer.android.com/reference/android/content/ContentResolver.html
  3. https://hc.apache.org/httpcomponents-client-ga/httpmime/apidocs/org/apache/http/entity/mime/content/InputStreamBody.html

Cảm ơn bạn. Sử dụng Uri và ContentResolver theo cách này đã đơn giản hóa ứng dụng của tôi một cách đáng kể khi xử lý các tệp.
jacksonakj


3

Đối với những người vẫn đang sử dụng mã của @Paul Burke với Android SDK phiên bản 23 trở lên, nếu dự án của bạn gặp lỗi nói rằng bạn đang thiếu EXTERNAL_PERMISSION và bạn chắc chắn rằng bạn đã thêm quyền của người dùng trong tệp AndroidManifest.xml của mình. Đó là bởi vì bạn có thể trong API Android 23 trở lên và Google yêu cầu phải đảm bảo lại quyền trong khi bạn thực hiện hành động để truy cập tệp trong thời gian chạy.

Điều đó có nghĩa là: Nếu phiên bản SDK của bạn từ 23 trở lên, bạn được yêu cầu cấp phép READ & WRITE trong khi bạn đang chọn tệp hình ảnh và muốn biết URI của nó.

Và sau đây là mã của tôi, ngoài giải pháp của Paul Burke. Tôi thêm các mã này và dự án của tôi bắt đầu hoạt động tốt.

private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static final String[] PERMISSINOS_STORAGE = {
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.WRITE_EXTERNAL_STORAGE
};

public static void verifyStoragePermissions(Activity activity) {
    int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);

    if (permission != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(
                activity,
                PERMISSINOS_STORAGE,
                REQUEST_EXTERNAL_STORAGE
        );
    }
}

Và trong hoạt động & đoạn của bạn, nơi bạn đang yêu cầu URI:

private void pickPhotoFromGallery() {

    CompatUtils.verifyStoragePermissions(this);
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType("image/*");
    // startActivityForResult(intent, REQUEST_PHOTO_LIBRARY);
    startActivityForResult(Intent.createChooser(intent, "选择照片"),
            REQUEST_PHOTO_LIBRARY);
}

Trong trường hợp của tôi, CompatUtils.java là nơi tôi xác định phương thức verifyStoragePermissions (dưới dạng tĩnh để tôi có thể gọi nó trong hoạt động khác).

Ngoài ra, sẽ hợp lý hơn nếu bạn tạo trạng thái if trước để xem phiên bản SDK hiện tại có cao hơn 23 hay không trước khi bạn gọi phương thức verifyStoragePermissions.


2

Đây là những gì tôi làm:

Uri selectedImageURI = data.getData();    imageFile = new File(getRealPathFromURI(selectedImageURI)); 

private String getRealPathFromURI(Uri contentURI) {
  Cursor cursor = getContentResolver().query(contentURI, null, null, null, null);
  if (cursor == null) { // Source is Dropbox or other similar local file path
      return contentURI.getPath();
      } else { 
      cursor.moveToFirst(); 
      int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); 
      return cursor.getString(idx); 
  }
}

LƯU Ý: managedQuery()phương pháp không được dùng nữa, vì vậy tôi không sử dụng nó.

Câu trả lời này là từ m3n0R trong câu hỏi android có đường dẫn thực sự bởi Uri.getPath () và tôi khẳng định không có tín dụng. Tôi chỉ nghĩ rằng những người chưa giải quyết vấn đề này có thể sử dụng nó.


2
Đây không phải là câu trả lời cho ứng dụng Thư viện mới (hoàn toàn là ứng dụng "Nhà cung cấp tài liệu truyền thông") trên KitKat.
nagoya0

Ứng dụng mà người hỏi gọi là "Thư viện" có thể là trình chọn tệp mới trên kitkat. FYI: addictivetips.com/android/...
nagoya0

Tôi cũng làm tương tự và có IndexOutOfBound trên Nexus 5X, Android 6 trên dòng này:cursor.getString(idx);
Osify

1

Vui lòng cố gắng tránh sử dụng phương thức TakePersistableUriPermission vì nó tăng ngoại lệ thời gian chạy cho tôi. / ** * Chọn từ thư viện. * /

public void selectFromGallery() {
    if (Build.VERSION.SDK_INT < AppConstants.KITKAT_API_VERSION) {

        Intent intent = new Intent(); 
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        ((Activity)mCalledContext).startActivityForResult(intent,AppConstants.GALLERY_INTENT_CALLED);

    } else {

        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        ((Activity)mCalledContext).startActivityForResult(intent, AppConstants.GALLERY_AFTER_KITKAT_INTENT_CALLED);
    }
}

OnActivity cho kết quả để xử lý dữ liệu hình ảnh:

@Override void void onActivityResult (int requestCode, int resultCode, Intent data) {

    //gallery intent result handling before kit-kat version
    if(requestCode==AppConstants.GALLERY_INTENT_CALLED 
            && resultCode == RESULT_OK) {

        Uri selectedImage = data.getData();
        String[] filePathColumn = {MediaStore.Images.Media.DATA};
        Cursor cursor = getContentResolver().query(selectedImage,filePathColumn, null, null, null);
        cursor.moveToFirst();
        int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
        String filePath = cursor.getString(columnIndex);
        cursor.close();
        photoFile = new File(filePath);
        mImgCropping.startCropImage(photoFile,AppConstants.REQUEST_IMAGE_CROP);

    }
    //gallery intent result handling after kit-kat version
    else if (requestCode == AppConstants.GALLERY_AFTER_KITKAT_INTENT_CALLED 
            && resultCode == RESULT_OK) {

        Uri selectedImage = data.getData();
        InputStream input = null;
        OutputStream output = null;

        try {
            //converting the input stream into file to crop the 
            //selected image from sd-card.
            input = getApplicationContext().getContentResolver().openInputStream(selectedImage);
            try {
                photoFile = mImgCropping.createImageFile();
            } catch (IOException e) {
                e.printStackTrace();
            }catch(Exception e) {
                e.printStackTrace();
            }
            output = new FileOutputStream(photoFile);

            int read = 0;
            byte[] bytes = new byte[1024];

            while ((read = input.read(bytes)) != -1) {
                try {
                    output.write(bytes, 0, read);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

Bạn đang hiển thị hình ảnh ở đâu trong trường hợp thứ hai?
Quantum_VC

xin lỗi tôi không thể thêm dòng mã này vào nếu không mImgCropping.startCropImage (photoFile, AppConstants.REQUEST_IMAGE_CROP); một lần nữa cần gọi chức năng tưởng tượng theo nhu cầu dự án của tôi
saranya

Loại tệp nào là photoFile và mImgCropping?
Philip BH

1

Nếu bất cứ ai quan tâm, tôi đã tạo một phiên bản Kotlin hoạt động cho ACTION_GET_CONTENT:

var path: String = uri.path // uri = any content Uri
val databaseUri: Uri
val selection: String?
val selectionArgs: Array<String>?
if (path.contains("/document/image:")) { // files selected from "Documents"
    databaseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    selection = "_id=?"
    selectionArgs = arrayOf(DocumentsContract.getDocumentId(uri).split(":")[1])
} else { // files selected from all other sources, especially on Samsung devices
    databaseUri = uri
    selection = null
    selectionArgs = null
}
try {
    val projection = arrayOf(MediaStore.Images.Media.DATA,
        MediaStore.Images.Media._ID,
        MediaStore.Images.Media.ORIENTATION,
        MediaStore.Images.Media.DATE_TAKEN) // some example data you can query
    val cursor = context.contentResolver.query(databaseUri,
        projection, selection, selectionArgs, null)
    if (cursor.moveToFirst()) {
        // do whatever you like with the data
    }
    cursor.close()
} catch (e: Exception) {
    Log.e(TAG, e.message, e)
}

Tôi chỉ muốn một mã làm việc của kotlin. Đó là công việc của tôi. cảm ơn
Harvi Sirja

1

Tôi đã thử một vài câu trả lời ở đây và tôi nghĩ rằng tôi có một giải pháp sẽ hoạt động mọi lúc và cũng quản lý các quyền.

Nó dựa trên giải pháp thông minh từ LEO. Bài đăng này phải chứa tất cả mã bạn cần để thực hiện công việc này và nó sẽ hoạt động trên mọi phiên bản điện thoại và Android;)

Để có khả năng chọn tệp từ thẻ SD, bạn sẽ cần điều này trong bảng kê khai của mình:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Hằng số:

private static final int PICK_IMAGE = 456; // Whatever number you like
public static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL = 28528; // Whatever number you like
public static final String FILE_TEMP_NAME = "temp_image"; // Whatever file name you like

Kiểm tra quyền và launchImagePick nếu có thể

if (ContextCompat.checkSelfPermission(getThis(),
        Manifest.permission.READ_EXTERNAL_STORAGE)
        != PackageManager.PERMISSION_GRANTED) {

    ActivityCompat.requestPermissions(getThis(),
            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
            MY_PERMISSIONS_REQUEST_READ_EXTERNAL);
}
else {
    launchImagePick();
}

Phản hồi cho phép

@Override
public void onRequestPermissionsResult(int requestCode,
                                       @NonNull
                                         String permissions[],
                                       @NonNull
                                         int[] grantResults) {

    if (manageReadExternalPermissionResponse(this, requestCode, grantResults)) {
        launchImagePick();
    }
}

Quản lý phản hồi cho phép

public static boolean manageReadExternalPermissionResponse(final Activity activity, int requestCode, int[] grantResults) {

    if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL) {

        // If request is cancelled, the result arrays are empty.

        if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            // Permission was granted, yay! Do the
            // contacts-related task you need to do.
            return true;

        } else if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_DENIED) {

            boolean showRationale = ActivityCompat.shouldShowRequestPermissionRationale(activity,
                    Manifest.permission.READ_EXTERNAL_STORAGE);

            if (!showRationale) {
                // The user also CHECKED "never ask again".
                // You can either enable some fall back,
                // disable features of your app
                // or open another dialog explaining
                // again the permission and directing to
                // the app setting.

            } else {
                // The user did NOT check "never ask again".
                // This is a good place to explain the user
                // why you need the permission and ask if he/she wants
                // to accept it (the rationale).
            }
        } else {
            // Permission denied, boo! Disable the
            // functionality that depends on this permission.
        }
    }
    return false;
}

Khởi động chọn ảnh

private void launchImagePick() {

    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType("image/*");
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    startActivityForResult(intent, PICK_IMAGE);

    // see onActivityResult
}

Quản lý phản hồi chọn ảnh

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == PICK_IMAGE) {

        if (resultCode == Activity.RESULT_OK) {
            if (data != null && data.getData() != null) {

                try {
                     InputStream inputStream = getContentResolver().openInputStream(data.getData())
                     if (inputStream != null) {

                        // No special persmission needed to store the file like that
                        FileOutputStream fos = openFileOutput(FILE_TEMP_NAME, Context.MODE_PRIVATE);

                        final int BUFFER_SIZE = 1 << 10 << 3; // 8 KiB buffer
                        byte[] buffer = new byte[BUFFER_SIZE];
                        int bytesRead = -1;
                        while ((bytesRead = inputStream.read(buffer)) > -1) {
                            fos.write(buffer, 0, bytesRead);
                        }
                        inputStream.close();
                        fos.close();

                        File tempImageFile = new File(getFilesDir()+"/"+FILE_TEMP_NAME);

                        // Do whatever you want with the File

                        // Delete when not needed anymore
                        deleteFile(FILE_TEMP_NAME);
                    }
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                // Error display
            }
        } else {
            // The user did not select any image
        }
    }
}

Đó là tất cả mọi người; điều này làm việc cho tôi trên tất cả các điện thoại tôi có.


0

Đây là một hack hoàn toàn, nhưng đây là những gì tôi đã làm ...

Vì vậy, trong khi chơi với việc thiết lập DocumentsProvider , tôi nhận thấy rằng mã mẫu (trong getDocIdForFile, khoảng dòng 450) tạo ra một id duy nhất cho một tài liệu được chọn dựa trên đường dẫn (duy nhất) của tệp so với gốc được chỉ định mà bạn cung cấp (nghĩa là, những gì bạn đặt mBaseDirtrên dòng 96).

Vì vậy, URI cuối cùng trông giống như:

content://com.example.provider/document/root:path/to/the/file

Như các tài liệu nói, nó chỉ giả sử một gốc duy nhất (trong trường hợp của tôi đó là Environment.getExternalStorageDirectory()nhưng bạn có thể sử dụng ở một nơi khác ... sau đó, nó lấy đường dẫn tệp, bắt đầu từ gốc và biến nó thành ID duy nhất, thêm vào " root:". Vì vậy, tôi có thể xác định đường dẫn bằng cách loại bỏ "/document/root:"phần từ uri.getPath (), tạo đường dẫn tệp thực tế bằng cách thực hiện một cái gì đó như thế này:

public void onActivityResult(int requestCode, int resultCode, Intent data) {
// check resultcodes and such, then...
uri = data.getData();
if (uri.getAuthority().equals("com.example.provider"))  {
    String path = Environment.getExternalStorageDirectory(0.toString()
                 .concat("/")
                 .concat(uri.getPath().substring("/document/root:".length())));
    doSomethingWithThePath(path); }
else {
    // another provider (maybe a cloud-based service such as GDrive)
    // created this uri.  So handle it, or don't.  You can allow specific
    // local filesystem providers, filter non-filesystem path results, etc.
}

Tôi biết. Thật đáng xấu hổ, nhưng nó đã làm việc. Một lần nữa, điều này phụ thuộc vào bạn sử dụng nhà cung cấp tài liệu của riêng bạn trong ứng dụng của bạn để tạo ID tài liệu.

(Ngoài ra, có một cách tốt hơn để xây dựng đường dẫn không giả sử "/" là dấu tách đường dẫn, v.v. Nhưng bạn hiểu ý.)


Để trả lời bản thân với một suy nghĩ thậm chí điên rồ hơn-- nếu ứng dụng của bạn đã xử lý file://ý định từ trình chọn tệp bên ngoài, bạn cũng có thể kiểm tra quyền hạn, như trong ví dụ trên để đảm bảo rằng nó từ nhà cung cấp tùy chỉnh của bạn và nếu vậy bạn có thể cũng sử dụng đường dẫn để "giả mạo" một file://mục đích mới bằng cách sử dụng đường dẫn bạn đã trích xuất, sau đó StartActivity()và để ứng dụng của bạn chọn nó. Tôi biết, khủng khiếp.
vỗ béo

0

Điều này làm việc tốt cho tôi:

else if(requestCode == GALLERY_ACTIVITY_NEW && resultCode == Activity.RESULT_OK)
{
    Uri uri = data.getData();
    Log.i(TAG, "old uri =  " + uri);
    dumpImageMetaData(uri);

    try {
        ParcelFileDescriptor parcelFileDescriptor =
                getContentResolver().openFileDescriptor(uri, "r");
        FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
        Log.i(TAG, "File descriptor " + fileDescriptor.toString());

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);

        options.inSampleSize =
           BitmapHelper.calculateInSampleSize(options,
                                              User.PICTURE_MAX_WIDTH_IN_PIXELS,
                                              User.PICTURE_MAX_HEIGHT_IN_PIXELS);
        options.inJustDecodeBounds = false;

        Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
        imageViewPic.setImageBitmap(bitmap);

        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
        // get byte array here
        byte[] picData = stream.toByteArray();
        ParseFile picFile = new ParseFile(picData);
        user.setProfilePicture(picFile);
    }
    catch(FileNotFoundException exc)
    {
        Log.i(TAG, "File not found: " + exc.toString());
    }
}

quên dumpImageMetaData (uri); nó là không cần thiết
Rafa

0

Xây dựng câu trả lời của Paul Burke tôi đã gặp nhiều vấn đề khi giải quyết đường dẫn URI của thẻ SD bên ngoài vì hầu hết các hàm "tích hợp" được đề xuất đều trả về các đường dẫn không được giải quyết thành tệp.

Tuy nhiên, đây là cách tiếp cận // TODO của tôi xử lý các khối lượng không chính .

String resolvedPath = "";
File[] possibleExtSdComposites = context.getExternalFilesDirs(null);
for (File f : possibleExtSdComposites) {
    // Reset final path
    resolvedPath = "";

    // Construct list of folders
    ArrayList<String> extSdSplit = new ArrayList<>(Arrays.asList(f.getPath().split("/")));

    // Look for folder "<your_application_id>"
    int idx = extSdSplit.indexOf(BuildConfig.APPLICATION_ID);

    // ASSUMPTION: Expected to be found at depth 2 (in this case ExtSdCard's root is /storage/0000-0000/) - e.g. /storage/0000-0000/Android/data/<your_application_id>/files
    ArrayList<String> hierarchyList = new ArrayList<>(extSdSplit.subList(0, idx - 2));

    // Construct list containing full possible path to the file
    hierarchyList.add(tail);
    String possibleFilePath = TextUtils.join("/", hierarchyList);

    // If file is found --> success
    if (idx != -1 && new File(possibleFilePath).exists()) {
        resolvedPath = possibleFilePath;
        break;
    }
}

if (!resolvedPath.equals("")) {
    return resolvedPath;
} else {
    return null;
}

Lưu ý rằng nó phụ thuộc vào cấu trúc phân cấp có thể khác nhau ở mỗi nhà sản xuất điện thoại - Tôi chưa thử nghiệm tất cả (nó hoạt động tốt cho đến nay trên Xperia Z3 API 23 và Samsung Galaxy A3 API 23).

Vui lòng xác nhận nếu nó không hoạt động tốt ở nơi khác.


-1

Câu trả lời của @paul burke hoạt động tốt cho cả ảnh máy ảnh và thư viện cho API cấp 19 trở lên, nhưng nó không hoạt động nếu SDK tối thiểu của dự án Android của bạn được đặt ở dưới 19 và một số câu trả lời giới thiệu ở trên không hoạt động cho cả thư viện và Máy ảnh. Chà, tôi đã sửa đổi mã của @paul burke, hoạt động cho cấp API dưới 19. Dưới đây là mã.

public static String getPath(final Context context, final Uri uri) {

    final boolean isKitKat = Build.VERSION.SDK_INT >=
                             Build.VERSION_CODES.KITKAT;
    Log.i("URI",uri+"");
    String result = uri+"";

    // DocumentProvider
    // if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
    if (isKitKat && (result.contains("media.documents"))) {

        String[] ary = result.split("/");
        int length = ary.length;
        String imgary = ary[length-1];
        final String[] dat = imgary.split("%3A");

        final String docId = dat[1];
        final String type = dat[0];

        Uri contentUri = null;
        if ("image".equals(type)) {
            contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        }
        else if ("video".equals(type)) {
        }
        else if ("audio".equals(type)) {
        }

        final String selection = "_id=?";
        final String[] selectionArgs = new String[] {
            dat[1]
        };

        return getDataColumn(context, contentUri, selection, selectionArgs);
    }
    else
    if ("content".equalsIgnoreCase(uri.getScheme())) {
        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    return null;
}

public static String getDataColumn(Context context, Uri uri, String selection,
                                   String[] selectionArgs) {
    Cursor cursor = null;
    final String column = "_data";
    final String[] projection = {
            column
    };

    try {
        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                null);
        if (cursor != null && cursor.moveToFirst()) {
            final int column_index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(column_index);
        }
    }
    finally {
        if (cursor != null)
            cursor.close();
    }
    return null;
}

Tôi nhận được java.lang.IllegalArgumentException: Không có cột nào được yêu cầu có thể được cung cấp khi chọn hình ảnh Google Doc
dirkoneill

@dirkoneill Mình cũng bị ngoại lệ. Bạn đã tìm thấy một sửa chữa?
Henry

-4

Câu trả lời cho câu hỏi của bạn là bạn cần có quyền. Nhập mã sau đây vào tệp manifest.xml của bạn:

<uses-sdk  android:minSdkVersion="8"   android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.WRITE_OWNER_DATA"></uses-permission>
<uses-permission android:name="android.permission.READ_OWNER_DATA"></uses-permission>`

Nó làm việc cho tôi ...

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.