Окончательный ответ о том, как получить данные Exif из URI

Этот вопрос обсуждался здесь по множеству вопросов, в основном с разными результатами, и из-за изменений API и разных типов URI нет окончательного ответа .

У меня нет ответа, но поговорим об этом. ExifInterface имеет единственный конструктор, который принимает filePath . Это само по себе раздражает, так как теперь не рекомендуется полагаться на пути – вам лучше использовать Uri s и ContentResolver . ОК.

Наш Uri названный uri может быть извлечен из намерения onActivityResult (если вы выбираете изображение из галереи с ACTION_GET_CONTENT ) или может быть Uri который мы ранее имели (если вы выбираете изображение с камеры и вызываете intent.putExtra(MediaStore.EXTRA_OUTPUT, uri) ).

API <19

Наш uri может иметь две разные схемы:

  • Урис, исходящий от камер, будет в основном иметь file:// schema. Их довольно легко лечить, потому что они держат путь. Вы можете вызвать new ExifInterface(uri.getPath()) и все готово.
  • Урис, исходящий из галереи или других контент-провайдеров, обычно имеет интерфейс 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 к Storage Access Framework (см. Здесь ) вместе с новым намерением, ACTION_OPEN_DOCUMENT и сборщиком платформ. Однако говорится, что

На Android 4.4 и выше у вас есть дополнительная возможность использовать намерение ACTION_OPEN_DOCUMENT, которое отображает пользовательский интерфейс выбора, который контролирует система, что позволяет пользователю просматривать все файлы, которые были доступны другим приложениям. Из этого единого пользовательского интерфейса пользователь может выбрать файл из любого поддерживаемого приложения.

ACTION_OPEN_DOCUMENT не предназначен для замены ACTION_GET_CONTENT. Тот, который вы должны использовать, зависит от потребностей вашего приложения.

Поэтому, чтобы сохранить это очень просто, предположим, что мы в порядке со старым ACTION_GET_CONTENT : он запустит диалог выбора, где вы можете выбрать приложение галереи.

Тем не менее, контентный подход больше не работает. Иногда он работает на Киткате, но никогда не работает на Lollipop, например. Я не знаю, что именно изменилось.

Я много искал и много пробовал; Другой подход, применяемый к 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 ): если вы выберете изображение из «Рецентов», оно будет работать; Если вы выберете изображение из «Загрузки», он сломается.

Так как ?!

Непосредственный ответ: « Нет» , вы не находите пути к файлу из содержимого uris в новой версии ОС. Можно сказать, что не все содержимое uris указывает на изображения или даже файлы.

Для меня это полностью нормально, и сначала я старался избежать этого. Но тогда, как мы должны использовать класс ExifInterface, если мы не должны использовать пути?

Я не понимаю, как это делают современные приложения – поиск ориентации и метаданных – это проблема, с которой вы сразу сталкиваетесь, а ContentResolver не предлагает API в этом смысле. У вас есть ContentResolver.openFileDescriptor() и подобные материалы, но нет API-интерфейсов для чтения метаданных (которые действительно находятся в этом файле). Могут существовать внешние библиотеки, которые читают Exif материал из потока, но мне интересно об общем / платформенном способе решить эту проблему.

Я искал аналогичный код в приложениях с открытым исходным кодом Google, но ничего не нашел.

Solutions Collecting From Web of "Окончательный ответ о том, как получить данные Exif из URI"

Следующее работает со всеми приложениями, которые я тестировал, в любом случае:

Это будет работать только в том случае, если Uri окажется чем-то вроде MediaStore . Это не удастся, если Uri случится из чего-то другого.

Непосредственный ответ: «Нет», вы не находите пути к файлу из содержимого uris в новой версии ОС. Можно сказать, что не все содержимое uris указывает на изображения или даже файлы.

Верный. Я неоднократно указывал на это, например, здесь .

Как мы должны использовать класс ExifInterface, если мы не должны использовать пути?

Вы этого не делаете. Используйте другой код, чтобы получить заголовки EXIF.

Могут существовать внешние библиотеки, которые читают Exif-материал из потока, но мне интересно об общем / платформенном способе решить эту проблему.

Используйте внешние библиотеки.

Я искал аналогичный код в приложениях с открытым исходным кодом Google, но ничего не нашел.

Вы найдете их в приложении Mms .

Чтобы расширить ответ 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-телефоне наше приложение будет сбой, если я передам URI на камеру, которая была Просто ссылаясь на файл. Поэтому мне пришлось создать URI, чтобы перейти к камере, как это.

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

Проблема там в том, что файл не позволяет превратить URI в реальный путь к файлу (кроме сохранения на пути временного файла).

Получение EXIF ​​из URI контента (на самом деле InputStream) теперь доступно в библиотеке поддержки. См. https://android-developers.googleblog.com/2016/12/introducing-the-exifinterface-support-library.html.