Подтвердить что ты не робот

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

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

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

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

API < 19

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

  • Урис, исходящий от камер, в основном имеет схему file://. Их довольно легко лечить, потому что они держат путь. Вы можете позвонить 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 easy to get the path
        path = uri.getEncodedPath();
        return new ExifInterface(path);
    }
    return null;
}

API19 +

Мои проблемы возникают из Kitkat вперед с content:// URI. Киткат представляет Storage Access Framework (см. здесь) вместе с новым намерением ACTION_OPEN_DOCUMENT и сборщиком платформ. Однако сказано, что

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

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

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

Однако подход к контенту больше не работает. Иногда он работает на Kitkat, но никогда не работает на 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 материал из потока, но Im задается вопросом об общем/платформенном способе решения этой проблемы.

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Возможно, существуют внешние библиотеки, которые читают материал Exif из потока, но Im задается вопросом об общем/платформенном способе решения этой проблемы.

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

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

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

Ответ 2

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

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

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