Eine letzte Antwort zum Abrufen von Exif-Daten von URI

Lesezeit: 1 Minute

Dieses Thema wurde hier in vielen Fragen diskutiert, mit meist unterschiedlichen Ergebnissen und aufgrund von API-Änderungen und unterschiedlichen Arten von URIs, keine endgültige Antwort.

Ich habe selbst keine Antwort, aber lass uns darüber reden. Die ExifInterface hat einen einzigen Konstruktor, der a akzeptiert filePath. Das allein ist schon ärgerlich, da davon abgeraten wird, sich jetzt auf Pfade zu verlassen – die sollte man lieber nutzen Uris und ContentResolver. OK.

Unser Uri genannt uri kann aus der Absicht in abgerufen werden onActivityResult (wenn Sie das Bild aus der Galerie mit auswählen ACTION_GET_CONTENT) oder kann ein sein Uri die wir zuvor hatten (wenn Sie das Bild von der Kamera auswählen und anrufen intent.putExtra(MediaStore.EXTRA_OUTPUT, uri)).

API<19

Unser uri kann zwei unterschiedliche Schemata haben:

  • Uris, die von Kameras kommen, haben meistens eine file:// Schema. Diese sind ziemlich einfach zu behandeln, weil sie den Weg halten. Du kannst anrufen new ExifInterface(uri.getPath()) und du bist fertig.
  • Uris, die von Galerien oder anderen Inhaltsanbietern stammen, haben normalerweise eine content:// Schnittstelle. Ich persönlich weiß nicht, was das soll, aber es macht mich wahnsinnig.

Dieser zweite Fall sollte meines Wissens mit a behandelt werden ContentResolver mit denen man kommen kann Context.getContentResolver(). Folgende funktioniert bei allen von mir getesteten Apps auf jeden Fall:

public static ExifInterface getPictureData(Context context, Uri uri) {
    String[] uriParts = uri.toString().split(":");
    String path = null;

    if (uriParts[0].equals("content")) {
        // we can use ContentResolver.
        // let’s query the DATA column which holds the path
        String col = MediaStore.Images.ImageColumns.DATA;
        Cursor c = context.getContentResolver().query(uri,
                new String[]{col},
                null, null, null);

        if (c != null && c.moveToFirst()) {
            path = c.getString(c.getColumnIndex(col));
            c.close();
            return new ExifInterface(path);
        }

    } else if (uriParts[0].equals("file")) {
        // it's easy to get the path
        path = uri.getEncodedPath();
        return new ExifInterface(path);
    }
    return null;
}

API19+

Meine Probleme treten ab Kitkat auf content:// URIs. Kitkat stellt die vor Storage Access Framework (sehen hier) zusammen mit einer neuen Absicht, ACTION_OPEN_DOCUMENT, und eine Plattformauswahl. Allerdings sagt man das

Auf Android 4.4 und höher haben Sie die zusätzliche Option, die Absicht ACTION_OPEN_DOCUMENT zu verwenden, die eine vom System gesteuerte Auswahl-Benutzeroberfläche anzeigt, die es dem Benutzer ermöglicht, alle Dateien zu durchsuchen, die andere Apps zur Verfügung gestellt haben. Über diese einzelne Benutzeroberfläche kann der Benutzer eine Datei aus einer der unterstützten Apps auswählen.

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

Um es ganz einfach zu halten, sagen wir, dass wir mit dem Alten einverstanden sind ACTION_GET_CONTENT: Es wird ein Auswahldialog ausgelöst, in dem Sie eine Galerie-App auswählen können.

Allerdings funktioniert der Content-Ansatz nicht mehr. Manchmal funktioniert es auf Kitkat, aber funktioniert nie auf Lollipop zum Beispiel. Ich weiß nicht, was sich genau geändert hat.

Ich habe viel gesucht und ausprobiert; Ein anderer Ansatz speziell für Kitkat ist:

String wholeId = DocumentsContract.getDocumentId(uri);
String[] parts = wholeId.split(“:”);
String numberId = parts[1];

Cursor c = context.getContentResolver().query(
    // why external and not internal ?
    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
    new String[]{ col },
    MediaStore.Images.Media._ID + “=?”,
    new String[]{ numberId },
    null);

Das funktioniert manchmal, aber andere nicht. Insbesondere funktioniert es, wenn wholeId ist so etwas wie image:2839aber offensichtlich bricht wann wholeId ist einfach eine Zahl.

Sie können dies mit dem Systempicker versuchen (dh die Galerie mit ACTION_OPEN_DOCUMENT): Wenn Sie ein Bild aus „Neueste“ auswählen, funktioniert es; Wenn Sie ein Bild aus „Downloads“ auswählen, bricht es ab.

Also wie?!

Die unmittelbare Antwort ist Du nicht, finden Sie in neueren Versionen des Betriebssystems keine Dateipfade von Inhalts-URIS. Man könnte sagen, dass nicht alle Inhaltsverzeichnisse auf Bilder oder gar Dateien verweisen.

Das ist für mich völlig in Ordnung, und ich habe anfangs daran gearbeitet, dies zu vermeiden. Aber dann, Wie sollen wir die ExifInterface-Klasse verwenden, wenn wir keine Pfade verwenden sollten?

Ich verstehe nicht, wie moderne Apps dies tun – das Finden von Orientierung und Metadaten ist ein Problem, mit dem Sie sofort konfrontiert sind, und ContentResolver bietet keine API in diesem Sinne an. Du hast ContentResolver.openFileDescriptor() und ähnliches Zeug, aber keine APIs zum Lesen von Metadaten (die sich wirklich in dieser Datei befinden). Möglicherweise gibt es externe Bibliotheken, die lesen Exif Sachen aus einem Stream, aber ich wundere mich über die allgemeine/Plattform-Methode, um dies zu lösen.

Ich habe in den Open-Source-Apps von Google nach ähnlichem Code gesucht, aber nichts gefunden.

  • Können Sie erreichen, was Sie über my Metadaten-Extraktor Bücherei? Solange Sie einen Stream öffnen können, kann er die Datei verarbeiten.

    – Zeichnete Noakes

    9. Januar 2016 um 22:24 Uhr

  • Android ist ein Haufen technischer Schulden und Bugs

    – GaRRaPeta

    3. November 2016 um 14:11 Uhr

  • Die ExifInterface-Klasse hat einen Konstruktor, der einen Inputstream von SDK-Level 24 nimmt developer.android.com/reference/android/media/…

    – Torleif

    9. März 2017 um 10:00 Uhr

  • @Torleif schön zu wissen, wird 2020 nützlich sein. 🙂

    – natürlich

    9. März 2017 um 10:26 Uhr

  • @natario es ist noch nicht 2020, aber es gibt jetzt eine Support-Bibliothek. Siehe meine Antwort für Beispielcode.

    – Textanfang

    27. März 2017 um 21:08 Uhr

Benutzeravatar von startoftext
Textanfang

Um die Antwort von alex.dorokhov mit etwas Beispielcode zu erweitern. Die Support-Bibliothek ist eine großartige Möglichkeit.

build.gradle

dependencies {
...    
compile "com.android.support:exifinterface:25.0.1"
...
}

Beispielcode:

import android.support.media.ExifInterface;
...
try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) {
      ExifInterface exif = new ExifInterface(inputStream);
      int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
    } catch (IOException e) {
      e.printStackTrace();
    }

Der Grund, warum ich es so machen musste, als wir anfingen, auf API 25 abzuzielen (vielleicht auch ein Problem auf 24+), aber immer noch auf API 19 zurückgreifen, stürzte unsere App auf Android 7 ab, wenn ich einen URI an die Kamera weitergab nur auf eine Datei verweisen. Daher musste ich einen URI erstellen, um ihn so an die Kameraabsicht zu übergeben.

FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".fileprovider", tempFile);

Das Problem dabei ist, dass es für die Datei nicht möglich ist, den URI in einen echten Dateipfad umzuwandeln (außer dem Festhalten am Pfad der temporären Datei).

  • Du bist ein echter MVP. +1

    – Scharfe Kante

    27. März 2017 um 1:49 Uhr

  • Dies ist eine perfekte Antwort

    – code4j

    3. Januar 2018 um 6:15 Uhr

  • Perfekt! Ich war wirklich festgefahren, wie man es auf sdk <24 richtig verwaltet, da jeder Versuch, den echten Dateipfad aus uri zu extrahieren, im Allgemeinen falsch ist. Vielen Dank

    – Benutzer1209216

    18. Januar 2018 um 9:56 Uhr


  • Immer noch Nullwerte für Uris, die nicht aus dem kommen MediaStore. Getestet mit einem Bild aus der Google Fotos-Cloud, das nicht über die Support-Bibliothek auf dem Telefon gespeichert ist. Hat jemand eine Lösung gefunden?

    – Alexandre Nussbaumer

    5. Mai 2019 um 14:15 Uhr


  • Ich habe versehentlich hochgestimmt, aber die Orientierung hier gibt für mich immer 0 zurück. Das war also nicht sinnvoll. Ich habe meine eigene Antwort unten gepostet.

    – coolcool1994

    27. Juli 2020 um 19:28 Uhr

Das Abrufen von EXIF ​​aus einem Inhalts-URI (eigentlich einem InputStream) ist jetzt in der Support-Bibliothek verfügbar. Sehen: https://android-developers.googleblog.com/2016/12/introducing-the-exifinterface-support-library.html

Benutzeravatar von CommonsWare
CommonsWare

Folgendes funktioniert jedenfalls mit allen von mir getesteten Apps:

Das geht nur, wenn die Uri kommt zufällig etwas von der MediaStore. Es wird scheitern, wenn die Uri kommt zufällig von irgendwas anderem.

Die unmittelbare Antwort lautet: Sie finden keine Dateipfade von Inhalts-URIS in neueren Versionen des Betriebssystems. Man könnte sagen, dass nicht alle Inhaltsverzeichnisse auf Bilder oder gar Dateien verweisen.

Richtig. Darauf habe ich bei vielen Gelegenheiten hingewiesen, so auch hier.

Wie sollen wir die ExifInterface-Klasse verwenden, wenn wir keine Pfade verwenden sollten?

Du nicht. Verwenden Sie anderen Code, um die EXIF-Header abzurufen.

Möglicherweise gibt es externe Bibliotheken, die Exif-Sachen aus einem Stream lesen, aber ich frage mich, wie dies auf der gemeinsamen/Plattform gelöst werden kann.

Verwenden Sie externe Bibliotheken.

Ich habe in den Open-Source-Apps von Google nach ähnlichem Code gesucht, aber nichts gefunden.

Du wirst einiges darin finden die MMS-App.

UPDATE: 10.01.2020: Verwenden ExifInterface aus den AndroidX-Bibliotheken. Es unterstützt die Verwendung InputStream um die EXIF-Daten einzulesen, und Sie können eine bekommen InputStream für Inhalte gekennzeichnet durch a Uri mittels a ContentResolver.

  • Überzeugende und schnelle Antwort, danke. Ich habe gesehen, dass Sie auf ähnliche Dinge geantwortet haben, und werde tun, was Sie vorschlagen. Wie auch immer, ich muss sagen, dass, wenn wir nach SO Fragen und Antworten urteilen, die meisten Entwickler/Apps da draußen immer noch versuchen, Wege zu finden! Die allermeisten Antworten hier deuten noch (nicht bestanden, daher meine Frage von heute) darauf hin Anfrage für Pfade, anstatt für Bäche geöffnet. Ich glaube, dass auf Googles Seite etwas fehlt, entweder in den Dokumenten oder bei der Ablehnung von ExifInterface. Sag nur..

    – natürlich

    9. Januar 2016 um 18:12 Uhr

  • Funktioniert ContentResolver.open(…) für jede URI/API-Ebene da draußen, oder gibt es etwas anderes, das ich in Betracht ziehen sollte? Ich spreche von URIs, die Sie mit a anfordern image/* Art.

    – natürlich

    9. Januar 2016 um 18:15 Uhr

  • @mvai: “Die meisten Entwickler/Apps da draußen versuchen immer noch, Pfade zu finden!” — einverstanden. Ich habe kommentiert viele Stack Overflow-Fragen dazu. “Ich glaube, dass etwas auf der Seite von Google fehlt, entweder in Dokumenten oder bei der Ablehnung von ExifInterface” – Dokumente könnten sicherlich mehr Liebe gebrauchen. ExifInterface war eine dieser Klassen, die sie ziemlich früh (2009) eingeworfen haben und mit denen sie nicht viel gemacht haben, außer die zu aktualisieren TAG_ Konstanten. Im Großen und Ganzen hat sich Google von der Bereitstellung von Inhalten, die nicht besonders Android-zentriert sind, abgewendet, um sie Dritten überlassen zu können.

    – CommonsWare

    9. Januar 2016 um 18:35 Uhr


  • @rm8x: Es ist nicht erforderlich, dass jedes Bild ein bestimmtes EXIF-Tag hat.

    – CommonsWare

    1. März 2021 um 20:05 Uhr

  • @rm8x: Das könnte ein Fehler im Jetpack sein ExifInterface Umsetzung dann. Wenn Sie ein Projekt mit einem Image erstellen können, das mit fehlschlägt ExifInterface aber mit der anderen Bibliothek erfolgreich ist, reichen Sie einen Fehlerbericht ein!

    – CommonsWare

    2. März 2021 um 20:34 Uhr

Verwenden Sie kein EXIF. Sie können die Orientierung des Bildes von Uri wie folgt erhalten:

private static int getOrientation(Context context, Uri photoUri) {
    Cursor cursor = context.getContentResolver().query(photoUri,
            new String[]{MediaStore.Images.ImageColumns.ORIENTATION}, null, null, null);

    if (cursor.getCount() != 1) {
        cursor.close();
        return -1;
    }

    cursor.moveToFirst();
    int orientation = cursor.getInt(0);
    cursor.close();
    cursor = null;
    //orientation here can be 90, 180, 270!
}

Benutzeravatar von UdayaLakmal
UdayaLakmal

Android 10-API 30

Exif-Daten aus Bild-URI abrufen

 public static Bitmap decodeBitmap( Context context, Uri imagePath) {
    Logger.d("decodeBitmap imagePath: " + imagePath.getPath());

    if (imagePath == null) {
        return null;
    }

    InputStream in;
    ExifInterface exif;
    Bitmap image = null;
    try {
        in = context.getContentResolver().openInputStream(imagePath);
        image = BitmapFactory.decodeStream(in);

        //Close input stream consumed for Bitmap decode
        in.close();

        // Open stream again for reading exif information for acquiring orientation details.
        // Use new input stream otherwise bitmap decode stream gets reset.
        in =  context.getContentResolver().openInputStream(imagePath);

        int orientation = ExifInterface.ORIENTATION_UNDEFINED;
        try {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
                exif = new ExifInterface(in);
            }else{
                exif = new ExifInterface(imagePath.getPath());
            }
            orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);

        } catch (IOException e) {
            Logger.d("IOException: " + e.getMessage());
        }

        //if you need, can correct orientation issues for gallery pick camera images with following.
        Logger.d("decodeBitmap orientation: " + orientation);
        switch (orientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
            case ExifInterface.ORIENTATION_TRANSPOSE:
                image = rotateImage(image, ROTATE_90);
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
            case ExifInterface.ORIENTATION_FLIP_VERTICAL:
                image = rotateImage(image, ROTATE_180);
                break;
            case ExifInterface.ORIENTATION_ROTATE_270:
            case ExifInterface.ORIENTATION_TRANSVERSE:
                image = rotateImage(image, ROTATE_270);
                break;
            default:
                break;
        }
        in.close();
    }  catch (IOException e) {
        Logger.d("IOException", e.getMessage());
    }
     return image;
}

In meinem Fall habe ich Probleme, mich mit InputStream zu orientieren. Anstatt also ExifInterface von InputStream zu bekommen, habe ich FileDescriptor verwendet.

Diese Lösung hat nicht funktioniert:

val inputStream = contentResolver.openInputStream(uri)
val bitmap: Bitmap? = BitmapFactory.decodeStream(inputStream)
val exifInterface = inputStream?.let { ExifInterface(inputStream) }
inputStream?.close()

Ich hatte bessere Ergebnisse, als ich einen separaten InputStream für ExifInterface öffnete (aber ich mochte es nicht so):

val inputStream = contentResolver.openInputStream(uri)
val bitmap: Bitmap? = BitmapFactory.decodeStream(inputStream)
inputStream?.close()

val inputStream2 = contentResolver.openInputStream(uri)
val fileDescriptor = contentResolver.openFileDescriptor(uri, "r")?.fileDescriptor
val exifInterface = inputStream2?.let { ExifInterface(inputStream2) }
inputStream2?.close()

Aber ich landete bei dieser Methode mit FileDescriptor für die Konstruktion von ExifInterface:

fun Context.getImageFromGallery(uri: Uri): Bitmap? {
    return try {
        val inputStream = contentResolver.openInputStream(uri)
        val bitmap: Bitmap? = BitmapFactory.decodeStream(inputStream)
        inputStream?.close()

        val fileDescriptor = contentResolver.openFileDescriptor(uri, "r")?.fileDescriptor
        val exifInterface = fileDescriptor?.let { ExifInterface(fileDescriptor) }

        return when (exifInterface?.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) {
            ExifInterface.ORIENTATION_ROTATE_90 -> TransformationUtils.rotateImage(bitmap!!, 90)
            ExifInterface.ORIENTATION_ROTATE_180 -> TransformationUtils.rotateImage(bitmap!!, 180)
            ExifInterface.ORIENTATION_ROTATE_270 -> TransformationUtils.rotateImage(bitmap!!, 270)
            ExifInterface.ORIENTATION_NORMAL -> bitmap
            else -> bitmap
        }
    } catch (e: java.lang.Exception) {
        e.printStackTrace()
        null
    }
}

1438800cookie-checkEine letzte Antwort zum Abrufen von Exif-Daten von URI

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy