摘要: Android MediaProvider 使用 SQLite 數據庫存儲圖片、視頻、音頻等多媒體文件的信息,供視頻播放器、音樂播放器、圖庫使用。本文詳細分析了 Android MediaProvider 多媒體數據庫(以 SDK 2.3.3 爲例)的模式(schema),並簡要敘述與系統媒體掃描服務 MediaScanner 的交互。html

1. 如何提取數據庫

以 root 權限進入 adb shell,使用 sqlite3 打開位於手機上 /data/data/com.android.providers.media/databases 上的一個數據庫。以 external 開頭的數據庫存儲的是 SD 卡媒體信息,一張卡對應一個,因此若是手機使用過多張卡會有多個數據庫。以 internal 開頭的數據庫存儲手機內部存儲器的媒體信息。由於通常用戶沒法訪問手機內部存儲器,並且這兩個數據庫結構是大致上是相同的,因此只須要關注 external 數據庫便可。mysql

Note: 數據庫都是以相似 external-ffffffff.db 的形式命名的, 後面的 8 個 16 進制字符是該 SD 卡 FAT 分區的 Volume ID。該 ID 是分區時決定的,只有從新分區或者手動改變纔會更改,能夠防止插入不一樣 SD 卡時數據庫衝突。要簡單瞭解 FAT 文件系統請看 Understanding FAT Filesystems android

接着在 sqlite3 執行命令 .schema 便可導出建立數據庫的 SQL 語句,也就是數據庫模式,具體以下(單擊展開代碼):git

Note: 若是手機沒有 sqlite3 程序,能夠搜索編譯過的源代碼的 out 目錄找到可執行文件,大約 90kb,而後 adb push 到手機的 /system/bin/ 目錄。安裝 sqlite三、查詢數據庫均須要 adb root 權限。 Android 的多媒體數據庫主要由表、視圖、索引以及觸發器組成。算法

接着還須要把數據庫轉換成圖,手工轉換的話就是根據 SQL 語句自行畫圖;推薦懶人使用自動轉換,先使用 adb pull 把數據庫導出,再使用 Power Designer 或者 Visio 的逆向工程(Reverse Engineer)功能生成物理數據模型(Physical Data Model)。注意要鏈接 sqlite 數據庫文件的話須要先安裝 sqlite 的 ODBC 驅動,教程在這裏:SQLite ODBC Driversql

2. 數據庫模式分析

圖片數據庫

圖片數據庫由兩個表組成,分別是 images 和 thumbnails,物理數據模型以下所示(Power Designer 逆向工程生成)shell


Note: 如何數據庫物理模型圖:<pk> 表示此爲主鍵。其他的表名、字段名、數據類型應該都能看明白。數據庫

Note: SQLite 從 3.6.19 版纔開始支持外鍵約束,Android 2.3.3 使用的是 3.7.x,但並無使用此特性,而是經過操做數據庫的程序(如 MediaScanner)以及觸發器來維護數據庫的一致性。這裏能夠了解 SQLite 的外鍵支持狀況緩存

數據表字段解析以下:網絡

images:圖片信息
字段 解析
_id 主鍵。圖片 id,從 1 開始自增
_data 圖片絕對路徑
_size 文件大小,單位爲 byte
_display_name 文件名
mime_type 相似於 image/jpeg 的 MIME 類型
title 不帶擴展名的文件名
date_added 添加到數據庫的時間,單位秒
date_modified 文件最後修改時間,單位秒
description
picasa_id 用於 picasa 網絡相冊
isprivate
latitude 緯度,須要照片有 GPS 信息
longitude 經度,須要照片有 GPS 信息
datetaken 取自 EXIF 照片拍攝時間,若爲空則等於文件修改時間,單位毫秒
orientation 取自 EXIF 旋轉角度,在圖庫旋轉圖片也會改變此值
mini_thumb_magic 取小縮略圖時生成的一個隨機數,見 MediaThumbRequest
bucket_id 等於 path.toLowerCase.hashCode(),見 MediaProvider.computeBucketValues()
bucket_display_name 直接包含圖片的文件夾就是該圖片的 bucket,就是文件夾名
thumbnails:縮略圖
字段 解析
_id 主鍵。縮略圖 id,從 1 開始自增
_data 圖片絕對路徑
image_id 縮略圖所對應圖片的 id,依賴於 images 表 _id 字段,可創建外鍵
kind 縮略圖類型,1 是大縮略圖,2 基本不用,3 是微型縮略圖但其信息不保存在數據庫
width 縮略圖寬度
height 縮略圖高度

視頻數據庫


數據表字段解析以下:

video:視頻信息
字段 解析
_id 主鍵。視頻 id
_data 視頻絕對路徑
_display_name 文件名
_size 文件大小,單位爲 byte
mime_type 相似於 video/avi 的 MIME 類型
date_added 添加到數據庫的時間,單位秒
date_modified 文件最後修改時間,單位秒
title 不帶擴展名的文件名
duration 視頻時長,單位毫秒
artist 藝術家
album 專輯名,通常爲文件夾名
resolution
description
isprivate
tags
category
language
mini_thumb_data
latitude
longitude
datetaken
mini_thumb_magic 取小縮略圖時生成的一個隨機數,見 MediaThumbRequest
bucket_id 等於 path.toLowerCase.hashCode(),見 MediaProvider.computeBucketValues()
bucket_display_name 直接包含視頻的文件夾就是該圖片的 bucket,就是文件夾名
bookmark
videothumbnails:視頻縮略圖
字段 解析
_id 主鍵。縮略圖 id
_data 縮略圖絕對路徑
video_id 縮略圖所對應視頻的 id,依賴於 video 表 _id 字段
kind 縮略圖類型,1 是大圖,視頻只能取類型 1
width 縮略圖寬度
height 縮略圖高度

音頻數據庫

音頻數據庫是最複雜的,由 10 個表組成。物理數據模型以下所示:


album_art:專輯封面
字段 解析
album_id 主鍵。專輯 id
_data 專輯封面緩存的路徑
albums:專輯信息
字段 解析
album_id 主鍵。專輯 id
album_key 全大寫字母,用於字母索引
album 專輯名
android_metadata:當前字符編碼
字段 解析
locale 默認字符編碼,例如 zh_CN
artists:藝術家
字段 解析
artist_id 主鍵。藝術家 id
artist_key 全大寫字母,用於字母索引
artist 藝術家
audio_genres:流派
字段 解析
_id 主鍵。流派 id
name 流派名稱
audio_genres_map:音頻流派映射
字段 解析
_id 主鍵。映射 id
audio_id 音頻 id
genre_id 流派 id

Note: 爲什麼要創建映射表:爲了消除數據冗餘。假若有大量音頻屬於同一流派,若是沒有映射表則須要每一個音頻都須要記錄一樣的流派數據,有了映射表以後則只有一條記錄就夠了。這符合數據庫設計的第三範式(the 3rd normal form)

audio_meta:音頻信息
字段 解析
_id 主鍵。音頻 id
_data 文件絕對路徑
_display_name 文件名
_size 文件大小,單位 byte
mime_type 相似於 audio/mpeg 的 MIME 類型
date_added 添加到數據庫的時間,單位秒
date_modified 文件最後修改時間,單位秒
title 來自 ID3 信息的標題,無則爲不帶擴展名的文件名
title_key 全大寫字母的標題
duration 時長
artist_id 藝術家 id
composer 來自 ID3 信息,做曲家
album_id 專輯 id
track 來自 ID3 信息,音軌
year 來自 ID3 信息,年代
is_ringtone 是否鈴聲,0 或 1
is_music 是否音樂,1 纔會在音樂播放器顯示
is_alarm 是否鬧鐘鈴聲
is_notification 是否通知鈴聲
is_podcast 是否 podcast
bookmark
audio_playlists:播放列表
字段 解析
_id 主鍵。播放列表 id
_data
name 播放列表名
date_added
date_modified
audio_playlists_map:音頻播放列表映射
字段 解析
_id 主鍵。映射 id
audio_id 音頻 id
playlist_id 播放列表 id
play_order 播放順序

索引

在 Android 數據庫當中基本上使用自增 id 值做爲主鍵,並創建了索引。索引能夠加快數據查找速度,但因爲須要維護索引因此插入/刪除等寫入操做速度會變慢。索引以下:

CREATE INDEX album_id_idx on audio_meta(album_id);
2 CREATE INDEX album_idx on albums(album);
3 CREATE INDEX albumkey_index on albums(album_key);
4 CREATE INDEX artist_id_idx on audio_meta(artist_id);
5 CREATE INDEX artist_idx on artists(artist);
6 CREATE INDEX artistkey_index on artists(artist_key);
7 CREATE INDEX image_bucket_index ON images(bucket_id, datetaken);
8 CREATE INDEX image_id_index on thumbnails(image_id);
9 CREATE INDEX sort_index on images(datetaken ASC, _id ASC);
10 CREATE INDEX title_idx on audio_meta(title);
11 CREATE INDEX titlekey_index on audio_meta(title_key);
12 CREATE INDEX video_bucket_index ON video(bucket_id, datetaken);
13

CREATE INDEX video_id_index on videothumbnails(video_id);


1 CREATE INDEX album_id_idx on audio_meta(album_id);
2 CREATE INDEX album_idx on albums(album);
3 CREATE INDEX albumkey_index on albums(album_key);
4 CREATE INDEX artist_id_idx on audio_meta(artist_id);
5 CREATE INDEX artist_idx on artists(artist);
6 CREATE INDEX artistkey_index on artists(artist_key);
7 CREATE INDEX image_bucket_index ON images(bucket_id, datetaken);
8 CREATE INDEX image_id_index on thumbnails(image_id);
9 CREATE INDEX sort_index on images(datetaken ASC, _id ASC);
10 CREATE INDEX title_idx on audio_meta(title);
11 CREATE INDEX titlekey_index on audio_meta(title_key);
12 CREATE INDEX video_bucket_index ON video(bucket_id, datetaken);
13 CREATE INDEX video_id_index on videothumbnails(video_id);

因爲比較簡單就不解釋了,要深刻了解索引能夠參考這個關於 SQL Server 的分析MySQL索引背後的數據結構及算法原理,原理應該是差很少的。

視圖

視圖相似於表,但並不是獨立存在,是從其餘表裏面查詢數據獲得的。使用視圖能夠加快數據庫查詢速度,不用每次都執行復雜的 SQL 語句查詢。圖以下所示:


Note: 如何看視圖:圖下面的部分是數據來源的表,中間是從表中選取的字段,但相似於 COUNT 等 SQL 查詢操做沒法在圖上體現,最好仍是看實際 SQL 語句。

Note: SQLite 當中視圖都是隻讀的,也就是說不能對視圖進行插入、更新、刪除等操做。可是能夠在視圖創建 INSTEAD OF 觸發器來達到一樣的目的,多媒體數據庫當中的 audio_delete 觸發器就是如此。

觸發器

觸發器是爲了維護數據庫刪除操做而創建的,由於所刪除的表可能與另外的表有關係,須要同時刪除另一個表的字段。能夠看如下一個例子:

CREATE TRIGGER audio_meta_cleanup
2 DELETE ON audio_meta
3 BEGIN
4     DELETE FROM audio_genres_map WHERE audio_id = old._id;
5     DELETE FROM audio_playlists_map WHERE audio_id = old._id;
6 END;
1 CREATE TRIGGER audio_meta_cleanup
2 DELETE ON audio_meta
3 BEGIN
4     DELETE FROM audio_genres_map WHERE audio_id = old._id;
5     DELETE FROM audio_playlists_map WHERE audio_id = old._id;
6 END;

這是關於 audio_meta 表的觸發器,意思是當刪除此表上的記錄時,同時刪除 audio_genres_map 表上 audio_id 與此表 id 相同的記錄,刪除 audio_playlists_map 表上 audio_id 與此表 id 相同的記錄。這樣當刪除 audio_meta 表的記錄時,另外兩個表的相應記錄也會自動刪除,不會因爲漏刪除而殘留多餘數據。

3. 如何維護數據庫

插入

插入、更新主要由 MediaScanner 進行,當刪除/移動媒體文件時 MediaScanner 會掃描磁盤並更新數據庫。數據插入主要在 endFile() 方法中進行,例如插入音頻記錄時相關的表都會插入相應的記錄。而圖片、視頻縮略圖,專輯封面這幾個則是第一次取圖片的時候纔會生成縮略圖保存到磁盤,並把記錄插入到數據庫中。

刪除

刪除操做主要由觸發器維護。例如當一個應用刪除圖片時,通常只會刪除圖片數據庫,因此必需要有觸發器同時刪除縮略圖數據庫。