Android 系统提供媒体库 URI 与 数据库的对应关系

[toc]

Android 系统提供媒体库 URI 与 数据库的对应关系

前言

在 Android 系统中,本地媒体(e.g. 音乐)文件会被检索并且以 数据库 的形式进行保存管理,在开发 Android 程序的时候,我们可以使用 ContentProvider 设置 uri 去获取相关的数据[1]
在使用 ContentProvider 组件的时候,通常的做法是继承父类 ContentProvider,然后重载父类中 inser()delete()update()query() 等方法实现对数据的操作[2]
既然本地媒体文件在系统中是以 数据库 的形式来管理,并且提供了 uri 供我们使用,那么我猜在系统内部应该是有个 ***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 获取本地音乐中,使用的 uricontent://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() 被创建启动的时候会查看是否有外置存储器,并执行 attachVolume() 方法(:508),将内外置存储器中的数据库路径存入 mDatabases

Uri

通用资源标识符(Uniform Resource Identifier)[2]

URI是一个用于标识某一互联网资源名称的字符串。 该种标识允许用户对任何(包括本地和互联网)的资源通过特定的协议进行交互操作。在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" 中的 "*" 给 internalexternal 预留位置,用于指明访问的数据库位于 内置存储器 或是 外置存储器

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 上可以方便查看代码。

AS

'external.db'路径:/data/data/com.android.providers.media/databases/


  1. Android中利用ContentResolver获取本地音乐和相片

  2. Android ContentProvider 完全解析及简单DEMO

  3. MeidaProvider 源码(需梯子)

  4. UriMatcher | Android Developers

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 1.什么是ContentProvider 首先,ContentProvider(内容提供者)是android中的四...
    秦越人87阅读 3,817评论 0 8
  • 2017年5月17日 Kylin_Wu 标注(★☆)为考纲明确给出考点(必考) 常见手机系统(★☆) And...
    Azur_wxj阅读 1,795评论 0 10
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,430评论 25 707
  • 其实每个人心中都有一座城池,有的人知道,有的人不知道,藏的最深,护的最周全,最重要,也最脆弱。 我心中的城池是中国...
    程煜阅读 258评论 0 1
  • 木心说,从前的日色变得慢 ,车,马,邮件都慢,一生只够爱一个人。 偶然读到这样的句子,我是极有感触的,我也曾期许一...
    啦啦啦杨大大阅读 248评论 0 0