Galeria Androida na Androidzie 4.4 (KitKat) zwraca inny identyfikator URI dla Intent.ACTION_GET_CONTENT

214

Przed KitKat (lub przed nową galerią) Intent.ACTION_GET_CONTENTzwrócił taki identyfikator URI

content: // media / external / images / media / 3951.

Użycie ContentResolveri zapytanie dla MediaStore.Images.Media.DATAzwróciło adres URL pliku.

Jednak w KitKat galeria zwraca URI (poprzez „Last”) w następujący sposób:

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

Jak sobie z tym poradzić?

Michael Greifeneder
źródło
21
Poza mankietem znajdowałbym sposoby korzystania z treści, które nie wymagają bezpośredniego dostępu do pliku. Na przykład Uripowinno to być możliwe do otwarcia jako strumień przez ContentResolver. Od dawna denerwuję się aplikacjami, które zakładają, content:// Uriże plik reprezentujący plik można zawsze przekształcić w plik File.
CommonsWare
1
@ CommonsWare, jeśli chcę zapisać ścieżkę obrazu w sqlite db, aby móc ją później otworzyć, czy powinienem zapisać URI lub ścieżkę do pliku bezwzględnego?
Snake,
2
@CommonsWare Zgadzam się z twoją nerwowością. :-) Jednak muszę mieć możliwość przekazania nazwy pliku (obrazu) do kodu natywnego. Rozwiązaniem jest skopiowanie danych uzyskanych za pomocą polecenia InputStreamon ContentResolverdo wcześniej wyznaczonego miejsca, aby miało znaną nazwę pliku. Wydaje mi się to jednak marnotrawstwem. Jakieś inne sugestie?
darrenp
2
@darrenp: Ummm ..., przepisać natywny kod do pracy z InputStreamponad JNI? Niestety, nie ma dla ciebie aż tylu opcji.
CommonsWare 15.01.2014
1
Warto wiedzieć. Dzięki za twoją odpowiedź. Od tego czasu dowiedziałem się, że teraz przekazujemy obraz do C ++ w pamięci, a nie przez plik, dzięki czemu możemy teraz używać InputStreamzamiast pliku (co jest świetne). Tylko odczyt znaczników EXIF ​​jest nieco trudny i wymaga biblioteki Drew Noakesa . Wielkie dzięki za komentarze.
darrenp

Odpowiedzi:

108

Spróbuj tego:

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

}

Prawdopodobnie potrzebujesz

@SuppressLint („NewApi”)

dla

takePersistableUriPermission

znalazca
źródło
1
Czy mógłbyś opracować, co robi kod KitKat? Czy to wymaga nowego pozwolenia? Wstępny kod KitKat również działa dla mnie na KitKat. Dlaczego więc miałbym używać innego kodu dla KitKat? Dzięki.
Michael Greifeneder,
67
wygląda na to, że nie możemy uzyskać ścieżki z nowego sdks uri. Szkoda też, że Google dokonał tego rodzaju zmian bez odpowiedniej dokumentacji i ogłoszenia.
user65721,
1
Czy możesz wyjaśnić, jak uzyskać adres URL pliku? Chciałbym uzyskać prawdziwą ścieżkę w sdcard. Na przykład, jeśli jest to zdjęcie, chciałbym uzyskać tę ścieżkę /storage/sdcard0/DCIM/Camera/IMG_20131118_153817_119.jpg zamiast Uri dokumentu.
Álvaro,
4
Na podstawie dokumentacji KitKat ( developer.android.com/about/versions/... ) OP może nie być tym, czego potrzebuje OP, chyba że zamierza zamiar używać / edytować dokumenty będące własnością innych aplikacji. Jeśli OP chce kopiować lub robić rzeczy w sposób zgodny ze starszymi wersjami, odpowiedź @voytez byłaby bardziej odpowiednia.
Colin M.,
8
To nie działa dla mnie. Dostaję następujący wyjątek (w magazynie 4.4.2): E / AndroidRuntime (29204): Spowodowany przez: java.lang.SecurityException: Żądane flagi 0x1, ale dozwolone są tylko 0x0
Russell Stewart
177

Nie wymaga to żadnych specjalnych uprawnień i działa z Storage Access Framework, a także z nieoficjalnym ContentProviderwzorcem (ścieżka pliku w _datapolu).

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

Zobacz aktualną wersję tej metody tutaj .

Paul Burke
źródło
2
Działa to fantastycznie w interfejsie użytkownika 4.4 Dokumentów Nexusa 5 i na niektórych innych urządzeniach KitKat, korzystających ze standardowych aplikacji galerii, dzięki Paul!
Josh
1
Dzięki za to zajęło mi wieki, aby dostać się tak daleko z SDK 19 !! Mój problem polega na tym, że moje urządzenie używa dysku Google jako przeglądarki plików. Jeśli plik znajduje się na ścieżce obrazu urządzenia, wszystko jest w porządku, ale jeśli plik znajduje się na dysku, nie można go otworzyć. Może po prostu muszę spojrzeć na radzenie sobie z otwieraniem zdjęć z dysku Google. Chodzi o to, że moja aplikacja jest napisana, aby używać ścieżki do pliku i uzyskiwać obraz za pomocą insampling ...
RuAware
2
@RuAware Po wybraniu pliku Dysku, zwraca on Authority: com.google.android.apps.docs.storagei Segments: [document, acc=1;doc=667]. Nie jestem pewien, ale załóżmy, że docwartością jest Uriidentyfikator, o który możesz zapytać. Najprawdopodobniej będziesz potrzebować uprawnień zgodnie z opisem w „Autoryzuj swoją aplikację na Androida” tutaj: developers.google.com/drive/integrate-android-ui . Zaktualizuj tutaj, jeśli to wymyślisz.
Paul Burke,
30
to jest absolutnie okropne! nie powinieneś dalej rozpowszechniać kodu, który tak „oszukuje”. obsługuje tylko aplikacje źródłowe, dla których znasz wzorzec, a cały model modelu dostawcy dokumentów ma obsługiwać dowolne źródła
j__m
2
_dataNie będzie działać, gdy ContentProvider nie obsługuje. Zaleca się postępować zgodnie z instrukcjami @CommonsWare i nie używać już pełnej ścieżki do pliku, ponieważ może to być plik w chmurze Dropbox zamiast prawdziwego pliku.
soshial
67

Miałem ten sam problem, wypróbowałem powyższe rozwiązanie, ale chociaż ogólnie działało, z jakiegoś powodu otrzymywałem odmowę zgody na dostawcę treści Uri dla niektórych obrazów, chociaż android.permission.MANAGE_DOCUMENTSpozwolenie zostało poprawnie dodane.

W każdym razie znalazłem inne rozwiązanie, które wymusza otwarcie galerii zdjęć zamiast widoku dokumentów KITKAT z:

// KITKAT

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

a następnie załaduj obraz:

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

EDYTOWAĆ

ACTION_OPEN_DOCUMENT może wymagać utrzymania flag uprawnień itp. i generalnie często powoduje wyjątki bezpieczeństwa ...

Innym rozwiązaniem jest użycie ACTION_GET_CONTENTkombinacji, z c.getContentResolver().openInputStream(selectedImageURI)którą będzie działać zarówno na wersjach pre-KK, jak i KK. Kitkat użyje wtedy widoku nowych dokumentów, a to rozwiązanie będzie działać ze wszystkimi aplikacjami, takimi jak Zdjęcia, Galeria, Eksplorator plików, Dropbox, Dysk Google itp.), Ale pamiętaj, że korzystając z tego rozwiązania, musisz utworzyć obraz w swoim onActivityResult()i przechowywać go na Na przykład karta SD. Ponowne odtworzenie tego obrazu z zapisanego identyfikatora URI przy następnym uruchomieniu aplikacji spowodowałoby wyjątek dotyczący bezpieczeństwa w narzędziu rozpoznawania treści, nawet jeśli dodasz flagi uprawnień zgodnie z opisem w dokumentacji interfejsu API Google (tak się stało, gdy przeprowadziłem testy)

Ponadto Wytyczne API dla programistów Androida sugerują:

ACTION_OPEN_DOCUMENT nie jest zamiennikiem ACTION_GET_CONTENT. Ten, którego należy użyć, zależy od potrzeb aplikacji:

Użyj ACTION_GET_CONTENT, jeśli chcesz, aby Twoja aplikacja po prostu odczytywała / importowała dane. Dzięki takiemu podejściu aplikacja importuje kopię danych, na przykład plik obrazu.

Użyj ACTION_OPEN_DOCUMENT, jeśli chcesz, aby Twoja aplikacja miała długoterminowy, stały dostęp do dokumentów będących własnością dostawcy dokumentów. Przykładem może być aplikacja do edycji zdjęć, która pozwala użytkownikom edytować obrazy zapisane w dostawcy dokumentów.

Voytez
źródło
1
Ta odpowiedź zawierała odpowiednie informacje do moich celów. Warunkowo użycie ACTION_PICK i EXTERNAL_CONTENT_URI na KitKat zapewniło taką samą możliwość uzyskiwania metadanych o obrazach w galerii za pośrednictwem ContentResolver, jak to możliwe w starszych wersjach za pomocą ACTION_GET_CONTENT.
Colin M.,
@voytez, czy ten identyfikator URI powrócił za pośrednictwem wiadomości przekonwertowanej na pełną rzeczywistą ścieżkę obrazu?
Snake,
Uważam, że tak powinno działać podobnie jak przed KitKat, ponieważ ten kod wymusza otwarcie galerii obrazów zamiast widoku dokumentów KK. Ale jeśli zamierzasz użyć go do stworzenia obrazu, to rozwiązanie jest lepsze, ponieważ konwersja na prawdziwą ścieżkę jest dodatkowym niepotrzebnym krokiem.
voytez
4
Pracowałem też dla mnie, zamiast Intent.ACTION_GET_CONTENT. W każdym razie zachowałem Intent.createChooser()opakowanie na nowym Intent, aby umożliwić użytkownikowi wybór aplikacji do przeglądania, i działałem zgodnie z oczekiwaniami. Czy ktoś może zobaczyć wady tego rozwiązania?
lorenzo-s
1
Dla każdego, kto zastanawia się, cytat pochodzi z developer.android.com/guide/topics/providers/…
snark
38

Tak jak wspomniano w Commonsware, nie należy zakładać, że strumień, który dostajesz, ContentResolvermożna przekształcić w plik.

Co naprawdę powinieneś zrobić, to otworzyć InputStreamz ContentProvider, a następnie utworzyć z niego mapę bitową. Działa również w wersji 4.4 i wcześniejszych, bez potrzeby refleksji.

    //cxt -> current context

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

    }

Oczywiście, jeśli zajmujesz się dużymi obrazami, powinieneś załadować je odpowiednimi inSampleSize: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html . Ale to inny temat.

Michał K.
źródło
To nie działa dla mnie na Nexusie 4 z KitKatem, ale na Galaxy S3 4.1.2
Dan2552
@ Dan2552 która część nie działa? Czy otrzymujesz jakiś wyjątek?
Michał K,
Okazało się, że korzystałem z niewłaściwego połączenia do galerii. Korzystałem z jednego, który był do dowolnego rodzaju dokumentów, ale z filtrem rozszerzenia pliku.
Dan2552,
2
Cóż za piękna, prosta odpowiedź, dziękuję! Dla innych po tej odpowiedzi „cxt” odnosi się do bieżącego kontekstu i zwykle będzie to „to”.
thomasforth
1
Prawdopodobnie oznacza to, że pliku po prostu nie ma. Identyfikator URI wydaje się OK.
Michał K
33

Uważam, że już opublikowane odpowiedzi powinny skłonić ludzi do pójścia we właściwym kierunku. Jednak to, co zrobiłem, miało sens dla starszego kodu, który aktualizowałem. Stary kod używał identyfikatora URI z galerii do zmiany, a następnie zapisania obrazów.

Przed wersją 4.4 (i dyskiem Google) identyfikatory URI wyglądałyby następująco: content: // media / external / images / media / 41

Jak stwierdzono w pytaniu, częściej wyglądają tak: content: //com.android.providers.media.documents/document/image: 3951

Ponieważ potrzebowałem możliwości zapisywania obrazów i nie zakłócania już istniejącego kodu, właśnie skopiowałem identyfikator URI z galerii do folderu danych aplikacji. Następnie powstał nowy identyfikator URI z zapisanego pliku obrazu w folderze danych.

Oto pomysł:

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

Uwaga - copyAndClose () po prostu wykonuje operacje we / wy pliku, aby skopiować InputStream do FileOutputStream. Kod nie został opublikowany.

LEW
źródło
całkiem sprytny. ja też potrzebowałem rzeczywistego pliku
URI
jesteś moim bohaterem, właśnie tego potrzebowałem! działa świetnie również w przypadku plików na Dysku Google
mishkin
Myśl nieszablonowo, prawda? : D Ten kod działa dokładnie tak, jak się spodziewałem.
WeirdElfB0y
2
opublikuj kod dla copyAndClose, odpowiedź nie jest kompletna
Bartek Pacia
24

Chciałem tylko powiedzieć, że ta odpowiedź jest genialna i używam jej przez długi czas bez problemów. Ale jakiś czas temu natknąłem się na problem polegający na tym, że DownloadsProvider zwraca identyfikatory URI w formacie content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdoc.pdfi dlatego aplikacja ulega awarii, NumberFormatExceptionponieważ nie można analizować segmentów URI tak długo. Ale raw:segment zawiera bezpośrednie URI, które można wykorzystać do odzyskania pliku, do którego istnieje odniesienie. Naprawiłem to, zastępując isDownloadsDocument(uri) iftreść następującymi:

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;
}
}
Odratować
źródło
2
Działa idealnie! Dziękuję
mikemike396,
8

Łączę wiele odpowiedzi w jedno działające rozwiązanie, które daje ścieżkę pliku

Typ MIME nie ma znaczenia dla przykładu.

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

Wynik obsługi

    @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
źródło
napotkałem problem .... uri.getPath () zwracał Uri z / external i nie zwracał ścieżki. dodałem to jeszcze, jeśli blok („content” .equalsIgnoreCase (uri.getScheme ())) i działa dobrze teraz .. czy możesz wyjaśnić, co to robi
FaisalAhmed
filePath staje się zerowy .. W Uri: content: //com.android.externalstorage.documents/document/799B-1419%3AScreenshot%2FScreenshot_20181117_162826.png
Bhavesh Moradiya
7

Pytanie

Jak uzyskać rzeczywistą ścieżkę pliku z identyfikatora URI

Odpowiedź

Według mojej wiedzy nie musimy pobierać ścieżki pliku z identyfikatora URI, ponieważ w większości przypadków możemy bezpośrednio użyć identyfikatora URI do wykonania naszej pracy (np. 1. pobranie mapy bitowej 2. Wysłanie pliku na serwer itp. .)

1. Wysyłanie na serwer

Możemy bezpośrednio wysłać plik na serwer za pomocą tylko identyfikatora URI.

Za pomocą URI możemy uzyskać InputStream, który możemy bezpośrednio wysłać na serwer za pomocą MultiPartEntity.

Przykład

/**
 * 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. Pobieranie mapy bitowej z identyfikatora URI

Jeśli identyfikator URI wskazuje na obraz, otrzymamy bitmapę, w przeciwnym razie 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;
}

Komentarze

  1. Android nie zapewnia żadnych metod pobierania ścieżki pliku z identyfikatora URI, aw większości powyższych odpowiedzi na stałe zapisaliśmy niektóre stałe, które mogą się zepsuć w wydaniu funkcji (przepraszam, mogę się mylić).
  2. Zanim przejdziesz bezpośrednio do rozwiązania dotyczącego pobierania ścieżki pliku z identyfikatora URI, spróbuj rozwiązać swój przypadek użycia za pomocą domyślnych metod identyfikatora URI i Androida.

Odniesienie

  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
źródło
Dziękuję Ci. Korzystanie z Uri i ContentResolver w ten sposób znacznie uprościło moją aplikację do obsługi plików.
jacksonakj
3

Dla tych, którzy nadal używają kodu @Paul Burke z Android SDK w wersji 23 i wyższej, jeśli Twój projekt napotkał błąd, mówiąc, że brakuje EXTERNAL_PERMISSION, i jesteś pewien, że dodałeś już uprawnienia użytkownika w pliku AndroidManifest.xml. Wynika to z faktu, że możesz korzystać z interfejsu API systemu Android 23 lub nowszego, a Google wymaga ponownego zagwarantowania zgody podczas wykonywania czynności dostępu do pliku w czasie wykonywania.

Oznacza to, że: jeśli twoja wersja zestawu SDK to 23 lub więcej, podczas wybierania pliku obrazu zostaniesz zapytany o uprawnienia do odczytu i zapisu i chcesz poznać jego identyfikator URI.

Poniżej znajduje się mój kod, oprócz rozwiązania Paula Burke'a. Dodam ten kod i mój projekt zacznie działać dobrze.

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

A w Twojej aktywności i fragmencie, w którym prosisz o identyfikator 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);
}

W moim przypadku CompatUtils.java to miejsce, w którym definiuję metodę VerifyStoragePermissions (jako typ statyczny, dzięki czemu mogę wywoływać ją w ramach innych działań).

Powinno to również mieć sens, jeśli najpierw sprawdzisz stan if, aby sprawdzić, czy bieżąca wersja zestawu SDK jest wyższa niż 23, czy nie, przed wywołaniem metody replaceStoragePermissions.

Anthonyeef
źródło
2

Tym się właśnie zajmuję:

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

UWAGA: managedQuery()metoda jest przestarzała, więc jej nie używam.

Ta odpowiedź pochodzi od m3n0R na pytanie android uzyskać prawdziwą ścieżkę przez Uri.getPath () i nie twierdzę, że kredyt. Pomyślałem, że ludzie, którzy jeszcze nie rozwiązali tego problemu, mogą z niego skorzystać.

Isaac Zais
źródło
2
To nie jest odpowiedź na nową aplikację Gallery (wyłącznie aplikację „Media Documents Provider”) na KitKat.
nagoya0
Aplikacja, którą pytający nazywa „Galerią”, jest prawdopodobnie nowym narzędziem do wybierania plików w KitKat. Do Twojej wiadomości: addictivetips.com/android/…
nagoya0
Robię podobnie i mam IndexOutOfBound na Nexus 5X, Android 6 na tej linii:cursor.getString(idx);
Osify
1

Staraj się unikać używania metody takePersistableUriPermission, ponieważ podniosła ona dla mnie wyjątek czasu wykonywania. / ** * Wybierz z galerii. * /

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 dla wyniku do obsługi danych obrazu:

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


    }
saranya
źródło
Gdzie wyświetlasz obraz w drugim przypadku?
Quantum_VC
przepraszam, że nie udało mi się dodać tego wiersza kodu w innym przypadku, jeśli mImgCropping.startCropImage (photoFile, AppConstants.REQUEST_IMAGE_CROP); jeszcze raz muszę wywołać funkcję kadrowania obrazu zgodnie z potrzebą mojego projektu
saranya
Jakim typem pliku jest photoFile i mImgCropping?
Philip BH
1

Jeśli ktoś jest zainteresowany, stworzyłem działającą wersję Kotlin dla 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
źródło
Chcę tylko działającego kodu kotlina. To dla mnie praca. dzięki
Harvi Sirja
1

Wypróbowałem kilka odpowiedzi tutaj i myślę, że mam rozwiązanie, które będzie działać za każdym razem i zarządza także uprawnieniami.

Opiera się na sprytnym rozwiązaniu LEO. Ten post powinien zawierać cały kod potrzebny do tego, aby działał, i powinien działać na każdym telefonie i wersji Androida;)

Aby móc wybrać plik z karty SD, potrzebujesz go w swoim manifeście:

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

Stałe:

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

Sprawdź uprawnienia i uruchom ImagePick, jeśli to możliwe

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

Odpowiedź na pozwolenie

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

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

Zarządzaj odpowiedzią na uprawnienia

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

Uruchom wybór obrazu

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
}

Zarządzaj odpowiedzią na pobranie obrazu

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

To wszystko ludzie; to działa dla mnie na wszystkich telefonach, które mam.

Quentin G.
źródło
0

To totalny hack, ale oto co zrobiłem ...

Podczas zabawy z konfigurowaniem DocumentsProvider zauważyłem, że przykładowy kod (w getDocIdForFile, około linii 450) generuje unikalny identyfikator dla wybranego dokumentu na podstawie ścieżki pliku (unikalnej) w stosunku do określonego katalogu głównego, który mu podałeś (to znaczy co ustawiłeś mBaseDirw linii 96).

URI wygląda więc tak:

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

Jak mówią doktorzy, zakłada tylko jeden katalog główny (w moim przypadku jest to możliwe, Environment.getExternalStorageDirectory()ale możesz użyć go gdzie indziej ... następnie pobiera ścieżkę pliku, zaczynając od katalogu głównego i czyni go unikalnym identyfikatorem, poprzedzającym „ root:”. może określić ścieżkę, eliminując "/document/root:„część z uri.getPath (), tworząc rzeczywistą ścieżkę do pliku, wykonując coś takiego:

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

Wiem. To wstyd, ale zadziałało. Ponownie, zależy to od ciebie, używając własnego dostawcy dokumentów w swojej aplikacji do wygenerowania identyfikatora dokumentu.

(Jest też lepszy sposób na zbudowanie ścieżki, która nie zakłada, że ​​„/” jest separatorem ścieżki itp. Ale masz pomysł.)

fattire
źródło
Aby odpowiedzieć sobie jeszcze bardziej szalonym pomysłem - jeśli Twoja aplikacja już obsługuje file://zamiary z zewnętrznego selektora plików, możesz również sprawdzić uprawnienia, jak w powyższym przykładzie, aby upewnić się, że pochodzi od niestandardowego dostawcy, a jeśli tak, możesz użyj również ścieżki, aby „wykuć” nowy file://zamiar, używając wyodrębnionej ścieżki, a następnie StartActivity()pozwól aplikacji ją odebrać. Wiem okropne.
fattire
0

To działało dobrze dla mnie:

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
źródło
zapomnij dumpImageMetaData (uri); nie jest to konieczne
Rafa
0

Opierając się na odpowiedzi Paula Burke napotkałem wiele problemów z rozwiązywaniem ścieżki URI zewnętrznej karty SD, ponieważ większość sugerowanych funkcji „wbudowanych” zwraca ścieżki, które nie są rozwiązywane do plików.

Jest to jednak moje podejście do jego // obsługi TODO woluminów innych niż podstawowe .

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

Uwaga: zależy to od hierarchii, która może być inna u każdego producenta telefonu - nie przetestowałem ich wszystkich (do tej pory działało dobrze na Xperia Z3 API 23 i Samsung Galaxy A3 API 23).

Potwierdź, jeśli nie działa dobrze gdzie indziej.

Evusas
źródło
-1

Odpowiedź @paul burke działa dobrze zarówno dla zdjęć z aparatu, jak i galerii dla interfejsu API poziomu 19 i wyższego, ale nie działa, jeśli minimalny zestaw SDK twojego projektu na Androida jest ustawiony poniżej 19, a niektóre powyższe odpowiedzi nie działają zarówno dla galerii, jak i aparat fotograficzny. Cóż, zmodyfikowałem kod @paul burke, który działa na poziomie API poniżej 19. Poniżej znajduje się kod.

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
źródło
Otrzymuję java.lang.IllegalArgumentException: Żadna z wymaganych kolumn nie może zostać podana przy wyborze obrazu Dokumentu Google
dirkoneill
@dirkoneill Dostaję ten sam wyjątek. Znalazłeś poprawkę?
Henry
-4

Odpowiedź na twoje pytanie brzmi: musisz mieć uprawnienia. Wpisz następujący kod w pliku manifest.xml:

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

To działało dla mnie ...

Ashwin
źródło