メディアデータはMedia Storeを使ったアクセスが最適です。
その理由は、メディア特有の付加情報を使って、データを管理できるからです。
管理されたデータはブログラムから扱い易いです。また、ユーザの使い勝手(エクスペリエンス)も向上します。
バケットはメディアデータを管理する付加情報の一つです。
無くてもアクセスは可能ですが、積極的に取り入れた方が良いと思います。
今回は「ファイル名とバケットを指定したアクセス」について、まとめます。
「API≦28」と「API≧29」で動作が異なります。注意して下さい。
※環境:Android Studio Narwhal Feature Drop | 2025.1.2
目次
バケットとは
メディアデータはメディアタイプ(images/video/audio)で分類されます。
更に、各データはカテゴリー(Pictures/Movies/Musicなど)で分類されます。
このカテゴリーをMedia Storeでは「バケット(Bucket)」と呼んでいます。
各バケットはストレージ上にディレクトリを持ちます。そして、メディアファイルは属するバケットのディレクトリへ格納するようにします。
# pwd /storage/emulated/0 # ls -ld * drwxrwx--x 2 root sdcard_rw 4096 2025-08-16 01:59 Alarms drwxrwx--x 3 root sdcard_rw 4096 2025-08-16 01:58 Android drwxrwx--x 2 root sdcard_rw 4096 2025-08-16 01:58 DCIM drwxrwx--x 2 root sdcard_rw 4096 2025-08-16 01:58 Download drwxrwx--x 2 root sdcard_rw 4096 2025-08-16 01:58 Movies drwxrwx--x 2 root sdcard_rw 4096 2025-08-16 01:58 Music drwxrwx--x 2 root sdcard_rw 4096 2025-08-16 01:58 Notifications drwxrwx--x 2 root sdcard_rw 4096 2025-08-16 01:58 Pictures drwxrwx--x 2 root sdcard_rw 4096 2025-08-16 01:58 Podcasts drwxrwx--x 2 root sdcard_rw 4096 2025-08-16 01:58 Ringtones ※エミュレータ―(API 28)で確認
このように、メディアファイルをバケット(ディレクトリ)で管理する仕組みが、Media Storeに備わっています。
ファイル名とバケットを指定したアクセス
Media Storeを使った「アクセスの基本」と、動作の有様は同じです。
※アクセスの基本の詳細は「ImagesデータへMedia Storeでアクセス(API≦28)」を参照
ただ、_dataの作成方法が変わるだけです。
メディア情報の追加
ファイル名とバケットを指定したアクセスにおいて、データベースのテーブルへメディア情報を追加する方法は、次の通りです。
指定したファイル名とバケットから、意図的に_dataフィールドを作成します。
例えば、バケットを「DCIM」、ファイル名を「photo.png」にしたければ、メディア情報の_dataフィールドは「/…/DCIM/photo.png」になります。
fun Context.insertImage( data: ByteArray, bucket: String, filename: String ): Uri? { val _bucketDir = Environment.getExternalStoragePublicDirectory(bucket) if(! _bucketDir.exists()) _bucketDir.mkdir() val _contentValues = ContentValues().apply { val _data = File(_bucketDir, filename).absolutePath put(DataKey, _data) } val _mediaUri = contentResolver.insert(ContentUri, _contentValues) _mediaUri?.let { writeToUri_Resolver(data, _mediaUri) } return _mediaUri }
insertが実行されると、表のようなメディア情報が作成されて、そのレコードがデータベースへ登録されます。
カラム | 意味 | フィールド値 |
---|---|---|
_id | プライマリキー | ユニークな連番 |
_data | ファイルのパス | 指定したファイル名・格納フォルダ名から作成 (/storage/emulated/0/DCIM/photo.png) ※指定した値 bucket(格納フォルダ名):DCIM filename(ファイル名):photo.png |
_display_name | 表示名 | _dataのファイル名 (photo.png) |
mime_type | マイムタイプ | _dataのファイル名の拡張子で決まる (image/png) |
bucket_display_name | バケット名 | _dataの格納フォルダ名 (DCIM) |
bucket_id | バケットID | _dataの格納フォルダのパスのハッシュ値 (※) |
title | タイトル | _dataの拡張子を除いたファイル名 (photo) |
※ハッシュ値の計算方法:「"/storage/emulated/0/DCIM".lowercase().hashCode()」 |
_idと_data以外のフィールド値は、_dataを元にして決まります。
# pwd /data/data/com.android.providers.media/databases # sqlite3 ./external.db sqlite> .headers on sqlite> .mode column sqlite> select _id,_data,_display_name,mime_type,bucket_display_name,bucket_id,title from images; _id _data _display_name mime_type bucket_display_name bucket_id title ---------- ---------------------------------- ------------- ---------- ------------------- ----------- ---------- 26 /storage/emulated/0/DCIM/photo.png photo.png image/png DCIM -2075821635 photo
そして、メディア情報のURI(insertの戻り値)が確定します。
メディアファイルの書き込み
メディア情報のURIが確定していれば、書き込みはこのURIを使って行います。
fun Context.writeToUri_Resolver(data: ByteArray, uri: Uri) { try { contentResolver.openOutputStream(uri, "w")?.use { outputStream -> outputStream.write(data) outputStream.flush() } } catch (e: Exception) { Log.i(TAG, "[writeToUri] ${e}") } }
openOutputStreamが実行されると、URIが示すレコードがデータベースから読み出されます。そして、メディア情報の_dataフィールドが参照されて、指定されたファイルパスにデータを出力します。
val _uri = insertImage(_data, Environment.DIRECTORY_DCIM, "photo.png")
# pwd /storage/emulated/0 # ls -ld DCIM/* -rw-rw---- 1 root sdcard_rw 47891 2025-08-15 01:57 DCIM/photo.png
メディアファイルの読み出し
メディア情報のURIが確定していれば、読み出しはこのURIを使って行います。
fun Context.readFromUri_Resolver(uri: Uri): ByteArray { val _os = ByteArrayOutputStream() contentResolver.openInputStream(uri).use { stream -> _os.use { stream?.copyTo(_os) } } return _os.toByteArray() }
openInputStreamが実行されると、URIが示すレコードがデータベースから読み出されます。そして、メディア情報の_dataフィールドが参照されて、指定されているパスからデータを取得します。
val _data = _uri?.let { readFromUri_Resolver(it) } Log.i(TAG, "Data = ${_data?.take(10)}")
Data = [71, 73, 70, 56, 55, 97, 24, 1, -46, 0]
注意:利用可能なバケット
メディアタイプ毎に利用可能なバケットの制限はありません。
例えば、imagesデータ(media_type=1)は全てのバケットに保存出来てしまいます。
バケット名 | パラメータ (Enviroment.****) | images (1) | video (3) | audio (2) | downloads (*) (API≧29) | files (*) |
---|---|---|---|---|---|---|
Pictures | DIRECTORY_PICTURES | ○ | ○ | ○ | ○ |
|
DCIM | DIRECTORY_DCIM | ○ | ○ | ○ | ○ | |
Movies | DIRECTORY_MOVIES | ○ | ○ | ○ | ○ | |
Music | DIRECTORY_MUSIC | ○ | ○ | ○ | ○ | |
Alarms | DIRECTORY_ALARMS | ○ | ○ | ○ | ○ | |
Notifications | DIRECTORY_NOTIFICATIONS | ○ | ○ | ○ | ○ | |
Podcasts | DIRECTORY_PODCASTS | ○ | ○ | ○ | ○ | |
Ringtones | DIRECTORY_RINGTONES | ○ | ○ | ○ | ○ | |
Audiobooks | DIRECTORY_AUDIOBOOKS (API≧29) | |||||
Recordings | DIRECTORY_RECORDINGS (API≧31) | |||||
Downloads | DIRECTORY_DOWNLOADS | ○ | ○ | ○ | ○ | |
Documents | DIRECTORY_DOCUMENTS | ○ | ○ | ○ | ○ | |
※DIRECTORY_SCREENSHOTS(API≧29)はPictureまたはDCIMのサブ(下位)ディレクトリ ※DIRECTORY_AUDIOBOOKSは(API≧29)で存在するが、(API≧30)で利用可能になる ※タイトル欄の( )はmedia_type ・*はMIMEに従う、text/plainの場合は「media_type=0」 |
バケット名 | パラメータ (Enviroment.****) | images (1) | video (3) | audio (2) | downloads (*) (API≧29) | files (*) |
---|---|---|---|---|---|---|
Pictures | DIRECTORY_PICTURES | ○ | ○ API≠29 | |||
DCIM | DIRECTORY_DCIM | ○ | ○ | |||
Movies | DIRECTORY_MOVIES | ○ | ||||
Music | DIRECTORY_MUSIC | ○ | ||||
Alarms | DIRECTORY_ALARMS | ○ | ||||
Notifications | DIRECTORY_NOTIFICATIONS | ○ | ||||
Podcasts | DIRECTORY_PODCASTS | ○ | ||||
Ringtones | DIRECTORY_RINGTONES | ○ | ||||
Audiobooks | DIRECTORY_AUDIOBOOKS (API≧29) | ○ API≧30 | ||||
Recordings | DIRECTORY_RECORDINGS (API≧31) | ○ | ||||
Downloads | DIRECTORY_DOWNLOADS | ○ | ○ | |||
Documents | DIRECTORY_DOCUMENTS | ○ | ||||
※DIRECTORY_SCREENSHOTS(API≧29)はPictureまたはDCIMのサブ(下位)ディレクトリ ※DIRECTORY_AUDIOBOOKSは(API≧29)で存在するが、(API≧30)で利用可能になる ※タイトル欄の( )はmedia_type ・*はMIMEに従う、text/plainの場合は「media_type=0」 |
(参考)API≧29は、メディアタイプ毎に利用可能なバケットの制限があります。注意して下さい。
注意:バケットの識別方法
メディアファイルが所属するバケットは、表に示したカラムから識別できます。
- (a)バケット名を表すカラム(bucket_display_name,bucket_id)
- (b)バケットを表すフラグ形式のカラム(is_music,is_alarm,など)
メディアデータのaudioは(a)を持ちません。その代わりに(b)を持ちます。
メディアタイプ | バケット | 識別方法(カラム) | |
---|---|---|---|
Images(1) | Picture(Defailt) | bucket_id bucket_display_name | |
DCIM | |||
video(3) | Movie(Defailt) | ||
Pictures | |||
DCIM | |||
audio(2) | Music(Defailt) | is_music | |
Alarms | is_alarm | ||
Notifications | is_notification | ||
Podcasts | is_podcast | ||
Ringtones | is_ringtone | ||
※DCIM:Digital Camera IMage ※メディアタイプの()はmedia_type値 |
メディアタイプ | バケット | 識別方法(カラム) | |
---|---|---|---|
Images(1) | Picture(Defailt) | bucket_id bucket_display_name relative_path | |
DCIM | |||
video(3) | Movie(Defailt) | ||
Pictures | |||
DCIM | |||
audio(2) | Music(Defailt) | is_music | |
Alarms | is_alarm | ||
Notifications | is_notification | ||
Podcasts | is_podcast | ||
Ringtones | is_ringtone | ||
Audiobooks(API≧29) | is_audiobook | ||
Recordings(API≧31) | is_recording | ||
※DCIM:Digital Camera IMage ※メディアタイプの()はmedia_type値 |
(参考)メディアデータのaudioは、API≧29の場合に(a)(b)の両方を持ちます。
「メディアデータのaudioは(a)を持ちません。」と書きました。
しかし、filesテーブルには、bucket_display_nameとbucket_idが存在しています。insertにより値も書き込まれています。
持たない理由は、filesテーブルのVIEWであるaudioテーブルに、この2つのカラムが切り出されていないからです。
そのため、参照ができません。
関連記事: