1.概述
多媒体系统,是Android平台中非常庞大的一个系统.MediaScanner是多媒体系统中重要的一员, MediaScanner和媒体文件扫描有关,例如,在music应用程序中见到的歌曲专辑名、歌曲时长等信息, 都是通过它扫描对应的歌曲而得到。
2. android.process.media分析
媒体扫描功能是通过一个APK实现的,它位于packages/providers/MediaProvider目录下,通过APK的AndroidManifest.xml文件可知,该APK运行时定义了一个进程名,如下:
application android:process="android.process.media"
从Android应用程序的四大组件来看,它使用了三个组件:
- MediaScannerService(从Service派生)模块负责扫描媒体文件,然后将扫描的信息插入数据库。
- MediaProvider(从ContentProvider派生)模块负责处理针对这些媒体文件的数据库操作请求,例如查询、删除、更新等
- MediaScannerReceiver(从BroadcastReceiver派生)模块负责接收外界发来的扫描请求,是MS对外的接口。
3. MSR模块分析
MSR的核心是MediaScannerReceiver,从BroadcastReceiver派生,专门用于接收广播,其代码如下:
public class MediaScannerReceiver extends BroadcastReceiver {
private final static String TAG = "MediaScannerReceiver";
@Override
// 在onReceive函数中处理广播
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
final Uri uri = intent.getData();
//如果收到BOOT_COMPLETED广播,内部启动扫描,实际上扫描的是/system/media目录
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
Log.d(TAG, "receive ACTION_BOOT_COMPLETED");
scan(context, MediaProvider.INTERNAL_VOLUME);
scan(context, MediaProvider.EXTERNAL_VOLUME);
} else {
if (uri.getScheme().equals("file")) {
// handle intents related to external storage
String path = uri.getPath();
//下面四个分别是内部存储、USB1、USB2、sdcard
String externalStoragePath = Environment.getExternalStorageDirectory().getPath();
String externalStorageUdiskPath = Environment.getExternalStorageUdiskDirectory().getPath();
String externalStorageUdisk1Path = Environment.getExternalStorageUdisk1Directory().getPath();
String externalSdcard1Path = Environment.getExternalStorageSdcard1Directory().getPath();
String legacyPath = Environment.getLegacyExternalStorageDirectory().getPath();
try {
path = new File(path).getCanonicalPath();
} catch (IOException e) {
Log.e(TAG, "couldn't canonicalize " + path);
return;
}
if (path.startsWith(legacyPath)) {
path = externalStoragePath + path.substring(legacyPath.length());
}
Log.d(TAG, "action: " + action + " path: " + path);
//如果收到MEDIA_MOUNTED消息,则开始判断外接的是两个USB口和sd卡哪个,然后开始扫描
if (Intent.ACTION_MEDIA_MOUNTED.equals(action) ||
Intent.ACTION_SCAN_SPECIFY_PATH.equals(action)) {
if (path.equals(externalStoragePath)) {
scan(context, MediaProvider.EXTERNAL_VOLUME);
} else if (path.equals(externalStorageUdiskPath)) {
scan(context, MediaProvider.EXTERNAL_UDISK_VOLUME);
} else if (path.equals(externalStorageUdisk1Path)) {
scan(context, MediaProvider.EXTERNAL_UDISK1_VOLUME);
} else if (path.equals(externalSdcard1Path)) {
scan(context, MediaProvider.EXTERNAL_SDCARD1_VOLUME);
} else {
Log.d(TAG, "unknow path : " + path);
}
}
// 外部应用可以发送 MEDIA_SCANNER_SCAN_FILE消息让MSR启动单个文件的扫描工作,这个文件必须在外接的USB或则sdcard上。
else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) &&
path != null && (path.startsWith(externalStoragePath + "/") ||
path.startsWith(externalStorageUdisk1Path + "/") ||
path.startsWith(externalStorageUdiskPath + "/") ||
path.startsWith(externalSdcard1Path + "/"))) {
scanFile(context, path);
} else {
Log.d(TAG, "unknown intent action :" + action);
}
}
}
}
}
从上面代码中发现了MSR接收三种请求,也就是说,它对外提供三个接口:
- 接收BOOT_COMPLETED请求,MSR开始启动内部存储的扫描,这个内部存储是/system/media这个目录
- 接收MEDIA_MOUNTED和SCAN_SPECIFY_PATH请求,且请求的是外部存储挂载,/mnt/udisk、/mnt/udisk1、/mnt/sdcard1,通过这个方式,MSR会启动外部存储区的扫描。
- 接收MEDIA_SCANNER_SCAN_FILE请求,并且该请求必须是外部存储区的一个文件。
MEDIA_MOUNTED这个广播是由MountService发送的,一旦有SD卡或者U盘被挂载,MSR就会被这个广播唤醒,接着媒体文件就会被扫描了。
扫描的调用函数scan代码如下:
private void scan(Context context, String volume) {
Bundle args = new Bundle();
args.putString("volume", volume);
context.startService(
new Intent(context, MediaScannerService.class).putExtras(args));
}
scan将启动MSS服务。下面来看下MSS的工作。
4. MSS模块分析
MSS从Service派生,并且实现了Runnable接口。下面是定义:
public class MediaScannerService extends Service implements Runnable
//MSS实现了Runnable接口,这表明它可能会创建工作线程
根据SDK中对Service的生命周期的描述可知,Service刚创建时会调用onCreate函数,接着是onStartCommand函数,之后每次外界调用一次startService都会触发onStartCommand函数。接下来就看下onCreate函数和onStartCommand函数。
4.1. onCreate分析
onCreate函数的代码如下所示,这是MSS被创建时调用的,在它的生命周期内仅被调用一次:
public void onCreate() {
// 获得电源锁,防止在扫描过程中休眠
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
//扫描是一个很漫长的过程,所以单独创建一个工作线程
Thread thr = new Thread(null, this, "MediaScannerService");
thr.start();
}
onCreate创建了一个工作线程,然后看下这个线程里做了什么呢,代码如下:
public void run() {
// 设置本线程的优先级,需要将优先级调低,因为媒体扫描可能耗费很长的时间,如果不调低优先级