android 关于如何从URI获取Exif数据的最终答案

qij5mzcb  于 2023-02-14  发布在  Android
关注(0)|答案(7)|浏览(140)

这个主题已经在这里的许多问题中讨论过了,大多数都有不同的结果,而且由于API的变化和URI的不同类型,没有明确的答案
我自己也没有答案,但让我们来讨论一下。ExifInterface有一个接受filePath的构造函数。这本身就很烦人,因为现在不鼓励依赖路径-你应该使用Uri s和ContentResolver。好的。
名为uriUri可以从onActivityResult中的Intent中检索(如果您使用ACTION_GET_CONTENT从图库中选取图片),也可以是我们以前拥有的Uri(如果您从相机中选取图片并调用intent.putExtra(MediaStore.EXTRA_OUTPUT, uri))。
空气污染API〈19
我们的uri可以有两种不同的模式:

  • 来自摄像头的URI大多数都有一个file://模式。这些模式很容易处理,因为它们包含路径。您可以调用new ExifInterface(uri.getPath()),这样就完成了。
  • 来自画廊或其他内容提供商的URI通常有一个content://接口,我个人不知道这是怎么回事,但这让我抓狂。

第二种情况,据我所知,应该用ContentResolver来处理,你可以用Context.getContentResolver()来处理。下面的适用于我测试过的所有应用程序,在任何情况下:

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+

我的问题是从Kitkat开始的content:// URI。Kitkat引入了Storage Access Framework(参见here)沿着一个新的intent ACTION_OPEN_DOCUMENT和一个平台选择器。
在Android 4.4及更高版本上,您可以使用ACTION_OPEN_DOCUMENT Intent,该Intent会显示一个由系统控制的选取器UI,允许用户浏览其他应用提供的所有文件。用户可以从这一个UI中选取任何受支持的应用中的文件。
ACTION_OPEN_DOCUMENT不能替代ACTION_GET_CONTENT。您应根据应用的需要使用ACTION_GET_CONTENT。
为了简单起见,我们假设可以使用旧的ACTION_GET_CONTENT:它将启动一个选择器对话框,您可以在其中选择一个图库应用程序。
然而,内容方法不再起作用了。有时候它在奇巧上起作用,但在棒棒糖上**从来就不起作用。我不知道到底发生了什么变化。
我已经寻找和尝试了很多;针对Kitkat专门采取的另一种方法是:

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

这种方法有时有效,但有时无效。具体来说,当wholeId类似于image:2839时,它有效,但当wholeId只是一个数字时,它显然无效。
您可以尝试使用系统选择器(例如,使用ACTION_OPEN_DOCUMENT启动图库):如果你从“最近”中选择一张图片,它就起作用了;如果你从“下载”中选择一个图像,它就会坏掉。

那么该怎么做呢?!

直接的答案是你不,你在新版本的操作系统中找不到内容URI的文件路径。可以说,不是所有的内容URI都指向图片甚至文件。
这对我来说完全没问题,一开始我努力避免这种情况,但是,如果我们不应该使用路径,我们应该如何使用ExifInterface类?
我不明白现代应用程序是如何做到这一点的--找到方向和元数据是你马上要面对的问题,而ContentResolver并没有提供任何这方面的API。你有ContentResolver.openFileDescriptor()和类似的东西,但没有读取元数据的API(它确实在那个文件中)。可能有外部库从流中读取Exif的东西,但我想知道解决这个问题的通用/平台方法。
我曾在谷歌的开源应用程序中搜索过类似的代码,但一无所获。

5f0d552i

5f0d552i1#

用一些示例代码来扩展alex.dorokhov的答案,支持库是一个很好的选择。
build.gradle

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

示例代码:

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

我之所以要这样做,是因为我们开始以api 25为目标(可能在24+上也有问题),但仍然支持回到api 19,在android 7上,如果我传入一个URI到相机,而这个URI只是引用一个文件,我们的应用会崩溃。因此,我必须创建一个URI,像这样传递到相机Intent。

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

这里的问题是,文件不可能将URI转换为实际的文件路径(除了保留临时文件路径)。

aiqt4smr

aiqt4smr3#

以下代码适用于我测试过的所有应用程序,在任何情况下:
只有当Uri恰好是来自MediaStore的东西时,它才会工作,如果Uri恰好来自其他东西,它就会失败。
直接的答案是,在较新版本的操作系统中,你找不到内容URI的文件路径。可以说,并非所有内容URI都指向图片甚至文件。
对,我在很多场合都指出过,比如在这里。
如果我们不应该使用路径,我们应该如何使用ExifInterface类?
您不需要。请使用其他代码获取EXIF头。
可能有外部库从流中读取Exif内容,但我想知道解决这个问题的公共/平台方法。
使用外部库。
我曾在谷歌的开源应用程序中搜索过类似的代码,但一无所获。
您将在the Mms app中找到一些。

    • 更新日期:2020年1月10日**:使用AndroidX库中的ExifInterface,支持使用InputStream读入EXIF数据,对于Uri标识的内容,可以通过ContentResolver获取InputStream
pgvzfuti

pgvzfuti4#

不要使用EXIF。你可以像这样从Uri获得图像的方向:

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!
}
ecfdbz9o

ecfdbz9o5#

安卓10 API 30
从图像URI获取Exif数据

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

d5vmydt96#

Dependency for AndroidX

import androidx.exifinterface.media.ExifInterface

要创建一个新示例,只需使用构造函数:

ExifInterface(...)

如果找不到依赖项,请将latest version添加到gradle文件(module: app

dependencies {
    implementation "androidx.exifinterface:exifinterface:X.X.X"
}
yuvru6vn

yuvru6vn7#

在我的例子中,我在使用InputStream获取方向时遇到了问题。所以我使用了FileDescriptor,而不是从InputStream获取ExifInterface。
此解决方案不起作用:

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

当我为ExifInterface打开单独的InputStream时,我得到了更好的结果(但我不喜欢这样):

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

但我最终使用FileDescriptor来构造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
    }
}

相关问题