MediaStore 媒体文件扫描流程及其媒体文件查询流程

媒体文件扫描流程涉及到的文件路径如下:
/frameworks/base/media/java/android/media/MediaScannerConnection.java
/frameworks/base/media/java/android/media/MediaScanner.java
/frameworks/av/media/libmedia/MediaScanner.cpp
/packages/providers/MediaProvider/src/com/android/providers/media/MediaScannerReceiver.java
/packages/providers/MediaProvider/src/com/android/providers/media/MediaScannerService.java
/packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java

1.按照条件查询媒体文件调用
//执行媒体库查询 图片uri: //media/external/images/media
cursor = mContext.getContentResolver().query(Media.EXTERNAL_CONTENT_URI, mediaColumns,
selection, selectionArgs, MediaStore.Images.ImageColumns.DATE_MODIFIED + " DESC");
调用六 MediaProvider.query
2.扫描单个文件调用
先创建连接然后再扫描文件。
mMediaScannerConnection = new MediaScannerConnection
mMediaScannerConnection.connect()
mMediaScannerConnection.scanFile(fullFilePath, null);
调用一接口

一、扫描单个文件媒体 接口 MediaScannerConnection.java
1.MediaScannerConnection.connect() //执行绑定MediaScannerService
Intent intent = new Intent(IMediaScannerService.class.getName());
intent.setComponent( new ComponentName("com.android.providers.media","com.android.providers.media.MediaScannerService"));
mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
mMediaScannerConnection.scanFile(fullFilePath, null); //执行扫描 文件
2.MediaScannerConnection.scanFile //扫描单个文件
mService.requestScanFile(path, mimeType, mListener) //调用三 MediaScannerService 的mBinder 对象接口
MediaScannerService.java
mBinder.requestScanFile //通过MediaScannerService 的binder对象接口调用,传递filepath, mimetype参数启动服务
startService(new Intent(MediaScannerService.this,MediaScannerService.class).putExtras(args));
调用三
二、媒体扫描广播 MediaScannerReceiver.java
//监听开机,U盘等存储挂载、卸载,扫描单个文件广播,语言切换 ACTION_BOOT_COMPLETED、MEDIA_MOUNTED、MEDIA_UNMOUNTED、MEDIA_SCANNER_SCAN_FILE、LOCALE_CHANGED
//如果是开机广播(参数internal) 、外置存储挂载(参数external)调用scan扫描目录,传递 volume 参数启动MediaScannerService
MediaScannerReceiver.scan(Context context, String volume)
args.putString("volume", volume);
context.startService(new Intent(context, MediaScannerService.class).putExtras(args));
//如果是扫描文件广播调用scanFile扫描文件,传递 filepath 参数启动MediaScannerService
MediaScannerReceiver.scanFile(Context context, String path)
args.putString("filepath", path);
context.startService(new Intent(context, MediaScannerService.class).putExtras(args))
调用三
三、媒体扫描服务 MediaScannerService.java
MediaScannerService.onStartCommand //mServiceHandler.sendMessage(msg) 在消息队列中处理消息
MediaScannerService.ServiceHandler.handleMessage
ServiceHandler.handleMessage
String filePath = arguments.getString("filepath")
String volume = arguments.getString("volume");
1.判断 filePath 不为空就扫描文件,
MediaScannerService.scanFile
//获取文件规范路径
scanner.scanSingleFile(canonicalPath, mimeType);
调用四 MediaScanner.scanSingleFile

2.否则判断 盘符是internal还是external 则扫描存储空间
StorageManager storageManager = (StorageManager)getSystemService(Context.STORAGE_SERVICE);
mExternalStoragePaths = storageManager.getVolumePaths() //外部存储路径数组
//directories为内部存储或者外部存储路径
MediaScannerService.scan(directories, volume);
Uri uri = Uri.parse("file://" + directories[0]);
//发送广播 系统开始扫描sd卡时,一般应用会做等待扫描结束
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri))
//如果是扫描挂载外部存储参数:external,则要先调用openDatabase 创建数据库
openDatabase(volumeName)
values.put("name", volumeName);
getContentResolver().insert(Uri.parse("content://media/"), values);

MediaProvider.java
MediaProvider.insert(
int match = URI_MATCHER.match(uri);//匹配match code为 VOLUMES
Uri newUri = insertInternal(uri, match, initialValues, notifyRowIds);
MediaProvider.insertInternal
DatabaseHelper helper = getDatabaseForUri(uri); //如果是VOLUMES,helper应该为空,只是创建数据库
String name = initialValues.getAsString("name");
Uri attachedVolume = attachVolume(name);
MediaProvider.attachVolume //判断参数是 external,创建数据库
final VolumeInfo vol = mStorageManager.getPrimaryPhysicalVolume()
1.如果vol不为空即使 usb挂载 ,获取主要的外部卷 ID,创建数据库 external-XXXXXXXX.db
String dbName = "external-" + Integer.toHexString(volumeId) + ".db";
helper = new DatabaseHelper(context, dbName, false,false, mObjectRemovedCallback);
2.如果vol为空,则是预置的存储,创建数据库 external.db
File dbFile = context.getDatabasePath(EXTERNAL_DATABASE_NAME)
helper = new DatabaseHelper(context, dbFile.getName(), false,false, mObjectRemovedCallback);
scanner.scanDirectories(directories)
调用四 执行扫描

//发送广播系统结束扫描sd卡,
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));

四、媒体扫描 MediaScanner.java
1.MediaScanner.scanSingleFile //扫描单个文件,获取文件lastModifiedSeconds 信息
mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),false, true, MediaScanner.isNoMediaPath(path))
MediaScanner.MyMediaScannerClient.doScanFile
FileEntry entry = beginFile(path, mimeType, lastModified,fileSize, isDirectory, noMedia)
MediaScanner.beginFile //FileEntry entry = makeEntryFor(path) 根据文件路径获取FileEntry 并根据lastModified 和db对比设置mLastModifiedChanged
MediaScanner.endFile(entry, ringtones, notifications, alarms, music, podcasts)//如果entry 中有db中有记录rowId,则更新扫描文件,如果没有这创建
1.1 如果媒体库没有文件就插入新数据
文件类型获取
//先从文件获取mimeType ,根据mimeType 获取fileType
String drmMimetype = mDrmManagerClient.getOriginalMimeType(path)
DrmManagerClient._getOriginalMimeType //native 方法
android_drm_DrmManagerClient.cpp
android_drm_DrmManagerClient_getOriginalMimeType
String8 mimeType = getDrmManagerClientImpl(env, thiz)->getOriginalMimeType(uniqueId,Utility::getStringValue(env, path), fd)
DrmManagerClientImpl.h getOriginalMimeType
//再从MediaFile mimeType 和 fileType 表中,根据mimeType 获取 fileType,
//ex:FILE_TYPE_JPEG 对应mimetype:image/jpeg
resultFileType = MediaFile.getFileTypeForMimeType(drmMimetype)
根据resultFileType 设置 tableUri ,比如文件类型是图片 则设置 mImagesUri = "content://media/external/images/media"
ContentValues values = toValues(); //将文件路径等存储到values
result = mMediaProvider.insert(tableUri, values); //调用六 插入扫描的文件到媒体库
2.MediaScanner.scanDirectories //扫描挂载存储路径
long start = System.currentTimeMillis();
prescan(null, true); //遍历媒体库文件如果文件不存在就删除该记录
long prescan = System.currentTimeMillis();
for (int i = 0; i < directories.length; i++) { //遍历挂载存储目录
// mClient 为 MyMediaScannerClient implements MediaScannerClient
processDirectory(directories[i], mClient); //processDirectory 是native方法 , 调用五
}

五、媒体扫描 遍历文件 MediaScanner.cpp
/frameworks/av/media/libmedia/MediaScanner.cpp
MediaScanner::processDirectory(
MediaScanResult result = doProcessDirectory(pathBuffer, pathRemaining, client, false)
MediaScanner::doProcessDirectory
循环调用 doProcessDirectoryEntry
MediaScanner::doProcessDirectoryEntry
//如果请求是目录 调用上面的 doProcessDirectory
MediaScanResult result = doProcessDirectory(path, pathRemaining - nameLength - 1,client, childNoMedia);
//如果是文件,调用java接口方法扫描单个文件
status_t status = client.scanFile(path, statbuf.st_mtime, statbuf.st_size,false /*isDirectory*/, noMedia);
六、媒体数据库处理 MediaProvider.java
1.插入数据库 MediaProvider.insert(
int match = URI_MATCHER.match(uri);//例如 匹配match code为 IMAGES_MEDIA
Uri newUri = insertInternal(uri, match, initialValues, notifyRowIds);
MediaProvider.insertInternal //case IMAGES_MEDIA
rowId = insertFile(helper, uri, initialValues, FileColumns.MEDIA_TYPE_IMAGE, true, notifyRowIds)
MediaProvider.insertFile //根据文件获取显示名称等信息,插入files表
rowId = db.insert("files", FileColumns.DATE_MODIFIED, values);
ps:如果传递过来的MEDIA_SCANNER_NEW_OBJECT_ID 不为0 则更新表,只有scanMtpFile 才可能不为0
2.查找数据库MediaProvider.query
public Cursor query(
int table = URI_MATCHER.match(uri); //匹配uri对应的table //如果是IMAGES_MEDIA对应的是images表
switch (table) {
case IMAGES_MEDIA:
qb.setTables("images");
//根据uri获取对应的DatabaseHelper,进而获取对应的db数据库,执行条件查询返回Cursor,
DatabaseHelper helper = getDatabaseForUri(uri);
SQLiteDatabase db = helper.getReadableDatabase();
Cursor c = qb.query(db, projectionIn, selection,combine(prependArgs, selectionArgs), groupBy, null, sort, limit);

监听到U盘卸载后执行
detachVolume这个方法,作用就是分离不生效的卷,可看到执行这个方法后,external+id.db这个database就被分离了,此时生效的是external.db。

posted @ 2023-03-01 15:18  adam.li  阅读(712)  评论(0编辑  收藏  举报