媒体文件扫描
1、扫描的时机
2、扫描的逻辑、添加数据库的流程
一、首先注册BroadcastReceiver,创建MediaScannerReceiver
1)、设置监听action
<receiver android:name="MediaScannerReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<data android:scheme="file" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_UNMOUNTED" />
<data android:scheme="file" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_SCANNER_SCAN_FILE" />
<data android:scheme="file" />
</intent-filter>
</receiver>
2)、在onReceive方法中启动具体的扫描操作
分为开机扫描和非开机扫描
a、开始扫描:内外存储都会被扫描
// Scan both internal and external storage
scan(context, MediaProvider.INTERNAL_VOLUME, null);
b、非开机扫描:
if (uri != null && uri.getScheme().equals("file")) {
// handle intents related to external storage
String path = uri.getPath();
String externalStoragePath = Environment.getExternalStorageDirectory().getPath();
String legacyPath = Environment.getLegacyExternalStorageDirectory().getPath();
try {
path = new File(path).getCanonicalPath();
} catch (Exception e) {
Log.e(TAG, "couldn't canonicalize " + path);
return;
}
if (path.startsWith(legacyPath)) {
path = externalStoragePath + path.substring(legacyPath.length());
}
String sdPath = StorageManagerSmt.getExternalSdcardPath();
Log.v(TAG, "action: " + action + " path: " + path);
if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
// scan whenever any volume is mounted
scan(context, MediaProvider.EXTERNAL_VOLUME, path);
} else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) &&
(path.startsWith(externalStoragePath + "/") ||
// scan doppelganger user path dead code
path.startsWith(DOPPELGANGER_USER_EXTERNAL_STORAGE_PATH + "/") ||
(sdPath != null && (!sdPath.equals("")) && path.startsWith(sdPath + "/")))) {
scanFile(context, path);
}
}
c、扫描操作
private void scan(Context context, String volume, String volumePath) {
Bundle args = new Bundle();
args.putString("volume", volume);
if (volumePath != null) {
args.putString("volumepath", volumePath);
}
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));
}
因为扫描存储设备是耗时操作,所以新启service来处理
二、进入MediaScannerService,进行扫描逻辑的处理
directories为需要扫描的路径集合:
Handler->handleMessage->scan->MediaScanner.scanDirectories(directories, volumeName){
...
makeFileCache
...
processDirectory
...
}
private native void processDirectory(String path, MediaScannerClient client)
进入到native层processDirectory
MediaScaner.cpp(framewoks/av/media/libmedia/MediaScanner.cpp)
MediaScanner::processDirectory-->MediaScanner::doProcessDirectory{
...
DIR* dir = opendir(path);//打开路径
while ((entry = readdir(dir))) {
if \(**doProcessDirectoryEntry**\(path, pathRemaining, client, noMedia, entry, fileSpot\)
== MEDIA\_SCAN\_RESULT\_ERROR\) {
result = MEDIA\_SCAN\_RESULT\_ERROR;
break;
}
}
...
}
-->MediaScanner::doProcessDirectoryEntry{
根据type为 DT_DIR , 或者为 DT_REG 分别进行处理
DT_DIR:
bool childNoMedia = noMedia;
// set noMedia flag on directories with a name that starts with '.'
// for example, the Mac ".Trashes" directory
if (name[0] == '.')
childNoMedia = true;
// report the directory to the client
if (stat(path, &statbuf) == 0) {
status_t status = client.scanFile(path, statbuf.st_mtime, 0,
true /*isDirectory*/, childNoMedia);
if (status) {
return MEDIA_SCAN_RESULT_ERROR;
}
}
// and now process its contents
strcat(fileSpot, "/");
MediaScanResult result = doProcessDirectory(path, pathRemaining - nameLength - 1,
client, childNoMedia);
if (result == MEDIA_SCAN_RESULT_ERROR) {
return MEDIA_SCAN_RESULT_ERROR;
}
DT_REG:
stat(path, &statbuf);
status_t status = client.scanFile(path, statbuf.st_mtime, statbuf.st_size,
false /*isDirectory*/, noMedia);
if (status) {
return MEDIA_SCAN_RESULT_ERROR;
}
(a)、代码中的client.canFile由代码逻辑可知:
client就是MyMediaScannerClient ,即MediaScanner.java中的内部类
MyMediaScannerClient 实现了MediaScannerClient接口
public interface MediaScannerClient
{
public void scanFile(String path, long lastModified, long fileSize,
boolean isDirectory, boolean noMedia);
/**
* Called by native code to return metadata extracted from media files.
*/
public void handleStringTag(String name, String value);
/**
* Called by native code to return mime type extracted from DRM content.
*/
public void setMimeType(String mimeType);
}
所以执行client.canFile就相当于从native层又回调到了java层MyMediaScannerClient的canFile方法
进入doScanFile{
beginFile ->
endFile -> 存入数据库中
}
(b)、doProcessDirectory 继续处理
}