Die Android-Galerie unter Android 4.4 (KitKat) gibt unterschiedliche URI für Intent.ACTION_GET_CONTENT zurück

214

Vor KitKat (oder vor der neuen Galerie) wurde Intent.ACTION_GET_CONTENTeine URI wie diese zurückgegeben

Inhalt: // media / external / images / media / 3951.

Mit dem ContentResolverund Abfragen für wird MediaStore.Images.Media.DATAdie Datei-URL zurückgegeben.

In KitKat gibt die Galerie jedoch eine URI (über "Last") wie folgt zurück:

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

Wie gehe ich damit um?

Michael Greifeneder
quelle
21
Außerhalb der Manschette würde ich Möglichkeiten finden, den Inhalt zu verwenden, der keinen direkten Zugriff auf die Datei erfordert. Das sollte zum Beispiel Urials Stream über geöffnet werden können ContentResolver. Ich war lange nervös wegen Apps, die davon ausgehen, content:// Uridass eine Datei, die eine Datei darstellt, immer in eine konvertiert werden kann File.
CommonsWare
1
@ CommonsWare, Wenn ich einen Bildpfad in sqlite db speichern möchte, damit ich ihn später öffnen kann, sollte ich den URI oder den absoluten Dateipfad speichern?
Schlange
2
@ CommonsWare Ich stimme Ihrer Nervosität zu. :-) Ich muss jedoch in der Lage sein, einen Dateinamen (für ein Bild) an nativen Code zu übergeben. Eine Lösung besteht darin, die Daten, die mit einem InputStreamauf dem Feld erhalten wurden, ContentResolveran einen zuvor festgelegten Ort zu kopieren, damit sie einen bekannten Dateinamen haben. Das klingt für mich jedoch verschwenderisch. Irgendwelche anderen Vorschläge?
Darrenp
2
@darrenp: Ähm ..., den nativen Code neu schreiben, um mit einem InputStreamOver-JNI zu arbeiten? Leider gibt es nicht allzu viele Optionen für Sie.
CommonsWare
1
Das ist nützlich zu wissen. Vielen Dank für Ihre Antwort. Ich habe seitdem herausgefunden, dass wir das Bild jetzt im Speicher an C ++ übergeben und nicht über eine Datei, sodass wir jetzt eine InputStreamanstelle einer Datei verwenden können (was großartig ist). Nur das Lesen von EXIF-Tags ist etwas schwierig und erfordert die Bibliothek von Drew Noakes . Vielen Dank für Ihre Kommentare.
Darrenp

Antworten:

108

Versuche dies:

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

}

Wahrscheinlich brauchen

@ SuppressLint ("NewApi")

zum

takePersistableUriPermission

Finder
quelle
1
Würde es Ihnen etwas ausmachen, näher zu erläutern, was der KitKat-Code tut? Benötigt dies eine neue Erlaubnis? Der Pre-KitKat-Code funktioniert auch bei KitKat. Warum sollte ich einen anderen Code für KitKat verwenden? Vielen Dank.
Michael Greifeneder
67
es scheint, wir können keinen Pfad von neuen SDKS Uri bekommen. Es ist auch eine Schande, dass Google diese Art von Änderung ohne ordnungsgemäße Dokumentation und Ankündigung vorgenommen hat.
user65721
1
Können Sie erklären, wie Sie die Datei-URL erhalten? Ich möchte den realen Pfad in SD-Karte erhalten. Wenn es sich beispielsweise um ein Bild handelt, möchte ich diesen Pfad /storage/sdcard0/DCIM/Camera/IMG_20131118_153817_119.jpg anstelle des Dokuments Uri erhalten.
Álvaro
4
Basierend auf KitKat-Dokumenten ( developer.android.com/about/versions/… ) ist dies möglicherweise nicht das, was das OP benötigt, es sei denn, er beabsichtigt, Dokumente zu verwenden / zu bearbeiten, die Eigentum der anderen Anwendung (en) sind. Wenn das OP eine Kopie wünscht oder Dinge in einer Weise tun möchte, die mit älteren Versionen übereinstimmt, wäre die Antwort von @voytez angemessener.
Colin M.
8
Das funktioniert bei mir nicht. Ich erhalte die folgende Ausnahme (auf Lager 4.4.2): E / AndroidRuntime (29204): Auslöser: java.lang.SecurityException: Angeforderte Flags 0x1, aber nur 0x0 sind zulässig
Russell Stewart
177

Dies erfordert keine besonderen Berechtigungen und funktioniert mit dem Storage Access Framework sowie dem inoffiziellen ContentProviderMuster (Dateipfad im _dataFeld).

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

Eine aktuelle Version dieser Methode finden Sie hier .

Paul Burke
quelle
2
Dies funktionierte fantastisch auf einer 4.4 Nexus 5 Documents-Benutzeroberfläche und einigen anderen bevorzugten KitKat-Geräten mit den Standard-Galerie-Apps, danke Paul!
Josh
1
Danke dafür, ich habe ewig gebraucht, um mit SDK 19 so weit zu kommen !! Mein Problem ist, dass mein Gerät Google Drive als Dateibrowser verwendet. Wenn sich die Datei auf dem Geräte-Image-Pfad befindet, ist sie in Ordnung, aber wenn sich die Datei auf dem Laufwerk befindet, wird sie nicht geöffnet. Vielleicht muss ich mich nur mit dem Öffnen von Bildern von Google Drive befassen. Meine App wurde geschrieben, um den Dateipfad zu verwenden und das Bild mithilfe von Insampling abzurufen ...
RuAware
2
@ RuAware Wenn Sie eine Laufwerksdatei auswählen, gibt sie Authority: com.google.android.apps.docs.storageund zurück Segments: [document, acc=1;doc=667]. Ich bin nicht sicher, gehe aber davon aus, dass der docWert die UriID ist, gegen die Sie abfragen können. Sie müssen wahrscheinlich Berechtigungen einrichten, wie unter "Autorisieren Ihrer App auf Android" hier beschrieben: developer.google.com/drive/integrate-android-ui . Bitte aktualisieren Sie hier, wenn Sie es herausfinden.
Paul Burke
30
das ist absolut schrecklich! Sie sollten keinen Code weiter verbreiten, der so "betrügt". Es werden nur die Quell-Apps unterstützt, für die Sie das Muster kennen, und der Sinn des Dokumentenanbietermodells besteht darin, beliebige Quellen zu unterstützen
j__m
2
Das _datawürde nicht funktionieren, wenn ContentProvider es nicht unterstützt. Es wird empfohlen, die Anweisungen von @CommonsWare zu befolgen und nicht mehr den vollständigen Dateipfad zu verwenden, da es sich möglicherweise um eine Datei in der Dropbox-Cloud anstelle einer echten Datei handelt.
Soshial
67

Hatte das gleiche Problem, versuchte die obige Lösung, aber obwohl es im Allgemeinen funktionierte, erhielt ich aus irgendeinem Grund die Verweigerung der android.permission.MANAGE_DOCUMENTSBerechtigung für einige Bilder beim Uri-Inhaltsanbieter, obwohl ich die Berechtigung ordnungsgemäß hinzugefügt hatte.

Auf jeden Fall wurde eine andere Lösung gefunden, die das Öffnen der Bildergalerie anstelle der Ansicht von KITKAT-Dokumenten erzwingt:

// KITKAT

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

und laden Sie dann das Bild:

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

BEARBEITEN

ACTION_OPEN_DOCUMENT Möglicherweise müssen Sie Berechtigungsflags usw. beibehalten. Dies führt im Allgemeinen häufig zu Sicherheitsausnahmen ...

Eine andere Lösung besteht darin, die ACTION_GET_CONTENTKombination zu verwenden, mit c.getContentResolver().openInputStream(selectedImageURI)der sowohl bei Pre-KK als auch bei KK gearbeitet werden kann. Kitkat verwendet dann die Ansicht "Neue Dokumente" und diese Lösung funktioniert mit allen Apps wie Fotos, Galerie, Datei-Explorer, Dropbox, Google Drive usw.). Beachten Sie jedoch, dass Sie bei Verwendung dieser Lösung ein Bild in Ihrem erstellen onActivityResult()und darauf speichern müssen SD-Karte zum Beispiel. Wenn Sie dieses Bild beim nächsten App-Start aus der gespeicherten URL wiederherstellen, wird eine Sicherheitsausnahme für den Content Resolver ausgelöst, selbst wenn Sie Berechtigungsflags hinzufügen, wie in den Google API-Dokumenten beschrieben (genau das ist passiert, als ich einige Tests durchgeführt habe).

Zusätzlich schlagen die API-Richtlinien für Android-Entwickler Folgendes vor:

ACTION_OPEN_DOCUMENT ist kein Ersatz für ACTION_GET_CONTENT. Welche Sie verwenden sollten, hängt von den Anforderungen Ihrer App ab:

Verwenden Sie ACTION_GET_CONTENT, wenn Ihre App einfach Daten lesen / importieren soll. Bei diesem Ansatz importiert die App eine Kopie der Daten, z. B. eine Bilddatei.

Verwenden Sie ACTION_OPEN_DOCUMENT, wenn Ihre App langfristig und dauerhaft auf Dokumente eines Dokumentanbieters zugreifen soll. Ein Beispiel wäre eine Fotobearbeitungs-App, mit der Benutzer Bilder bearbeiten können, die in einem Dokumentanbieter gespeichert sind.

voytez
quelle
1
Diese Antwort enthielt die richtigen Informationen für meine Zwecke. Die bedingte Verwendung von ACTION_PICK und EXTERNAL_CONTENT_URI in KitKat bot die gleiche Möglichkeit, Metadaten zu Bildern in der Galerie über ContentResolver abzurufen, wie dies bei älteren Versionen mit einfach ACTION_GET_CONTENT möglich ist.
Colin M.
@voytez, Kann dieser URI durch Ihre Nachricht zurückgegeben werden und in den vollständigen realen Pfad des Bildes konvertiert werden?
Schlange
Ich glaube schon, es sollte genau wie vor KitKat funktionieren, da dieser Code das Öffnen der Bildergalerie anstelle der Ansicht von KK-Dokumenten erzwingt. Wenn Sie jedoch beabsichtigen, damit ein Image zu erstellen, ist diese Lösung besser, da die Konvertierung in einen realen Pfad ein zusätzlicher unnötiger Schritt ist.
Voytez
4
Arbeitete auch für mich statt Intent.ACTION_GET_CONTENT. Wie auch immer, ich habe den Intent.createChooser()Wrapper auf dem neuen Stand gehalten Intent, damit der Benutzer die App zum Surfen auswählen kann, und habe wie erwartet gearbeitet. Kann jemand Nachteile für diese Lösung sehen?
Lorenzo-s
1
Für alle, die sich fragen, kommt das Zitat von developer.android.com/guide/topics/providers/…
Snark
38

Genau wie Commonsware erwähnt, sollten Sie nicht davon ausgehen, dass der Stream, über den Sie erhalten ContentResolver in eine Datei ist.

Was Sie wirklich tun sollten, ist, das InputStreamvon zu öffnen ContentProviderund dann eine Bitmap daraus zu erstellen. Und es funktioniert auch mit 4.4 und früheren Versionen, ohne dass Überlegungen erforderlich sind.

    //cxt -> current context

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

    }

Wenn Sie mit großen Bildern umgehen, sollten Sie diese natürlich mit den entsprechenden Bildern laden inSampleSize folgenden Informationen : http://developer.android.com/training/displaying-bitmaps/load-bitmap.html . Aber das ist ein anderes Thema.

Michał K.
quelle
Dies funktioniert nicht für mich mit einem Nexus 4 mit Kitkat, aber mit einem Galaxy S3 mit 4.1.2
Dan2552
@ Dan2552 Welcher Teil funktioniert nicht? Bekommst du eine Ausnahme?
Michał K
Es stellte sich heraus, dass ich den falschen Absichtsaufruf für die Galerie verwendet habe. Ich habe eines verwendet, das für alle Arten von Dokumenten vorgesehen war, jedoch mit einem Dateierweiterungsfilter.
Dan2552
2
Was für eine sehr einfache Antwort, danke! Für andere, die dieser Antwort folgen, bezieht sich 'cxt' auf den aktuellen Kontext und ist normalerweise 'this'.
Thomasforth
1
Dies bedeutet wahrscheinlich, dass die Datei einfach nicht vorhanden ist. Die URI scheint in Ordnung zu sein.
Michał K
33

Ich glaube, die bereits veröffentlichten Antworten sollten die Leute in die richtige Richtung bringen. Hier ist jedoch, was ich getan habe, was für den Legacy-Code, den ich aktualisierte, Sinn machte. Der Legacy-Code verwendete den URI aus der Galerie, um die Bilder zu ändern und dann zu speichern.

Vor 4.4 (und Google Drive) sahen die URIs folgendermaßen aus: content: // media / external / images / media / 41

Wie in der Frage angegeben, sehen sie häufiger so aus: content: //com.android.providers.media.documents/document/image: 3951

Da ich die Möglichkeit brauchte, Bilder zu speichern und den bereits vorhandenen Code nicht zu stören, habe ich einfach den URI aus der Galerie in den Datenordner der App kopiert. Dann entstand ein neuer URI aus der gespeicherten Bilddatei im Datenordner.

Hier ist die Idee:

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

Hinweis - copyAndClose () führt nur Datei-E / A aus, um InputStream in einen FileOutputStream zu kopieren. Der Code wird nicht veröffentlicht.

LÖWE
quelle
ziemlich clever. Ich brauchte auch die eigentliche Datei uri
GreaterKing
Du bist mein Held, genau das habe ich gebraucht! funktioniert auch gut für Google Drive-Dateien
Mischkin
Denken Sie über den Tellerrand hinaus, oder? : D Dieser Code funktioniert genau so, wie ich es erwartet hatte.
WeirdElfB0y
2
poste den Code für copyAndClose, die Antwort ist nicht vollständig
Bartek Pacia
24

Ich wollte nur sagen, dass diese Antwort brillant ist und ich sie lange Zeit ohne Probleme benutze. Aber vor einiger Zeit bin ich auf ein Problem gestoßen, dass DownloadsProvider URIs im Format zurückgibt content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdoc.pdfund daher die App abstürzt, NumberFormatExceptionda es unmöglich ist, ihre Uri-Segmente so lange zu analysieren. Das raw:Segment enthält jedoch eine direkte URL, mit der eine referenzierte Datei abgerufen werden kann. Also habe ich es behoben, indem ich isDownloadsDocument(uri) ifInhalte durch folgende ersetzt habe:

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
quelle
2
Funktioniert perfekt! Vielen Dank
mikemike396
8

Ich habe mehrere Antworten zu einer funktionierenden Lösung kombiniert, die sich aus dem Dateipfad ergibt

Der MIME-Typ ist für den Beispielzweck irrelevant.

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

Handhabungsergebnis

    @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
quelle
Ich hatte ein Problem ... uri.getPath () gab uri mit / external zurück und es gab keinen Pfad zurück. Ich habe dies hinzugefügt, wenn ("content" .equalsIgnoreCase (uri.getScheme ())) blockiert und dies jetzt gut funktioniert.
Kannst
filePath wird null. In uri: content: //com.android.externalstorage.documents/document/799B-1419%3AScreenshot%2FScreenshot_20181117_162826.png
Bhavesh Moradiya
7

Frage

So erhalten Sie einen tatsächlichen Dateipfad von einem URI

Antworten

Meines Wissens müssen wir den Dateipfad nicht von einem URI abrufen, da wir in den meisten Fällen den URI direkt verwenden können, um unsere Arbeit zu erledigen (z. B. 1. Bitmap abrufen 2. Eine Datei an den Server senden usw. .)

1. Senden an den Server

Wir können die Datei direkt über den URI an den Server senden.

Mit dem URI können wir InputStream erhalten, den wir mit MultiPartEntity direkt an den Server senden können.

Beispiel

/**
 * 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. Abrufen einer BitMap von einem URI

Wenn der URI auf das Bild zeigt, erhalten wir eine Bitmap, andernfalls 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;
}

Bemerkungen

  1. Android bietet keine Methoden zum Abrufen des Dateipfads von einem URI. In den meisten der oben genannten Antworten haben wir einige Konstanten fest codiert, die möglicherweise in der Funktionsfreigabe beschädigt werden (Entschuldigung, ich kann mich irren).
  2. Bevor Sie direkt zu einer Lösung für das Abrufen des Dateipfads von einem URI gehen, versuchen Sie, Ihren Anwendungsfall mit einem URI und Android-Standardmethoden zu lösen.

Referenz

  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
quelle
Danke dir. Die Verwendung von Uri und ContentResolver auf diese Weise hat meine Anwendung beim Umgang mit Dateien erheblich vereinfacht.
Jacksonakj
6

Diese Android-Bibliothek behandelt die Falländerungen in KitKat (einschließlich der älteren Versionen - 2.1+):
https://github.com/iPaulPro/aFileChooser

Verwenden Sie die String path = FileUtils.getPath(context, uri), um den zurückgegebenen Uri in eine Pfadzeichenfolge zu konvertieren, die auf allen Betriebssystemversionen verwendet werden kann. Weitere Informationen finden Sie hier: https://stackoverflow.com/a/20559175/860488

Morten Holmgaard
quelle
3

Für diejenigen, die den Code von @Paul Burke mit Android SDK Version 23 und höher noch verwenden, wenn in Ihrem Projekt der Fehler aufgetreten ist, dass EXTERNAL_PERMISSION fehlt, und Sie sehr sicher sind, dass Sie Ihrer AndroidManifest.xml-Datei bereits Benutzerberechtigungen hinzugefügt haben. Dies liegt daran, dass Sie möglicherweise in Android API 23 oder höher und Google die Berechtigung erneut garantieren müssen, während Sie die Aktion ausführen, um zur Laufzeit auf die Datei zuzugreifen.

Das bedeutet: Wenn Ihre SDK-Version 23 oder höher ist, werden Sie bei der Auswahl der Bilddatei um die Berechtigung LESEN & SCHREIBEN gebeten und möchten deren URI kennen.

Und das Folgende ist mein Code, zusätzlich zu Paul Burkes Lösung. Ich füge diesen Code hinzu und mein Projekt funktioniert einwandfrei.

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

Und in Ihrer Aktivität und Ihrem Fragment, in dem Sie nach der URI fragen:

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

In meinem Fall definiere ich in CompatUtils.java die Methode verifyStoragePermissions (als statischen Typ, damit ich sie in anderen Aktivitäten aufrufen kann).

Es sollte auch sinnvoller sein, wenn Sie zuerst einen if-Status festlegen, um festzustellen, ob die aktuelle SDK-Version über 23 liegt oder nicht, bevor Sie die verifyStoragePermissions-Methode aufrufen.

Anthonyeef
quelle
2

Das ist was ich mache:

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

HINWEIS: Die managedQuery()Methode ist veraltet, daher verwende ich sie nicht.

Diese Antwort ist von m3n0R auf Frage Android bekommen echten Pfad von Uri.getPath () und ich beanspruche keine Gutschrift. Ich dachte nur, dass Leute, die dieses Problem noch nicht gelöst haben, dies nutzen könnten.

Isaac Zais
quelle
2
Dies ist keine Antwort auf die neue Galerie-App (ausschließlich "Media Documents Provider" -App) auf KitKat.
Nagoya0
Die App, die der Fragesteller "Galerie" nennt, ist wahrscheinlich eine neue Dateiauswahl auf kitkat. Zu
Ihrer Information
Ich mache ähnliche und habe IndexOutOfBound auf Nexus 5X, Android 6 in dieser Zeile:cursor.getString(idx);
Osify
1

Bitte versuchen Sie, die Verwendung der takePersistableUriPermission-Methode zu vermeiden, da sie für mich eine Laufzeitausnahme auslöste. / ** * Aus Galerie auswählen. * /

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 für das Ergebnis zur Verarbeitung der Bilddaten:

@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
quelle
Wo zeigen Sie das Bild im zweiten Fall an?
Quantum_VC
Entschuldigung, ich konnte diese Codezeile nicht in else hinzufügen, wenn mImgCropping.startCropImage (photoFile, AppConstants.REQUEST_IMAGE_CROP); Ich muss wieder die Imagecropping-Funktion gemäß meinem Projektbedarf
aufrufen
Welcher Dateityp ist photoFile und mImgCropping?
Philip BH
1

Wenn jemand interessiert ist, habe ich eine funktionierende Kotlin-Version erstellt für 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
quelle
Ich möchte nur einen funktionierenden Code von Kotlin. Es ist Arbeit für mich. danke
Harvi Sirja
1

Ich habe hier einige der Antworten ausprobiert und denke, ich habe eine Lösung, die jedes Mal funktioniert und auch Berechtigungen verwaltet.

Es basiert auf der cleveren Lösung von LEO. Dieser Beitrag sollte den gesamten Code enthalten, den Sie benötigen, damit dies funktioniert, und er sollte auf jedem Telefon und jeder Android-Version funktionieren;)

Um eine Datei von einer SD-Karte auswählen zu können, benötigen Sie Folgendes in Ihrem Manifest:

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

Konstanten:

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

Überprüfen Sie die Berechtigung und starten Sie wenn möglichImagePick

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

Berechtigungsantwort

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

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

Berechtigungsantwort verwalten

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

Starten Sie die Bildauswahl

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
}

Verwalten der Bildauswahlantwort

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

Das war's Leute; Das funktioniert bei allen Telefonen, die ich habe.

Quentin G.
quelle
0

Dies ist ein totaler Hack, aber hier ist was ich getan habe ...

Während ich mit dem Einrichten eines DocumentsProviders spielte , bemerkte ich, dass der Beispielcode (in getDocIdForFile, um Zeile 450) eine eindeutige ID für ein ausgewähltes Dokument generiert, basierend auf dem (eindeutigen) Pfad der Datei relativ zu dem angegebenen Stammverzeichnis, das Sie ihm geben (d. H. was Sie mBaseDirin Zeile 96 eingestellt haben).

Die URI sieht also ungefähr so ​​aus:

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

Wie in den Dokumenten angegeben, wird nur ein einziger Stamm angenommen (in meinem Fall Environment.getExternalStorageDirectory()können Sie ihn aber woanders verwenden ... dann wird der Dateipfad beginnend am Stamm verwendet und zur eindeutigen ID, die " root:" vorangestellt wird . Also ich Sie können den Pfad bestimmen, indem Sie den "/document/root:Teil "aus uri.getPath () entfernen und einen tatsächlichen Dateipfad erstellen, indem Sie Folgendes tun:

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

Ich weiß. Es ist beschämend, aber es hat funktioniert. Dies hängt wiederum davon ab, dass Sie Ihre eigenen verwenden Dokumentenanbieter in Ihrer App verwenden, um die Dokument-ID zu generieren.

(Es gibt auch eine bessere Möglichkeit, den Pfad zu erstellen, bei der nicht angenommen wird, dass "/" das Pfadtrennzeichen usw. ist. Aber Sie haben die Idee.)

Fett
quelle
Um mir mit einem noch verrückteren Gedanken zu antworten: Wenn Ihre App bereits file://Absichten von einer externen Dateiauswahl verarbeitet, können Sie auch die Berechtigung wie im obigen Beispiel überprüfen, um sicherzustellen, dass sie von Ihrem benutzerdefinierten Anbieter stammt, und wenn ja, können Sie dies Verwenden Sie den Pfad auch, um eine neue file://Absicht mithilfe des von Ihnen extrahierten Pfads zu "fälschen" , StartActivity()und lassen Sie sie dann von Ihrer App abholen. Ich weiß, schrecklich.
Fett
0

Das hat bei mir gut funktioniert:

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
quelle
vergiss die dumpImageMetaData (uri); es ist unnötig
Rafa
0

Aufbauend auf Paul Burkes Antwort ich viele Probleme beim Auflösen des URI-Pfads einer externen SD-Karte, da die meisten der vorgeschlagenen "integrierten" Funktionen Pfade zurückgeben, die nicht in Dateien aufgelöst werden.

Dies ist jedoch mein Ansatz für sein // TODO-Handle für nicht primäre Volumes .

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

Beachten Sie, dass dies von der Hierarchie abhängt, die bei jedem Telefonhersteller unterschiedlich sein kann. Ich habe nicht alle getestet (es hat bisher bei Xperia Z3 API 23 und Samsung Galaxy A3 API 23 gut funktioniert).

Bitte bestätigen Sie, wenn es anderswo nicht gut funktioniert.

Evusas
quelle
-1

Die Antwort von @paul burke funktioniert sowohl für Kamera- als auch für Galeriebilder für API-Level 19 und höher. Sie funktioniert jedoch nicht, wenn das Mindest-SDK Ihres Android-Projekts auf unter 19 eingestellt ist, und einige der oben genannten Antworten funktionieren nicht für Galerie und Kamera. Nun, ich habe den Code von @paul burke geändert, der für API-Level unter 19 funktioniert. Unten ist der Code.

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
quelle
Ich erhalte java.lang.IllegalArgumentException: Keine der angeforderten Spalten kann angegeben werden, wenn ein Google Doc-Bild ausgewählt wird
dirkoneill
@irkoneill Ich bekomme die gleiche Ausnahme. Hast du eine Lösung gefunden?
Henry
-4

Die Antwort auf Ihre Frage lautet, dass Sie über Berechtigungen verfügen müssen. Geben Sie den folgenden Code in Ihre Datei manifest.xml ein:

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

Es hat bei mir funktioniert ...

Ashwin
quelle