アンドロイド - MediaStoreでメディアファイルの情報を読む

By JS | Last updated: October 19, 2019

まず、Media ProviderとMediaStoreという用語について知っている。

Media providerは端末に保存された画像、ビデオ、オーディオファイルの情報を提供するプロバイダです。 このプロバイダに、私たちが探したい種類のデータを照会することができます。戻される情報に、私たちは、ファイル名、保存した時、保存された位置などを知ることができます。

MediaStoreはアプリがMedia providerが提供するファイルをアクセスすることができるように助けてくれるAPIの束です。 クエリに必要なデータが定義されています。

したがって、我々はMediaStoreのAPIがどのような意味なのか理解し、そのAPIを介してMedia providerにクエリをして、データを取得するされます。

この記事では、メディアプロバイダにImage、Video、Audioファイルに対してクエリを実行する方法を調べて、データにアクセスする方法を説明します。

Android 10(Q)でScoped Storageが適用されました。メディアファイルは、MediaStoreを介してread/writeすることを推奨しています。

この記事のサンプルコードは、すべてkotlinで作成されました。

メディアファイルを保存する方法は、アンドロイド - MediaStore(Media Provider)へのファイル保存する方法を参照してください。

権限

MediaStoreでファイルを読むには、次のようにREAD権限が必要です。 AndroidManifestに権限を追加して、アプリが実行されるとき、ユーザーからの権限が必要です。

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

画像クエリ

MediaStoreでデバイスのイメージを照会するコードは次のとおりです。

val projection = arrayOf(
    MediaStore.Images.Media._ID,
    MediaStore.Images.Media.DISPLAY_NAME,
    MediaStore.Images.Media.DATE_TAKEN
)
val selection = "${MediaStore.Images.Media.DATE_TAKEN} >= ?"
val selectionArgs = arrayOf(
    dateToTimestamp(day = 1, month = 1, year = 1970).toString()
)
val sortOrder = "${MediaStore.Images.Media.DATE_TAKEN} DESC"

val cursor = contentResolver.query(
    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
)

private fun dateToTimestamp(day: Int, month: Int, year: Int): Long =
    SimpleDateFormat("dd.MM.yyyy").let { formatter ->
        formatter.parse("$day.$month.$year")?.time ?: 0
    }

実質的に照会するコードは、 ContentResolver.query()です。 APIの引数に、私たちが探しているデータの情報を入れるとします。 まるでデータベースに照会するときに "select ~~"で興味のあるデータを設定することと同じです。 戻り値の型は Cursorであり、このオブジェクトを使用して発見したデータを確認することができます。

上記のコードを見れば、 query()に次のように5つの因子が入ります。 (次のコードは、 ContentResolver.javaのqueryメソッドです)

public final @Nullable Cursor query(@NonNull Uri uri,
        @Nullable String[] projection, @Nullable String selection,
        @Nullable String[] selectionArgs, @Nullable String sortOrder)

それぞれの引数は、次のような意味です。

  • Uri: 探しているデータのUri。
  • Projection: DBのcolumnのとおりです。結果として受けたいデータの種類を示します。
  • Selection: DBのwhereキーワードと同じです。どのような条件でフィルタリングされた結果を受信したときに使用します。
  • Selection args: Selectionと共に使用されます。
  • Sort order: クエリ結果のデータをsortingしたときに使用します。

上記の条件でクエリしたデータがある場合はCursorに結果を巡回(Loop)することができます。 次のコードは、Cursorに、すべてのデータを巡回して、結果を出力する例です。

cursor?.use {
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
    val dateTakenColumn =
        cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN)
    val displayNameColumn =
        cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
    while (cursor.moveToNext()) {
        val id = cursor.getLong(idColumn)
        val dateTaken = Date(cursor.getLong(dateTakenColumn))
        val displayName = cursor.getString(displayNameColumn)
        val contentUri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            id.toString()
        )
        Log.d(
            TAG, "id: $id, display_name: $displayName, date_taken: " +
                    "$dateTaken, content_uri: $contentUri"
        )
    }
}

Cursorはクエリを実行すると、Projectionで要求されたcolumnが含まれています。それぞれのcolumnのデータを取得してログに出力しました。

CusorでColumnを読み取るときは、次のようにgetLong()またはgetString()にcolumnのindexを挿入します。 Columnの戻り値の型に合わせてAPIを使用してください。

cursor.getLong(dateTakenColumn)
cursor.getString(displayNameColumn)

Columnのindexは次のようなコードで実現できます。

val dateTakenColumn =
    cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN)

上記のコードを実行してみると次のようにログが出力されます。

MainActivity: id: 41, display_name: new-york-4471754_1920.jpg, date_taken: Thu Jan 01 09:00:00 GMT+09:00 1970, content_uri: content://media/external/images/media/41
MainActivity: id: 42, display_name: beach-4532547_1920.jpg, date_taken: Thu Jan 01 09:00:00 GMT+09:00 1970, content_uri: content://media/external/images/media/42
.....

ContentResolver.query()の引数として渡される項目について、もう少し詳しく説明します。

Uri

Uri(Uniform Resource Identifier)は、データの位置を表現します。

query() APIは引数として渡されるUri内のデータを検索します。

MediaStore.Images.Media.EXTERNAL_CONTENT_URIというUriを引数として渡さた場合はUriパスの下でのみファイルを探します。このUriをStringに出力してみると、次のとおりです。

  • content://media/external/images/media

このパスとイメージクエリの結果から出力されたファイルのUriパスを比較して見れば、共通パスがあることを知ることができます。

Projection

Projectionは String[]を引数として受け取ります。結果として受けたいcolumnの名前を配列に入力します。

次のようなcolumnがあります。

  • MediaStore.Images.Media._ID
  • MediaStore.Images.Media.DATA
  • MediaStore.Images.Media.DISPLAY_NAME
  • MediaStore.Images.Media.TITLE
  • MediaStore.Images.Media.DATE_TAKEN
  • MediaStore.Images.Media.DATE_ADDED

MediaStore.MediaColumnsから、より多くのcolumnを見ることができます。 (このページはJavaDocであり、MediaStoreのクラスが継承関係になっていて、すべての種類のColumnが整理されていません。)

Selection, Selection args

SelectionはDBのwhereと同じです。次のコードは、日付に基づいてどのような条件を満たしていれば、結果に含まれています。

val selection = "${MediaStore.Images.Media.DATE_TAKEN} >= ?"

selectionの疑問符(?)に入る内容がSelection argsです。 argsの型は String[]であるが、1つの値だけ入れて渡さください。(二つ以上使用しているケースを使って見たことがないですね。この部分は、よく分かりません)

次のコードを見れば、探している時間をtime stampに変更して伝達しました。

val selectionArgs = arrayOf(
    dateToTimestamp(day = 1, month = 1, year = 1970).toString()
)

private fun dateToTimestamp(day: Int, month: Int, year: Int): Long =
    SimpleDateFormat("dd.MM.yyyy").let { formatter ->
        formatter.parse("$day.$month.$year")?.time ?: 0
    }

もし、すべてのデータを照会したい場合はselectionとargsにnullを入力します。

val cursor = contentResolver.query(
    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
    projection,
    null,  // selection
    null,  // selectionArgs
    sortOrder
)

argsで時間は1970年1月1日からmsに表現したGMTを受けていますよ。 PCからデバイスにファイルを入れるとTIMEが1970年に設定されます。上記のコードのようにselectionを設定すると、クエリがダメですね。 2000年代以上のファイルは、クエリがしました。このような問題がある場合selectionをnullに設定します。コードが間違って見える(?)解決方法を見つけた場合、後で更新させていただきます。

Sort order

結果として受信データをソートするには、次のようにsort orderを入力します。

以下は、ファイルの時間に対して降順/昇順にソートするコードです。

val sortOrderDesc = "${MediaStore.Images.Media.DATE_TAKEN} DESC"
val sortOrderAsc = "${MediaStore.Images.Media.DATE_TAKEN} ASC"

動画のクエリ

動画を照会する方法も画像と同じです。探しているUri位置などを変更してくれればされます。 上記の MediaStore.Images.XXXにされたコードをMediaStore.Video.XXXに変更ください。

Videoに変更したコードは次のとおりです。

val projection = arrayOf(
    MediaStore.Video.Media._ID,
    MediaStore.Video.Media.DISPLAY_NAME,
    MediaStore.Video.Media.DATE_TAKEN
)
val selection = "${MediaStore.Video.Media.DATE_TAKEN} >= ?"
val selectionArgs = arrayOf(
    dateToTimestamp(day = 1, month = 1, year = 1970).toString()
)

val sortOrder = "${MediaStore.Video.Media.DATE_TAKEN} DESC"
val cursor = contentResolver.query(
    MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
    projection,
    null, // selection
    null, //selectionArgs
    sortOrder
)
cursor?.use {
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
    val dateTakenColumn =
        cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_TAKEN)
    val displayNameColumn =
        cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
    while (cursor.moveToNext()) {
        val id = cursor.getLong(idColumn)
        val dateTaken = Date(cursor.getLong(dateTakenColumn))
        val displayName = cursor.getString(displayNameColumn)
        val contentUri = Uri.withAppendedPath(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            id.toString()
        )
        Log.d(
            TAG, "id: $id, display_name: $displayName, date_taken: " +
                    "$dateTaken, content_uri: $contentUri"
        )
    }
}

オーディオのクエリ

オーディオも、他のメディアと同じように、クエリします。 MediaStore.Images.XXXにされたコードをMediaStore.Audio.XXXに変更ください。

次のコードは、Audioに変更したコードであり、さらにalbum、duration、titleの情報も取得するようprojectionに対応する項目を追加しました。

val projection = arrayOf(
    MediaStore.Audio.Media._ID,
    MediaStore.Audio.Media.DISPLAY_NAME,
    MediaStore.Audio.Media.ALBUM,
    MediaStore.Audio.Media.TITLE,
    MediaStore.Audio.Media.DURATION,
    MediaStore.Audio.Media.DATE_TAKEN
)
val selection = "${MediaStore.Audio.Media.DATE_TAKEN} >= ?"
val selectionArgs = arrayOf(
    dateToTimestamp(day = 1, month = 1, year = 1970).toString()
)

val sortOrder = "${MediaStore.Audio.Media.DATE_TAKEN} DESC"
val cursor = contentResolver.query(
    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
    projection,
    null,  // selection,
    null, // selectionArgs,
    sortOrder
)
cursor?.use {
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
    val dateTakenColumn =
        cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATE_TAKEN)
    val displayNameColumn =
        cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)
    val albumColumn =
        cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM)
    val titleColumn =
        cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE)
    val durationColumn =
        cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)

    while (cursor.moveToNext()) {
        val id = cursor.getLong(idColumn)
        val dateTaken = Date(cursor.getLong(dateTakenColumn))
        val displayName = cursor.getString(displayNameColumn)
        val album = cursor.getString(albumColumn)
        val title = cursor.getString(titleColumn)
        val duration = cursor.getString(durationColumn)
        val contentUri = Uri.withAppendedPath(
            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
            id.toString()
        )
        Log.d(TAG, "id: $id, display_name: $displayName, title:$title, " +
                "album: $album, duration:$duration, date_taken: " +
                "$dateTaken, content_uri: $contentUri"
        )
    }
}

上記のコードを実行すると、次のようにログが出力されます。

MainActivity: id: 13492, display_name: 048 ベン - 熱愛中.mp3, title:熱愛中, album: RECIPE, duration:270720, date_taken: Thu Jan 01 09:00:00 GMT+09:00 1970, content_uri: content://media/external/audio/media/13492

データアクセス

Cursorを介して得られた IDUri情報を得ることができます。

クエリを要求されたUriパスとファイルのIDが次のように与えられてしまった場合、

  • MediaStore.Audio.Media.EXTERNAL_CONTENT_URI : content://media/external/audio/media
  • File ID : 13492

このファイルのUriは次のように二つの文字列を合わせた値になります。

  • content://media/external/audio/media/13492

Stringではなく、Uriオブジェクトに取得するには、次のように Uri.withAppendedPath()を利用するとされます。

val contentUri = Uri.withAppendedPath(
    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
    id.toString()
)

// content://media/external/audio/media/13492

このように得られたUriにファイルにアクセスすることができます。

サンプルコード

イメージを照会してRecyclerViewに示すサンプルを作成しました。 完全なコードは、GitHub:MediaStoreを参照してください。

mediastore mediaprovider sample

上記のサンプルは、GoogleのAndroid Storage Samplesを参照して作成しました。

参考

Related Posts

codechachaCopyright ©2019 codechacha