[toc]
Android 系統(tǒng)提供媒體庫 URI 與 數據庫的對應關系
前言
在 Android 系統(tǒng)中笼吟,本地媒體(e.g. 音樂)文件會被檢索并且以 數據庫
的形式進行保存管理,在開發(fā) Android 程序的時候霸旗,我們可以使用 ContentProvider
設置 uri
去獲取相關的數據[1]贷帮。
在使用 ContentProvider
組件的時候,通常的做法是繼承父類 ContentProvider
诱告,然后重載父類中 inser()
撵枢、 delete()
、 update()
精居、 query()
等方法實現對數據的操作[2]锄禽。
既然本地媒體文件在系統(tǒng)中是以 數據庫
的形式來管理,并且提供了 uri
供我們使用靴姿,那么我猜在系統(tǒng)內部應該是有個 ***Provider
的去實現對 數據庫
的操作沃但。
通過 Google 和 Baidu 找到了相關名詞——MediaProvider
,并且找到了源碼[3]空猜。
MediaProvider
public class MeidaProvider extends ContentProvider {
private static final Uri MEDIA_URI = Uri.parse("content://media");
private static final Uri ALBUMART_URI = Uri.parse("content://media/external/audio/albumart");
...
}
我想這個 MeidaProvider.class
也許會給我們想要的線索绽慈,因為使用 ContentResolver
獲取本地音樂中,使用的 uri
為 content://media/external/audio/media
辈毯,而這個 class
也出現了類似的字段坝疼。
MeidaProvider extends ContentProvider
那么應該會 重載 ContentProvider
的相關方法以向外提供數據操作方法。而在查詢音樂數據使用的方法為 query(Uri uri, ...)
傳入一個 uri
谆沃,所以先查看 query( )
的內容钝凶。
query(Uri uri, ...)
//MediaProvider.class:1813
public Cursor query(Uri uri, String[] projectionIn, String selection, String[] selectionArgs, String sort) {
int table = URI_MATCHER.match(uri);
...
String groupBy = null;
DatabaseHelper helper = getDatabaseForUri(uri);
...
switch (table) {
...
case AUDIO_MEDIA:
if (projectionIn != null && projectionIn.length == 1 && selectionArgs == null
&& (selection == null || selection.equalsIgnoreCase("is_music=1")
|| selection.equalsIgnoreCase("is_podcast=1") )
&& projectionIn[0].equalsIgnoreCase("count(*)")
&& keywords != null) {
//Log.i("@@@@", "taking fast path for counting songs");
qb.setTables("audio_meta");
} else {
qb.setTables("audio");
for (int i = 0; keywords != null && i < keywords.length; i++) {
if (i > 0) {
qb.appendWhere(" AND ");
}
qb.appendWhere(MediaStore.Audio.Media.ARTIST_KEY +
"||" + MediaStore.Audio.Media.ALBUM_KEY +
"||" + MediaStore.Audio.Media.TITLE_KEY + " LIKE ? ESCAPE '\\'");
prependArgs.add("%" + keywords[i] + "%");
}
}
break;
case AUDIO_MEDIA_ID:
qb.setTables("audio");
qb.appendWhere("_id=?");
prependArgs.add(uri.getPathSegments().get(3));
break;
...
}
//MediaProvider.class:4716
static
{
...
URI_MATCHER.addURI("media", "*/audio/media", AUDIO_MEDIA);
URI_MATCHER.addURI("media", "*/audio/media/#", AUDIO_MEDIA_ID);
URI_MATCHER.addURI("media", "*/audio/media/#/genres", AUDIO_MEDIA_ID_GENRES);
...
}
從以上代碼可以看出,在 query( )
中唁影,使用 URI_MATCHER.match(uri)
對傳入的 uri
進行解析耕陷,然后在 switch( )
去 setTables( )
設置對應的表或者視圖。
getDatabaseForUri(uri);
//MediaProvider.java:4456
private DatabaseHelper getDatabaseForUri(Uri uri) {
synchronized (mDatabases) {
if (uri.getPathSegments().size() >= 1) {
return mDatabases.get(uri.getPathSegments().get(0));
}
}
return null;
}
//MediaProvider.java:4490
private Uri attachVolume(String volume) {
//將 “db” 路徑判斷存入 “mDatabases”
}
在 MediaProvider.onCrearte()
被創(chuàng)建啟動的時候會查看是否有外置存儲器据沈,并執(zhí)行 attachVolume()
方法(:508)哟沫,將內外置存儲器中的數據庫路徑存入 mDatabases
。
Uri
通用資源標識符(Uniform Resource Identifier)[2]
URI是一個用于標識某一互聯網資源名稱的字符串锌介。 該種標識允許用戶對任何(包括本地和互聯網)的資源通過特定的協(xié)議進行交互操作嗜诀。在ContentProvider機制中,使用ContentResolver對象通過URI定位ContentProvider提供的資源孔祸。
ContentProvider使用的URI語法結構如下:content://<authority>/<data_path>/<id>
- content:// 是通用前綴隆敢,表示該UIR用于ContentProvider定位資源。
- < authority > 是授權者名稱崔慧,用來確定具體由哪一個ContentProvider提供資源拂蝎。因此一般< authority >都由類的小寫全稱組成,以保證唯一性惶室。
- < data_path > 是數據路徑温自,用來確定請求的是哪個數據集玄货。如果ContentProvider近提供一個數據集,數據路徑則可以省略捣作;如果ContentProvider提供多個數據集誉结,數據路徑必須指明具體數據集。數據集的數據路徑可以寫成多段格式券躁,例如people/girl和people/boy惩坑。
- < id > 是數據編號,用來唯一確定數據集中的一條記錄也拜,匹配數據集中_ID字段的值以舒。如果請求的數據不只一條,< id >可以省略慢哈。
如請求整個people數據集的URI為:
content://com.example.peopleprovider/people
而請求people數據集中第3條數據的URI則應寫為:
content://com.example.peopleprovider/people/3
URI_MATCHER.addURI( );
Add a URI to match, and the code to reutrn when this URI is matched. URI nodes may be exact match string, the token "*" that matches any text, or the token "#" that matches only numbers.[4]
//MediaProvider.class:4716
static
{
...
URI_MATCHER.addURI("media", "*/audio/media", AUDIO_MEDIA);
URI_MATCHER.addURI("media", "*/audio/media/#", AUDIO_MEDIA_ID);
URI_MATCHER.addURI("media", "*/audio/media/#/genres", AUDIO_MEDIA_ID_GENRES);
...
}
添加 uri
匹配對應關系蔓钟。
第二個參數 "*/audio/media"
中的 "*" 給 internal
和 external
預留位置,用于指明訪問的數據庫位于 內置存儲器 或是 外置存儲器 卵贱。
URI_MATCHER.match(uri);
源碼使用了 UriMatch
對傳入的 uri
進行匹配滥沫。
總結
MediaProvider
本質上就是一個 Provider
,用過 ContentProvider
去理解應該不難键俱。
附:MediaStore Uri 與 數據庫對應表(僅供參考)
URI (content://media/external/audio/) | Table \ View (external.db) |
---|---|
media (specific) | audio_meta |
media (all) & media/# | audio |
media/#/genres & media/#/genres/# | audio_genres |
media/#/playlists & media/#/playlists/# | audio_playlists |
genres & genres/# | audio_genres |
genres/#/members | audio_genres_map_noid |
genres/all/members | audio_genres_map_noid |
playlists & playlists/# | audio_playlists |
playlists/#/members & playlists/#/members/# | audio_playlists_map |
artists (specific) | audio_meta |
artists (all) & artists/# | artist_info |
artists/#/albums | [多張表聯合] |
albums (specific) | audio_meta |
albums (all) & albums/# | album_info |
albumart/# | album_art |
P. S. MediaProvider.class
比較大兰绣,下載下來之后,放到 Android Studio
上可以方便查看代碼编振。
'external.db'路徑:/data/data/com.android.providers.media/databases/