web-dev-qa-db-ja.com

URIからExifデータを取得する方法に関する最終回答

このトピックは、ここで多くの質問で議論されてきましたが、ほとんどが結果が異なり、APIの変更とさまざまな種類のURIにより、決定的な答えはありません

自分には答えがありませんが、それについて話しましょう。 ExifInterfaceには、filePathを受け入れる単一のコンストラクターがあります。パス自体に依存することは現在推奨されていないので、それ自体は面倒です-代わりにUrisとContentResolverを使用する必要があります。 OK。

Uriという名前のuriは、onActivityResultのインテントから取得できます(_ACTION_GET_CONTENT_を使用してギャラリーから画像を選択した場合)またはUri以前(カメラから画像を選択してintent.putExtra(MediaStore.EXTRA_OUTPUT, uri)を呼び出した場合)。

API <19

uriは2つの異なるスキーマを持つことができます:

  • カメラから来るUrisのほとんどは_file://_スキーマを持ちます。それらはパスを保持しているため、扱いやすいです。 new ExifInterface(uri.getPath())を呼び出すと完了です。
  • ギャラリーまたはその他のコンテンツプロバイダーからのUrisには、通常_content://_インターフェイスがあります。私は個人的にそれが何であるかを知りませんが、私を怒らせています。

私が理解している限り、この2番目のケースはContext.getContentResolver()で取得できるContentResolverで処理する必要があります。次のworksは、私がテストしたすべてのアプリで、いずれの場合でも:

_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 +

_content://_ URIを使用したKitKat以降の問題が発生します。 KitKatでは、_Storage Access Framework_( here を参照)と新しいインテント、_ACTION_OPEN_DOCUMENT_、およびプラットフォームピッカーが導入されています。しかし、それは言われています

Android 4.4以降では、システムが制御するピッカーUIを表示するACTION_OPEN_DOCUMENTインテントを使用する追加オプションがあり、ユーザーは他のアプリが利用可能にしたすべてのファイルを閲覧できます。この単一のUIから、ユーザーはサポートされているアプリのいずれかからファイルを選択できます。

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_を使用してギャラリーを起動します)。「最近」から画像を選択すると、動作します。 「ダウンロード」から画像を選択すると、破損します。

どうやって?

即時の答えはありません、OSの新しいバージョンではコンテンツURIからのファイルパスは見つかりません。すべてのコンテンツURIが写真やファイルを指すわけではないと言うことができます。

それは私にはまったく問題ありません。最初はこれを避けるために働きました。しかし、その後、パスを使用すべきではない場合、ExifInterfaceクラスをどのように使用することになっていますか?

最新のアプリがこれをどのように行うか理解できません。向きとメタデータを見つけることはすぐに直面する問題であり、ContentResolverはその意味でAPIを提供しません。 ContentResolver.openFileDescriptor()などがありますが、メタデータを読み取るAPIはありません(これはそのファイルにあります)。ストリームからExifを読み込む外部ライブラリがあるかもしれませんが、これを解決するための共通/プラットフォームの方法について疑問に思っています。

Googleのオープンソースアプリで同様のコードを検索しましたが、何も見つかりませんでした。

34
natario

以下は、私がテストしたすべてのアプリで動作します。

UriがたまたまMediaStoreからのものである場合にのみ機能します。 Uriが他の何かから来た場合、失敗します。

即時の答えは、「しません。新しいバージョンのOSではコンテンツURIからのファイルパスは見つかりません」です。すべてのコンテンツURIが写真やファイルを指すわけではないと言うことができます。

正しい。 here など、多くの場合にこれを指摘しました。

パスを使用しない場合、ExifInterfaceクラスをどのように使用するのですか?

あなたはしません。他のコードを使用してEXIFヘッダーを取得します。

Exifをストリームから読み込む外部ライブラリがあるかもしれませんが、これを解決するための一般的な/プラットフォームの方法について疑問に思っています。

外部ライブラリを使用します。

Googleのオープンソースアプリで同様のコードを検索しましたが、何も見つかりませんでした。

Mmsアプリ にいくつかあります。

9
CommonsWare

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を作成する必要がありました。

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

そこに問題があるのは、そのファイルがURIを実際のファイルパスに変換できないことです(一時ファイルパスを保持する以外)。

42
startoftext

コンテンツURI(実際にはInputStream)からEXIFを取得することが、サポートライブラリで利用可能になりました。参照: https://Android-developers.googleblog.com/2016/12/introducing-the-exifinterface-support-library.html

10
alex.dorokhov