Android MediaStore.MediaColumnsを調べる
散歩でカモを見つけ観察している柴犬です。
概要
「Android CameraX を始めてみて3」の takePhoto() 関数の入れ子 onImageSaved() 関数の中でもう少し突き詰めてみたいところがありましたので、記録します。
2023年10月26日現在
次に紹介する本はちょっと過激な表紙ですが、本の内容はまじめてよく理解できる書き方をしています。先の「Androidアプリ開発」で飛ばしているところを丁寧に説明しているので、これで理解が早まりました。お勧めです。
しかもこれが100円で買え、ボリュームがすごい量です。なので著者に感謝です。
onImageSaved() 関数の中2つの疑問
調べてなんとなくできたという感じでしたが次の 2 点について疑問に思っていましたので、もう少し突っ込んで試行してみました。
1. projection はどういう働きをするのか
・・・・私の判定 SQL でいうならば Field の指定のようです。
2. カラムのインデックスがなぜ 0 なのか
・・・・私の判定 一つの Field だけなので 0 でよい。
override fun onImageSaved(output: ImageCapture.OutputFileResults) { val msg = "Photo capture succeeded: ${output.savedUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) //*****疑問1 val projection = arrayOf(MediaStore.MediaColumns.DATA) //アプリケーションのデータベースに接続して URI を検索します val cursor = applicationContext.contentResolver.query( Uri.parse( output.savedUri.toString() ), projection, null, null, null ) if (cursor != null) { var path: String? = null //最初のレコードにカーソルを移します if (cursor.moveToFirst()) { //*****疑問2 //path はカラムのインデックスが 0 のようです path = cursor.getString(0) } cursor.close() if (path != null) { //パスが取得できたので File を作成します val file = File(path) //ftpDirectory は "ftp://192.168.□□.□□/" 不要 val filelist = FtpAccess().Await( "□□□□", "□□□□□□□□□□", "192.168.□□.□□", "□□□□□□/□□□□□□/", file ) findViewById<TextView>(R.id.tvinfo).text = filelist } } }
疑問点を推測
上のコードを実行して最終行で表示させないように次のとおりコメントアウト
findViewById<TextView>(R.id.tvinfo).text = filelist
↓
//findViewById<TextView>(R.id.tvinfo).text = filelist
if (moveToFirst()) {} のブロックの中に次のコード(cursorのスコープ内なので)
val columnnames: Array<String> = getColumnNames()
findViewById<TextView>(R.id.tvinfo).text = columnnames.joinToString("\n")
または
val infolist = mutableListOf<String>()
for (i in 0..(getCount() - 1)) {
infolist.add(getColumnName(i))
}
findViewById<TextView>(R.id.tvinfo).text = infolist.joinToString("\n")
を挿入して実行します。
実行の結果
いくつかの名前があると予想していましたが、なんと次の一つのみでした。
_data
原因の推測
この原因を推測してみました。
cursor を取得するときに projection を次のようにして、データの項目のみとしているからと結論しました。
val projection = arrayOf(MediaStore.MediaColumns.DATA)
であるなら、projection を次のように指定しなければすべてのカラム名が見えるはずです。
val cursor = applicationContext.contentResolver.query(
Uri.parse(output.savedUri.toString()), projection, null, null, null )
↓↓
val cursor = applicationContext.contentResolver.query(
Uri.parse(output.savedUri.toString()), null, null, null, null )
推測の検証結果
早々に実行してみました。予想通りすべて表示されました。
全部で次の56項目が表示されました。
instance_id,
compilation,
disc_number,
duration,
album_artist,
description,
picasa_id,
resolution,
atitude,
orientation,
artist,
author,
height,
is_drm,
bucket_display_name,
owner_package_name,
f_number,
volume_name,
date_modified,
writer,
date_expires,
composer,
_display_name,
scene_capture_type,
datetaken,
mime_type,
bitrate,
cd_track_number,
_id,
iso,
xmp,
year,
_data,
_size,
album,
genre,
title,
width,
longitude,
is_favorite,
is_trashed,
exposure_time,
group_id,
document_id,
generation_added,
is_download,
generation_modified,
is_pending,
date_added,
mini_thumb_magic,
capture_framerate,
num_tracks,
isprivate,
original_document_id,
bucket_id,
ralative_path
_data は上から32番目にありました。
MediaStore.MediaColumns
val projection = arrayOf(MediaStore.MediaColumns.DATA) の配列の中身である
MediaStore.MediaColumns.DATA が何か調べてみることにします。それはただの値でした。
MediaStore.MediaColumns に関するリファレンスは次の URL に載っています。
https://developer.android.com/reference/android/provider/MediaStore.MediaColumns
これををまとめて次の表を作りました。
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_ALBUM
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_ARTIST または ExifInterface#TAG_ARTIST
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_AUTHOR
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_BITRATE
The primary bucket ID of this media item.
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_CAPTURE_FRAMERATE
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_COMPILATION
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_COMPOSER
Absolute filesystem path to the media item on disk.
The time the media item was first added.
The time the media item should be considered expired.
このメディアから導き出される指標値 File#lastModified()
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_DATE または ExifInterface#TAG_DATETIME_ORIGINAL
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER
The display name of the media item.
The “document ID” GUID as defined by the XMP Media Management standard, extracted from any XMP metadata contained within this media item.
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_DURATION
Generation number at which metadata for this media item was first inserted.
Generation number at which metadata for this media item was last changed.
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_GENRE
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_VIDEO_HEIGHT, MediaMetadataRetriever#METADATA_KEY_IMAGE_HEIGHT または ExifInterface#TAG_IMAGE_LENGTH
The “instance ID” GUID as defined by the XMP Media Management standard, extracted from any XMP metadata contained within this media item.
Flag indicating if the media item has been marked as being part of the Downloads collection.
Flag indicating if a media item is DRM protected.
Flag indicating if the media item has been marked as being a “favorite” by the user.
Flag indicating if a media item is pending, and still being inserted by its owner.
Flag indicating if a media item is trashed.
The MIME type of the media item.
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_NUM_TRACKS
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_VIDEO_ROTATION, MediaMetadataRetriever#METADATA_KEY_IMAGE_ROTATION, または ExifInterface#TAG_ORIENTATION
The “original document ID” GUID as defined by the XMP Media Management standard, extracted from any XMP metadata contained within this media item.
Package name that contributed this media.
Relative path of this media item within the storage device where it is persisted.
Calculated value that combines WIDTH and HEIGHT into a user-presentable string.
このメディアから導き出される指標値 File#length()
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_TITLE
Volume name of the specific storage device where this media item is persisted.
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_VIDEO_WIDTH, MediaMetadataRetriever#METADATA_KEY_IMAGE_WIDTH または ExifInterface#TAG_IMAGE_WIDTH
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_WRITER
インデックスされた XMP メタデータ
このメディアから導き出される指標値 MediaMetadataRetriever#METADATA_KEY_YEAR
DATAの説明
ディスク上のメディアへの絶対ファイルパスを格納しているとあります。
このパスを使ってファイル操作を行うことができるが、ファイルが常に利用可能であるとしてはならないとあります。
Android 11以降は読み取り専用になり、ファイルの場所を更新するには、DISPLAY_NAME列とRELATIVE_PATH列の値を使用しますとあります。
44項目にないもの
description は44項目の中にありませんが、次の MediaStore.Images.ImageColumns に含まれています。
今回はカメラで撮影した画像ファイルが対象ですので MediaStore.Images.ImageColumns の項目も含むようです。
44項目 + 10項目 = 54項目で2項目まだ不足していますが、それは
instance_id と _id の2項目で Columns データ構造上のものだから省かれていると推測されます。
MediaStore.Images.ImageColumns
このメディアから導き出される指標値 ExifInterface#TAG_IMAGE_DESCRIPTION
このメディアから導き出される指標値 ExifInterface#TAG_EXPOSURE_TIME
このメディアから導き出される指標値 ExifInterface#TAG_F_NUMBER
このメディアから導き出される指標値 ExifInterface#TAG_ISO_SPEED_RATINGS
Whether the image should be published as public or private.This constant represents a column name that can be used with a ContentProvider through a ContentValues or Cursor object.
This constant was deprecated in API level 29. location details are no longer indexed for privacy reasons, and this value is now always null. You can still manually obtain location metadata using ExifInterface#getLatLong(float[]).
This constant was deprecated in API level 29. location details are no longer indexed for privacy reasons, and this value is now always null. You can still manually obtain location metadata using ExifInterface#getLatLong(float[]).
This constant was deprecated in API level 29. all thumbnails should be obtained via MediaStore.Images.Thumbnails#getThumbnail, as this value is no longer supported.
This constant was deprecated in API level 29. this value was only relevant for images hosted on Picasa, which are no longer supported.
このメディアから導き出される指標値 ExifInterface#TAG_SCENE_CAPTURE_TYPE
見直し後のコード
これまでの結果を踏まえて次のようにコードを見直しました。
また、Kotlin イディオム風に
if (cursor != null)
↓↓
cursor?.
if (path != null)
↓↓
path?.
としてみました。
override fun onImageSaved(output: ImageCapture.OutputFileResults) { val msg = "Photo capture succeeded: ${output.savedUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) //val projection = null // 読み込む列の指定 // パフォーマンスとしては指定した方がよい val projection = arrayOf(MediaStore.MediaColumns.DATA) val selection = null // 行の絞り込みの指定 val selectionArgs = null // selectionの?を置き換える引数 val sortOrder = null // 並び順 //アプリケーションのデータベースに接続して URI を検索します val cursor = applicationContext.contentResolver.query( Uri.parse( output.savedUri.toString() ), projection, selection, selectionArgs, sortOrder ) cursor?.apply { var path: String? = null //最初のレコードにカーソルを移します if (moveToFirst()) { //データがあるカラム名は _data です。 val index: Int = getColumnIndex(MediaStore.MediaColumns.DATA) path = getString(index) } close() path?.let { //パスが取得できたので File を作成します val file = File(it) //ftpDirectory は "ftp://192.168.□□.□□/" 不要 val filelist = FtpAccess().Await( "□□□□", "□□□□□□□□□□", "192.168.□□.□□", "□□□□□□/□□□□□□/", file ) findViewById<TextView>(R.id.tvinfo).text = filelist } } }
実行の結果は問題ありませんでした。
今回はここまでとします。