• Android 系统提供媒体库 URI 与 数据库的对应关系
    • 前言
    • MediaProvider
      • queryUri uri
      • getDatabaseForUriuri
    • Uri
      • URI_MATCHERaddURI
      • URI_MATCHERmatchuri
    • 总结
    • 附MediaStore Uri 与 数据库对应表仅供参考

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:4456private DatabaseHelper getDatabaseForUri(Uri uri) {    synchronized (mDatabases) {        if (uri.getPathSegments().size() >= 1) {                return mDatabases.get(uri.getPathSegments().get(0));            }        }        return null;    }//MediaProvider.java:4490private Uri attachVolume(String volume) {    //将 “db” 路径判断存入 “mDatabases”}

MediaProvider.onCrearte() 被创建启动的时候会查看是否有外置存储器,并执行 attachVolume() 方法(:508),将内外置存储器中的数据库路径存入 mDatabases

Uri

通用资源标识符(Uniform Resource Identifier)4

URI是一个用于标识某一互联网资源名称的字符串。 该种标识允许用户对任何(包括本地和互联网)的资源通过特定的协议进行交互操作。在ContentProvider机制中,使用ContentResolver对象通过URI定位ContentProvider提供的资源。
ContentProvider使用的URI语法结构如下:

content:////
  • 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.5

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


  1. Android中利用ContentResolver获取本地音乐和相片 ↩
  2. Android ContentProvider 完全解析及简单DEMO ↩
  3. MeidaProvider 源码(需梯子) ↩
  4. Android ContentProvider 完全解析及简单DEMO ↩
  5. UriMatcher | Android Developers ↩

更多相关文章

  1. 一句话锁定MySQL数据占用元凶
  2. Android(安卓)异步开发之 AsyncQueryHandler
  3. Android(安卓)ListView理解,BaseAdapter
  4. Android自定义dialog向Activity传递数据
  5. 自定义Android(安卓)ORM 框架greenDAO数据库文件的路径
  6. Android学习笔记之数据持久化
  7. Android(安卓)第三方开源:Volley通过网络下载数据
  8. android 数组数据绑定到listview
  9. 利用ContentProvider的添加数据

随机推荐

  1. Palo Alto Networks(派拓网络)云威胁报告新
  2. 一周 Go World 新鲜事
  3. 快递100电商快递地图轨迹推送服务API接口
  4. 关键路径(C语言)
  5. 焕新2021 | Palo Alto Networks(派拓网络)
  6. 快递100查询地图轨迹API接口案例代码
  7. Ceph最新版Dashboard初探
  8. 5G时代,互联网***的“道”与“术”
  9. C语言动态内存函数的理解和总结
  10. API文档工具是什么?附带6款好用的API文档