多媒体文件管理主要组成部分:
1),MediaScannerService,扫描多媒体文件。
扫描的对象包括内部、外部存储设备,它继承自service,并实现了Runnable接口,在一个独立的线程中执行扫描操作。
也会扫描文件,文件扫描完成时会有回调scanCompleted()@IMediaScannerListener。
2),MediaProvider,存储媒体文件信息。
MediaScannerService扫描到的结果,由MediaProvider完成存储。
3),MediaStore,查询入口。
MediaProvider相当于存储文件的仓库,而MediaStore相当于展示媒体文件的柜台。当你想查看一个媒体文件时,通常是从柜台入手。
MediaStore把所有的文件分为几类:
MediaStore.java
MediaStore.Files所有的文件,包括非多媒体文件。
MediaStore. InternalThumbnails,这个类是被图像缩略图,视频缩略图内部使用的。它没有提供uri,所以别的地方应该访问不了。
MediaStore. Images,图像文件存储的地方。
MediaStore.Audio,音频文件类存储的地方。
MediaStore.Video,视频文件类存储的地方。
每种类型都可以通过getContentUri()接口获取具体的引用位置。
MediaStore.java,
URI中用到的常量定义:
public static final String AUTHORITY ="media";
private static final StringCONTENT_AUTHORITY_SLASH = "content://" +
AUTHORITY +"/";
Volume 分internal和 external。
public static final class Files {
//文件files对应的uri:content://media/external/file ,其中volume是external。
//也就是external这个数据库的files这张表的uri。
public static Uri getContentUri(String volumeName) {
return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + "/file");
}
}
图像又分media和thumbnails
public static final class Images {
public static final class Media implements ImageColumns {
public static Uri getContentUri(String volumeName) {
return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
"/images/media");
}
}
public static class Thumbnails implements BaseColumns {
public static Uri getContentUri(String volumeName) {
return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
"/images/thumbnails");
}
}
}
接着看下处理文件存储的MediaProvider,主要工作是创建用于存储各种媒体信息的数据库,并提供更新、查询等操作。
android\packages\providers\MediaProvider
先看他的Androidmanifest.xml中对权限的要求:
AndroidManifest.xml
<provider android:name="MediaProvider" android:authorities="media"
android:multiprocess="false" android:exported="true">
//这个元素用于给内容提供器的数据子集授权,数据子集是由content:uri的路径部分来标识的。授权是提供器给客户端提供的一种能力,这样客户端就可以访问通常没有权限访问的数据,从而克服基于单次访问的权限。
如果android:grantUriPermissions="true",那么权限能够被授予内容提供器范围内的任何数据。如果android:grantUriPermissions="false,那么权限只能授予这个元素指定的数据子集。
android:pathPrefix 属性指定了路径的初始部分,权限能够被授予共享这个初始路径的所有数据子集。
android:path 属性指定了一个完整路径,权限只能被授予这个路径所标识的具体的数据子集。
android:pathPattern 属性定义了一个完整的uri路径,但这个uri中包含了通配符。
<grant-uri-permission android:pathPrefix="/external/" />
//这个元素用于定义内容提供器中的具体的数据子集的路径,及必要的权限。
android:permission 这个属性定义了一个权限名称,为了读写内容提供器的数据,客户端必须要有这个权限。这个属性是给数据设置读写权限的便利方法,但是readPermission和writePermission属性比这个优先级高。
Android:readpermission 读取内容提供器中的数据,客户端必须要有这个权限。
Android:writePermission 修改内容提供器中的数据,客户端必须要有这个权限。
<path-permission
android:pathPrefix="/external/"
android:readPermission="android.permission.READ_EXTERNAL_STORAGE"
android:writePermission="android.permission.WRITE_EXTERNAL_STORAGE" />
</provider>
下面看mediaprovider是如何创建数据库的:
//附加数据库到存储盘符(internal or external)上。
private Uri attachVolume(String volume) @MediaProvider.java{
DatabaseHelper helper = null;
//如果数据库已经存在了,就不需要重复创建了。mDatabases是一个hashmap,统管所有的DatabaseHelper。ensureDefaultFolders这个函数会创建一些默认的文件夹。
helper = mDatabases.get(volume);
if (helper != null) {
if (EXTERNAL_VOLUME.equals(volume)) {
ensureDefaultFolders(helper, helper.getWritableDatabase());
}
return Uri.parse("content://media/" + volume);
//这是内部存储设备,创建的数据库是:INTERNAL_DATABASE_NAME = "internal.db";
if (INTERNAL_VOLUME.equals(volume)) {
helper = new DatabaseHelper(context, INTERNAL_DATABASE_NAME, true,
false, mObjectRemovedCallback);
} else if (EXTERNAL_VOLUME.equals(volume)) {
//获取外部存储卷轴的id,volumeId
final StorageVolume actualVolume = mStorageManager.getPrimaryVolume();
final int volumeId = actualVolume.getFatVolumeId();
//如果外部存储设备还没挂载,会新创建一个名字为external-****.db的数据库,而不是我们期望的数据库名,然后,如果android.process.media进程(mediaprovider所属的进程)被杀掉了或重启了,真正的外部存储设备会被附加上。
String dbName = "external-" + Integer.toHexString(volumeId) + ".db";
helper = new DatabaseHelper(context, dbName, false,
false, mObjectRemovedCallback);
mVolumeId = volumeId;
}
//将刚创建的database添加到hashmap中。
mDatabases.put(volume, helper);
}
具体看下ensureDefaultFolders()函数,主要创建了默认的文件夹,都创建了那些文件夹的呢?
private static final String[] sDefaultFolderNames = {
Environment.DIRECTORY_MUSIC, //Music
Environment.DIRECTORY_PODCASTS, //Podcasts
Environment.DIRECTORY_RINGTONES, //Ringtones
Environment.DIRECTORY_ALARMS, //Alarms
Environment.DIRECTORY_NOTIFICATIONS, //Notifications
Environment.DIRECTORY_PICTURES, //Pictures
Environment.DIRECTORY_MOVIES, //Movies
Environment.DIRECTORY_DOWNLOADS, //DownLoads
Environment.DIRECTORY_DCIM, //Dcim
};
private void ensureDefaultFolders(DatabaseHelper helper, SQLiteDatabase db)
@ MediaProvider.java {
//获取主存储设备。
final StorageVolume vol = mStorageManager.getPrimaryVolume();
final String key;
if (VolumeInfo.ID_EMULATED_INTERNAL.equals(vol.getId())) {
key = "created_default_folders";
} else {
key = "created_default_folders_" + vol.getUuid();
}
final SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(getContext());
if (prefs.getInt(key, 0) == 0) {
for (String folderName : sDefaultFolderNames) {
final File folder = new File(vol.getPathFile(), folderName);
if (!folder.exists()) {
//创建文件夹,并插入到数据库。
folder.mkdirs();
insertDirectory(helper, db, folder.getAbsolutePath());
}
}
}
//这些文件的创建只做一次,如果用户手动删除了,后面也不会在创建。除非恢复出厂值,或者这个模块的缓存清了,SharedPreferences就是控制这个的。
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(key, 1);
editor.commit();
}
分析过数据库的创建,结合query,看下数据库中都提供哪些table。
public Cursor query(Uri uri, String[] projectionIn, String selection, String[] selectionArgs, String
sort)@ MediaProvider.java {
//step1,根据uri匹配table。URI_MATCHER是在mediaprovider创建之初通过addURI添加了匹配选项。
如:URI_MATCHER.addURI("media", "*/images/media", IMAGES_MEDIA);第一个参数是Authority,第二个参数是path,最后一个参数是uri匹配成功时的返回值。
int table = URI_MATCHER.match(uri);
//通过uri来选择相应的数据库。如果之前数据库还没创建,这里就需要真正的去生成这个数据库实例。
DatabaseHelper helper = getDatabaseForUri(uri);
//数据库查询业务构造器,用于构建各种查询参数。
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
//各个类型的表,查询过程类似,这里只列举了一个示例。
switch (table) {
case IMAGES_MEDIA_ID:
//设置要查询的表,通过URI_MATCHER得到的table值并不是数据库里的表名,这里setTables的才是表名。
qb.setTables("images");
//sql查询语句的where。
qb.appendWhere("_id=?");
prependArgs.add(uri.getPathSegments().get(3));
break;
case VIDEO_MEDIA:
qb.setTables("video");
caseIMAGES_MEDIA_ID:
qb.setTables("images");
case AUDIO_MEDIA:
qb.setTables("audio_meta");
case AUDIO_MEDIA_ID:
qb.setTables("audio");
caseAUDIO_MEDIA_ID_GENRES:
qb.setTables("audio_genres");
caseAUDIO_MEDIA_ID_PLAYLISTS:
qb.setTables("audio_playlists");
case AUDIO_GENRES:
qb.setTables("audio_genres");
caseAUDIO_PLAYLISTS:
qb.setTables("audio_playlists");
case AUDIO_ALBUMS:
qb.setTables("audio_meta");
case MEDIA_BOOKMARK:
qb.setTables("bookmarks");
}
//把各种查询条件合并成查询语句,通过qb发出查询请求。
Cursor c = qb.query(db, projectionIn, selection,
ombine(prependArgs, selectionArgs), groupBy, null, sort, limit);
}