一、扫描方式
在4.4平台之前可以通过两种方式来扫描多媒体文件:
1、MediaScannerConnection.scanFile()方法,
MediaScannerConnection.scanFile(this,new String[] {Environment.getExternalStorageDirectory().getAbsolutePath()},null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
}
});
2、还有一种是发送广播的方法;
Intent intent =new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile("filePath"));
sendBroadcast(intent);
二、扫描流程
1、接收扫描广播的处理类在多媒体数据库应用MediaProvider工程中的MediaScannerReceiver.java 类中;
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {//开机时候会发送ACTION_BOOT_COMPLETED广播扫描内外卷
scan(context, MediaProvider.INTERNAL_VOLUME);//扫描内部卷
scanUntilAllStorageMounted(context);//扫描外部卷
} else if (action.equals(Intent.ACTION_SHUTDOWN) || action.equals(ACTION_SHUTDOWN_IPO)) {
sIsShutdown = true;
} else if (!sIsShutdown) {
final Uri uri = intent.getData();
if (uri != null && uri.getScheme().equals("file")) {
String path = uri.getPath();
String externalStoragePath = Environment.getExternalStorageDirectory().getPath();
String legacyPath = Environment.getLegacyExternalStorageDirectory().getPath();
try {
path = new File(path).getCanonicalPath();
} catch (IOException e) {
return;
}
if (path.startsWith(legacyPath)) {
path = externalStoragePath + path.substring(legacyPath.length());
}
if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
} else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) && isInScanDirectory(context, path)) {
scanFile(context, path);//扫描具体的文件夹下的所有文件;
} else if (Intent.ACTION_MEDIA_UNMOUNTED.equals(action)) {
}
}
}
2、调用具体扫描动作的时候,开启媒体扫描服务,也在MediaProvider工程中;
private void scan(Context context, String volume) {
Bundle args = new Bundle();
args.putString("volume", volume);
context.startService(
new Intent(context, MediaScannerService.class).putExtras(args));
}
private void scanFile(Context context, String path) {
Bundle args = new Bundle();
args.putString("filepath", path);
context.startService(
new Intent(context, MediaScannerService.class).putExtras(args));
}
3、MediaScannerService逻辑处理;
在MediaScannerService中开启后台线程执行文件扫描动作,在服务创建的时候判断线程池是否可开启线程池扫描,因为多媒体扫描不是线程安全的,如果是在单线程环境下运行不会有问题,但是如果是在多线程环境下运行,如果正在处于扫描文件的过程中又接收到新的文件扫描的请求则需要在扫描完毕之后开启线程池再扫描一次;在扫描开始之前会发送扫描开始的广播Intent.ACTION_MEDIA_SCANNER_STARTED,在扫描完成之后则发送扫描完成的广播Intent.ACTION_MEDIA_SCANNER_FINISHED;
在扫描开始之前有一个预扫描的动作,调用framework层MediaScanner的preScanAll函数来遍历数据库中的每一条数据,检查每一条数据所对应的SD卡文件是否存在,如果相应的文件已经从SD卡删除了,则从数据库中删除此数据;
MediaScanner scanner = createMediaScanner();// 创建MediaScanner对象,调用相应方法扫描文件;
scanner.scanDirectories(directories, volumeName);
4、framework层MediaScanner处理;
在framework的MediaScanner.java类的注释中详细说明了MediaScanner的扫描处理过程;这边有一个点需要注意,如果是父文件夹含有.nomedia文件,则此文件夹下的所有文件和此文件夹下的子文件夹的所有文件都会以一般的文件形式记录保存在数据库中,不会以多媒体文件的形式纪录保存;如果你的应用需要缓存一些多媒体文件在SD卡中,比如头像或者一些视频、音频,而你不想这些文件显示在系统相册里,则在保存这些缓存文件的父文件夹下新建一个.nomedia文件即可;
此处MediaScanner调用Native层的 MediaScannerprocessDirectory方法递归遍历所扫描文件夹下的所有文件;详见MediaScanner.cpp文件;
* In summary:
* Java MediaScannerService calls
* Java MediaScanner scanDirectories, which calls
* Java MediaScanner processDirectory (native method), which calls
* native MediaScanner processDirectory, which calls
* native MyMediaScannerClient scanFile, which calls
* Java MyMediaScannerClient scanFile, which calls
* Java MediaScannerClient doScanFile, which calls
* Java MediaScanner processFile (native method), which calls
* native MediaScanner processFile, which calls
* native parseMP3, parseMP4, parseMidi, parseOgg or parseWMA, which calls
* native MyMediaScanner handleStringTag, which calls
* Java MyMediaScanner handleStringTag.
* Once MediaScanner processFile returns, an entry is inserted in to the database.
*
* The MediaScanner class is not thread-safe, so it should only be used in a single threaded manner.
5、整体框架