Galeri Android di Android 4.4 (KitKat) mengembalikan URI yang berbeda untuk Intent.ACTION_GET_CONTENT

214

Sebelum KitKat (atau sebelum Galeri baru), Intent.ACTION_GET_CONTENTURI yang dikembalikan seperti ini

konten: // media / eksternal / gambar / media / 3951.

Menggunakan ContentResolverdan quering untuk MediaStore.Images.Media.DATAmengembalikan URL file.

Namun di KitKat Galeri mengembalikan URI (via "Terakhir") seperti ini:

konten: //com.android.providers.media.documents/document/image: 3951

Bagaimana saya menangani ini?

Michael Greifeneder
sumber
21
Tanpa sadar, saya akan menemukan cara menggunakan konten yang tidak memerlukan akses langsung ke file. Misalnya, itu Uriharus dapat dibuka sebagai aliran melalui ContentResolver. Saya sudah lama gugup dengan aplikasi yang menganggap bahwa content:// Uriyang mewakili file selalu dapat dikonversi menjadi File.
CommonsWare
1
@ CommonsWare, Jika saya ingin menyimpan jalur gambar dalam sqlite db sehingga saya dapat membukanya nanti, haruskah saya menyimpan jalur file URI atau absolut?
Snake
2
@CommonsWare Saya setuju dengan kegugupan Anda. :-) Namun, saya harus bisa memberikan nama file (untuk gambar) ke kode asli. Solusinya adalah menyalin data yang diperoleh dengan menggunakan InputStreampada ContentResolverke tempat yang telah ditentukan sebelumnya sehingga memiliki nama file yang dikenal. Namun, ini terdengar boros bagi saya. Ada saran lain?
darrenp
2
@ Darrenp: Ummm ..., tulis ulang kode asli untuk bekerja dengan InputStreamlebih dari JNI? Sayangnya, tidak banyak opsi untuk Anda.
CommonsWare
1
Itu berguna untuk diketahui. Terimakasih atas tanggapan Anda. Saya telah menemukan bahwa kita sekarang mengirimkan gambar ke C ++ dalam memori daripada melalui file sehingga kita sekarang dapat menggunakan InputStreambukan file (yang hebat). Hanya pembacaan tag EXIF ​​yang sedikit rumit dan memerlukan pustaka Drew Noakes . Terima kasih banyak untuk komentar Anda.
darrenp

Jawaban:

108

Coba ini:

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

}

Mungkin butuh

@SuppressLint ("NewApi")

untuk

takePersistableUriPermission

penemu
sumber
1
Maukah Anda menguraikan apa yang dilakukan kode KitKat? Apakah ini memerlukan izin baru? Kode KitKat pra berfungsi juga untuk saya di KitKat. Jadi mengapa saya memilih untuk menggunakan kode yang berbeda untuk KitKat? Terima kasih.
Michael Greifeneder
67
sepertinya kita tidak bisa mendapatkan jalur dari sdks baru uri. Sayang sekali Google membuat perubahan semacam ini tanpa dokumen dan pengumuman yang benar.
user65721
1
Bisakah Anda menjelaskan cara mendapatkan URL file? Saya ingin mendapatkan path asli di sdcard. Misalnya, jika ini adalah gambar, saya ingin mendapatkan path ini /storage/sdcard0/DCIM/Camera/IMG_20131118_153817_119.jpg alih-alih dokumen Uri.
Álvaro
4
Berdasarkan dokumen KitKat ( developer.android.com/about/versions/... ) ini mungkin bukan yang dibutuhkan OP kecuali dia memang bermaksud menggunakan / mengedit dokumen yang dimiliki oleh aplikasi lain. Jika OP menginginkan salinan atau melakukan hal-hal dengan cara yang konsisten dengan versi yang lebih lama, jawaban oleh @voytez akan lebih sesuai.
Colin M.
8
Ini tidak bekerja untuk saya. Saya mendapatkan pengecualian berikut (stok 4.4.2): E / AndroidRuntime (29204): Disebabkan oleh: java.lang.SecurityException: Bendera yang diminta 0x1, tetapi hanya 0x0 yang diizinkan
Russell Stewart
177

Ini tidak memerlukan izin khusus, dan berfungsi dengan Kerangka Akses Penyimpanan, serta ContentProviderpola tidak resmi (jalur file di _datalapangan).

/**
 * 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());
}

Lihat versi terbaru dari metode ini di sini .

Paul Burke
sumber
2
Ini bekerja luar biasa pada 4.4 Nexus 5 Documents UI dan beberapa perangkat KitKat lainnya menggunakan aplikasi galeri standar, terima kasih Paul!
Josh
1
Terima kasih untuk ini, saya butuh waktu lama untuk mencapai sejauh ini dengan SD 19 !! Masalah saya adalah perangkat saya menggunakan Google drive sebagai browser file. Jika file ada di jalur gambar perangkat mendapat baik-baik saja tetapi jika file pada drive itu tidak terbuka. Mungkin saya hanya perlu melihat berurusan dengan membuka gambar dari google drive. Masalahnya adalah aplikasi saya ditulis untuk menggunakan jalur file dan mendapatkan gambar menggunakan insampling ...
RuAware
2
@RuAware Ketika Anda memilih file Drive, itu mengembalikan Authority: com.google.android.apps.docs.storagedan Segments: [document, acc=1;doc=667]. Saya tidak yakin, tetapi anggap bahwa docnilainya adalah UriID yang dapat Anda tolak. Anda mungkin perlu izin untuk diatur sebagaimana terinci dalam "Otorisasi aplikasi Anda di Android" di sini: developers.google.com/drive/integrate-android-ui . Harap perbarui di sini jika Anda mengetahuinya.
Paul Burke
30
ini benar-benar mengerikan! Anda tidak harus terus menyebarkan kode yang "menipu" seperti ini. itu hanya mendukung aplikasi sumber yang Anda ketahui polanya, dan inti dari model penyedia dokumen adalah mendukung sumber yang sewenang-wenang
j__m
2
Ini _datatidak akan berfungsi ketika ContentProvider tidak mendukungnya. Disarankan untuk mengikuti instruksi @CommonsWare dan tidak menggunakan path file lengkap lagi, karena itu bisa berupa file di Dropbox cloud, bukan file nyata.
soshial
67

Punya masalah yang sama, mencoba solusi di atas tetapi meskipun itu bekerja secara umum, untuk beberapa alasan saya mendapatkan penolakan izin pada penyedia konten Uri untuk beberapa gambar meskipun saya memiliki android.permission.MANAGE_DOCUMENTSizin yang ditambahkan dengan benar.

Pokoknya menemukan solusi lain yang memaksa membuka galeri gambar bukannya tampilan dokumen KITKAT dengan:

// KITKAT

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

dan kemudian memuat gambar:

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

EDIT

ACTION_OPEN_DOCUMENT mungkin mengharuskan Anda untuk tetap menggunakan flag izin dll dan umumnya menghasilkan Pengecualian Keamanan ...

Solusi lain adalah dengan menggunakan ACTION_GET_CONTENTkombinasi c.getContentResolver().openInputStream(selectedImageURI)yang akan bekerja pada pra-KK dan KK. Kitkat akan menggunakan tampilan dokumen baru kemudian dan solusi ini akan bekerja dengan semua aplikasi seperti Foto, Galeri, File Explorer, Dropbox, Google Drive dll ...) tetapi ingat bahwa ketika menggunakan solusi ini Anda harus membuat gambar di onActivityResult()dan menyimpannya di Kartu SD misalnya. Menciptakan gambar ini dari uri yang disimpan pada peluncuran aplikasi berikutnya akan membuat Pengecualian Keamanan pada penyelesai konten bahkan ketika Anda menambahkan bendera izin seperti yang dijelaskan dalam dokumen Google API (itulah yang terjadi ketika saya melakukan beberapa pengujian)

Selain itu, Panduan API Pengembang Android menyarankan:

ACTION_OPEN_DOCUMENT tidak dimaksudkan sebagai pengganti untuk ACTION_GET_CONTENT. Yang harus Anda gunakan tergantung pada kebutuhan aplikasi Anda:

Gunakan ACTION_GET_CONTENT jika Anda ingin aplikasi Anda cukup membaca / mengimpor data. Dengan pendekatan ini, aplikasi mengimpor salinan data, seperti file gambar.

Gunakan ACTION_OPEN_DOCUMENT jika Anda ingin aplikasi Anda memiliki akses jangka panjang dan berkelanjutan ke dokumen yang dimiliki oleh penyedia dokumen. Contohnya adalah aplikasi pengeditan foto yang memungkinkan pengguna mengedit gambar yang disimpan dalam penyedia dokumen.

voytez
sumber
1
Jawaban ini berisi informasi yang tepat untuk tujuan saya. Dengan syarat menggunakan ACTION_PICK dan EXTERNAL_CONTENT_URI di KitKat memberikan kemampuan yang sama untuk mendapatkan meta-data tentang Gambar di galeri melalui ContentResolver seperti dimungkinkan pada versi yang lebih lama hanya dengan menggunakan ACTION_GET_CONTENT.
Colin M.
@voytez, bisakah URI ini dikembalikan melalui pesan Anda yang dikonversi ke jalur gambar yang sebenarnya?
Snake
Saya percaya begitu, itu harus bekerja seperti sebelumnya KitKat karena kode ini memaksa membuka galeri gambar daripada melihat dokumen KK. Tetapi jika Anda bermaksud menggunakannya untuk membuat gambar maka solusi ini lebih baik karena mengonversi ke jalur nyata adalah langkah tambahan yang tidak perlu.
voytez
4
Bekerja untuk saya juga, bukan Intent.ACTION_GET_CONTENT. Pokoknya saya menyimpan Intent.createChooser()pembungkus pada yang baru Intent, untuk membiarkan pengguna memilih aplikasi untuk browsing, dan bekerja seperti yang diharapkan. Adakah yang bisa melihat kekurangan untuk solusi ini?
lorenzo-s
1
Bagi siapa pun yang bertanya-tanya kutipan berasal dari developer.android.com/guide/topics/providers/…
snark
38

Seperti yang disebutkan Commonsware, Anda tidak boleh berasumsi, bahwa aliran yang Anda dapatkan dapat ContentResolverdikonversi menjadi file.

Apa yang benar-benar harus Anda lakukan adalah membuka InputStreamdari ContentProvider, lalu membuat Bitmap darinya. Dan itu bekerja pada versi 4.4 dan sebelumnya juga, tidak perlu refleksi.

    //cxt -> current context

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

    }

Tentu saja jika Anda menangani gambar besar, Anda harus memuatnya dengan tepat inSampleSize: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html . Tapi itu topik lain.

Michał K
sumber
Ini tidak berfungsi untuk saya dengan Nexus 4 yang menjalankan Kitkat tetapi dengan Galaxy S3 yang menjalankan 4.1.2
Dan2552
@ Dan2552 bagian mana yang tidak berfungsi? Apakah Anda mendapat pengecualian?
Michał K
Ternyata saya menggunakan panggilan niat salah ke galeri. Saya menggunakan yang untuk semua jenis dokumen, tetapi dengan filter ekstensi file.
Dan2552
2
Jawaban yang sangat sederhana, terima kasih! Untuk orang lain yang mengikuti jawaban ini 'cxt' mengacu pada konteks saat ini dan biasanya akan menjadi 'ini'.
thomasforth
1
Ini mungkin berarti bahwa file tersebut tidak ada di sana. URI sepertinya baik-baik saja.
Michał K
33

Saya percaya tanggapan yang sudah diposting harus membuat orang pergi ke arah yang benar. Namun di sini adalah apa yang saya lakukan yang masuk akal untuk kode warisan yang saya perbarui. Kode lama menggunakan URI dari galeri untuk mengubah dan kemudian menyimpan gambar.

Sebelum 4.4 (dan google drive), URI akan terlihat seperti ini: konten: // media / eksternal / gambar / media / 41

Seperti yang dinyatakan dalam pertanyaan, mereka lebih sering terlihat seperti ini: content: //com.android.providers.media.documents/document/image: 3951

Karena saya membutuhkan kemampuan untuk menyimpan gambar dan tidak mengganggu kode yang sudah ada, saya hanya menyalin URI dari galeri ke folder data aplikasi. Kemudian berasal URI baru dari file gambar yang disimpan di folder data.

Inilah idenya:

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 */
 }

Catatan - copyAndClose () hanya file I / O untuk menyalin InputStream ke FileOutputStream. Kode tidak diposkan.

LEO
sumber
cukup pintar. Saya juga membutuhkan file aktual uri
Greater King
Anda adalah pahlawan saya, inilah yang saya butuhkan! berfungsi dengan baik untuk file Google Drive
mishkin
Pikirkan di luar kotak, kan? : D Kode ini berfungsi persis seperti yang saya harapkan.
WeirdElfB0y
2
memposting kode untuk copyAndClose, jawabannya tidak lengkap
Bartek Pacia
24

Hanya ingin mengatakan bahwa jawaban ini brilian dan saya menggunakannya untuk waktu yang lama tanpa masalah. Tetapi beberapa waktu yang lalu saya menemukan masalah ketika DownloadsProvider mengembalikan URI dalam format content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdoc.pdfdan karenanya aplikasi macet NumberFormatExceptionkarena tidak mungkin untuk menguraikan segmen uri selama. Tetapi raw:segmen mengandung uri langsung yang dapat digunakan untuk mengambil file yang direferensikan. Jadi saya telah memperbaikinya dengan mengganti isDownloadsDocument(uri) ifkonten dengan yang berikut:

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;
}
}
Bringoff
sumber
2
Bekerja dengan sempurna! Terima Kasih
mikemike396
8

Saya telah menggabungkan beberapa jawaban menjadi satu solusi kerja yang dihasilkan dengan path file

Jenis mime tidak relevan untuk tujuan contoh.

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

Hasil penanganan

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

}
Grzegorz Pawełczuk
sumber
saya menghadapi masalah .... uri.getPath () mengembalikan uri dengan / eksternal dan tidak mengembalikan jalur. saya menambahkan ini lagi jika ("konten" .equalsIgnoreCase (uri.getScheme ())) memblokir dan ini berfungsi dengan baik sekarang .. dapatkah Anda menjelaskan apa fungsinya
FaisalAhmed
filePath menjadi null .. Dalam uri: konten: //com.android.externalstorage.documents/document/799B-1419%3AScreenshot%2FScreenshot_20181117_162826.png
Bhavesh Moradiya
7

Pertanyaan

Cara mendapatkan jalur file aktual dari URI

Menjawab

Sepengetahuan saya, kita tidak perlu mendapatkan path file dari URI karena untuk sebagian besar kasus kita dapat langsung menggunakan URI untuk menyelesaikan pekerjaan kita (seperti 1. mendapatkan bitmap 2. Mengirim file ke server, dll. .)

1. Mengirim ke server

Kami dapat langsung mengirim file ke server hanya menggunakan URI.

Menggunakan URI kita bisa mendapatkan InputStream, yang dapat kita kirim langsung ke server menggunakan MultiPartEntity.

Contoh

/**
 * 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. Mendapatkan BitMap dari URI

Jika URI menunjuk ke gambar maka kita akan mendapatkan bitmap, kalau tidak 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;
}

Komentar

  1. Android tidak menyediakan metode apa pun untuk mendapatkan jalur file dari URI, dan dalam sebagian besar jawaban di atas kami telah mengkodekan beberapa konstanta, yang dapat merusak rilis fitur (maaf, saya mungkin salah).
  2. Sebelum pergi langsung ke solusi mendapatkan jalur file dari URI, coba jika Anda dapat memecahkan kasus penggunaan Anda dengan metode standar URI dan Android.

Referensi

  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
Vasanth
sumber
Terima kasih. Menggunakan Uri dan ContentResolver dengan cara ini menyederhanakan aplikasi saya secara signifikan ketika berhadapan dengan file.
jacksonakj
3

Bagi mereka yang masih menggunakan kode @Paul Burke dengan Android SDK versi 23 dan lebih tinggi, jika proyek Anda menemui kesalahan dengan mengatakan bahwa Anda kehilangan EXTERNAL_PERMISSION, dan Anda sangat yakin telah menambahkan izin pengguna dalam file AndroidManifest.xml Anda. Itu karena Anda mungkin di Android API 23 atau lebih tinggi dan Google membuatnya perlu untuk menjamin izin lagi saat Anda melakukan tindakan untuk mengakses file dalam runtime.

Itu berarti: Jika versi SDK Anda adalah 23 atau di atas, Anda diminta izin BACA & MENULIS saat Anda memilih file gambar dan ingin tahu URI dari itu.

Dan berikut ini adalah kode saya, di samping solusi Paul Burke. Saya menambahkan kode ini dan proyek saya mulai berfungsi dengan baik.

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

Dan di aktivitas & fragmen tempat Anda meminta 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);
}

Dalam kasus saya, CompatUtils.java adalah tempat saya mendefinisikan metode memverifikasiStoragePermissions (sebagai tipe statis sehingga saya dapat memanggilnya dalam aktivitas lain).

Juga harus lebih masuk akal jika Anda membuat status if terlebih dahulu untuk melihat apakah versi SDK saat ini di atas 23 atau tidak sebelum Anda memanggil metode verifikasiStoragePermissions.

Anthonyeef
sumber
2

Inilah yang saya lakukan:

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

CATATAN: managedQuery()metode sudah usang, jadi saya tidak menggunakannya.

Jawaban ini dari m3n0R pada pertanyaan android dapatkan jalur nyata oleh Uri.getPath () dan saya mengklaim tidak ada kredit. Saya hanya berpikir bahwa orang yang belum menyelesaikan masalah ini belum dapat menggunakan ini.

Isaac Zais
sumber
2
Ini bukan jawaban untuk aplikasi Galeri baru (ketat aplikasi "Penyedia Dokumen Media") di KitKat.
nagoya0
Aplikasi yang disebut penanya "Galeri" mungkin adalah pemilih file baru di kitkat. FYI: addictivetips.com/android/…
nagoya0
Saya melakukan hal yang sama dan mendapat IndexOutOfBound pada Nexus 5X, Android 6 pada baris ini:cursor.getString(idx);
Osify
1

Silakan coba untuk menghindari menggunakan metode takePersistableUriPermission karena itu menimbulkan pengecualian runtime untuk saya. / ** * Pilih dari galeri. * /

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 untuk hasil menangani data gambar:

@Override void yang dilindungi 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();
        }


    }
saranya
sumber
Di mana Anda menampilkan gambar dalam kasus kedua?
Quantum_VC
maaf saya gagal menambahkan baris kode ini jika mImgCropping.startCropImage (photoFile, AppConstants.REQUEST_IMAGE_CROP); lagi perlu memanggil fungsi imagecropping sesuai kebutuhan proyek saya
saranya
Jenis file apa itu photoFile dan mImgCropping?
Philip BH
1

Jika ada yang tertarik, saya membuat versi Kotlin yang berfungsi untuk 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)
}
0101100101
sumber
Saya hanya ingin kode kerja kotlin. Ini bekerja untuk saya. terima kasih
Harvi Sirja
1

Saya sudah mencoba beberapa jawaban di sini, dan saya pikir saya punya solusi yang akan bekerja setiap waktu dan mengelola izin juga.

Ini didasarkan pada solusi pintar dari LEO. Posting ini harus berisi semua kode yang Anda perlukan untuk membuat ini berfungsi, dan itu harus bekerja pada ponsel dan versi Android apa pun;)

Untuk memiliki kemampuan untuk mengambil file dari kartu SD, Anda akan memerlukan ini di manifes Anda:

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

Konstanta:

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

Periksa izin dan luncurkanImagePick jika memungkinkan

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

Respons izin

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

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

Kelola respons izin

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

Luncurkan pengambilan gambar

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
}

Kelola respons Pilihan gambar

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

Itu semua orang; ini berfungsi untuk saya di semua telepon yang saya miliki.

Quentin G.
sumber
0

Ini adalah retasan total, tapi inilah yang saya lakukan ...

Jadi saat bermain dengan menyiapkan DocumentsProvider , saya perhatikan bahwa kode sampel (dalam getDocIdForFile, sekitar baris 450) menghasilkan id unik untuk dokumen yang dipilih berdasarkan jalur (unik) file relatif terhadap akar yang ditentukan yang Anda berikan (yaitu, apa yang Anda setel mBaseDirpada saluran 96).

Jadi URI akhirnya terlihat seperti:

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

Seperti yang dikatakan oleh docs, itu mengasumsikan hanya satu root (dalam kasus saya itu Environment.getExternalStorageDirectory()tetapi Anda dapat menggunakan tempat lain ... kemudian mengambil path file, mulai dari root, dan menjadikannya ID unik, mengawali " root:". Jadi saya dapat menentukan lintasan dengan menghilangkan bagian "/document/root:"dari uri.getPath (), membuat lintasan file yang sebenarnya dengan melakukan sesuatu seperti ini:

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

Aku tahu. Ini memalukan, tetapi berhasil. Sekali lagi, ini bergantung pada Anda menggunakan penyedia dokumen Anda sendiri di aplikasi Anda untuk menghasilkan ID dokumen.

(Juga, ada cara yang lebih baik untuk membangun jalan yang tidak menganggap "/" adalah pemisah jalur, dll. Tapi Anda mendapatkan ide.)

Fattire
sumber
Untuk membalas sendiri dengan pemikiran yang lebih gila - jika aplikasi Anda sudah menangani file://maksud dari pemetik file eksternal, Anda juga dapat memeriksa otoritas, seperti pada contoh di atas untuk memastikannya dari penyedia kustom Anda, dan jika demikian Anda bisa juga gunakan jalur untuk "memalsukan" file://maksud baru menggunakan jalur yang Anda ekstrak, lalu StartActivity()dan biarkan aplikasi Anda mengambilnya. Saya tahu, mengerikan.
Fattire
0

Ini bekerja dengan baik untuk saya:

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());
    }
}
Rafa
sumber
lupakan dumpImageMetaData (uri); itu tidak perlu
Rafa
0

Membangun berdasarkan jawaban Paul Burke Saya menghadapi banyak masalah dalam menyelesaikan jalur URI kartu SD eksternal karena sebagian besar fungsi "built-in" yang disarankan mengembalikan jalur yang tidak dapat diselesaikan ke file.

Namun, ini adalah pendekatan saya tentang // TODO-nya menangani volume non-primer .

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

Catatan itu tergantung pada hierarki yang mungkin berbeda pada setiap produsen ponsel - Saya belum menguji semuanya (sejauh ini berfungsi dengan baik pada Xperia Z3 API 23 dan Samsung Galaxy A3 API 23).

Harap konfirmasi jika tidak berkinerja baik di tempat lain.

Evusas
sumber
-1

@paul burke jawaban berfungsi dengan baik untuk gambar kamera dan galeri untuk API level 19 ke atas, tetapi tidak berfungsi jika SDK minimum proyek Android Anda diatur di bawah 19, dan beberapa jawaban yang merujuk di atas tidak berfungsi untuk galeri dan kamera. Baiklah, saya telah memodifikasi kode @paul burke yang berfungsi untuk level API di bawah 19. Di bawah ini adalah kodenya.

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;
}
parvez rafi
sumber
Saya menerima java.lang.IllegalArgumentException: Tidak ada kolom yang diminta yang dapat diberikan ketika memilih gambar Google Doc
dirkoneill
@ bandone, saya mendapatkan Pengecualian yang sama. Apakah Anda menemukan perbaikan?
Henry
-4

Jawaban atas pertanyaan Anda adalah Anda harus memiliki izin. Ketikkan kode berikut dalam file manifes.xml Anda:

<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>`

Itu bekerja untuk saya ...

ashwin
sumber