10.2 android.process.media分析
多媒体系统的媒体扫描功能,是通过一个APK应用程序提供的,它位于package/providers/MediaProvider目录下。通过分析APK的Android.mk文件可知,该APK运行时指定了一个进程名,如下所示:
application android:process=android.process.media
原来,通过ps命令经常看到的进程就是它啊!另外,从这个APK程序所处的package\providers目录也可以知道,它还是一个ContentProvider。事实上从Android应用程序的四大组件来看,它使用了其中的三个组件:
MediaScannerService(从Service派生)模块负责扫描媒体文件,然后将扫描得到的信息插入到媒体数据库中。
MediaProvider(从ContentProvider派生)模块负责处理针对这些媒体文件的数据库操作请求,例如查询、删除、更新等。
MediaScannerReceiver(从BroadcastReceiver派生)模块负责接收外界发来的扫描请求。也就是MS对外提供的接口。
注意 除了支持通过广播发送扫描请求外,MediaScannerService也支持利用Binder机制跨进程调用扫描函数。这部分内容将在本章的拓展内容中介绍。
本章仅关注android.process.media进程中的MediaScannerService和MediaScannerReceiver模块,为了书写方便,下文将这两个模块简称为MSS和MSR,另外将MediaScanner简称下面,开始分析android.process.media中和媒体文件扫描相关的工作流程。
10.2.1 MSR模块分析
MSR模块的核心类MediaScannerReceiver从BroadcastReceiver派生,它是专门用来接收广播的,那么它感兴趣的广播有哪几种呢?其代码如下所示:
[—>MediaScannerReceiver.java]
public class MediaScannerReceiver extends BroadcastReceiver
{
private final static String TAG="MediaScannerReceiver";
@Override//MSR在onReceive函数中处理广播。
public void onReceive(Context context,Intent intent){
String action=intent.getAction();
Uri uri=intent.getData();
//一般手机外部存储的路径是/mnt/sdcard。
String externalStoragePath=
Environment.getExternalStorageDirectory().getPath();
//为了简化书写,所有Intent的ACTION_XXX_YYY字串都会简写为XXX_YYY。
if(action.equals(Intent.ACTION_BOOT_COMPLETED)){
//如果收到BOOT_COMPLETED广播,则启动内部存储区的扫描工作,内部存储区
//实际上扫描的是/system/media目录,这里存储了系统自带的铃声等媒体文件。
scan(context,MediaProvider.INTERNAL_VOLUME);
}else{
if(uri.getScheme().equals("file")){
String path=uri.getPath();
/*
注意下面这个判断,如果收到MEDIA_MOUNTED消息,并且外部存储挂载的路径和“/mnt/sdcard”一样,则启动外部存储也就是SD卡的扫描工作。
*/
if(action.equals(Intent.ACTION_MEDIA_MOUNTED)&&
externalStoragePath.equals(path)){
scan(context,MediaProvider.EXTERNAL_VOLUME);
}else if(action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
&&path!=null
&&path.startsWith(externalStoragePath+"/")){
/*
外部应用可以发送MEDIA_SCANNER_SCAN_FILE广播让MSR启动单个文件的扫描工作。注意这个文件必须位于SD卡上。
*/
scanFile(context,path);
}
}
}
}
从上面的代码中发现了MSR接收的三种请求,也就是说,它对外提供三个接口函数:
接收BOOT_COMPLETED请求,这样MSR会启动内部存储区的扫描工作,注意这个内部存储区实际上是/system/media这个目录。
接收MEDIA_MOUNTED请求,并且该请求携带的外部存储挂载点路径必须是/mnt/sdcard,通过这种方式MSR会启动外部存储区也就是SD卡的扫描工作,扫描目标是文件夹/mnt/sdcard。
接收MEDIA_SCANNER_SCAN_FILE请求,并且该请求必须是SD卡上的一个文件,即文件路径须以/mnt/sdcard开头,这样,MSR会启动针对这个文件的扫描工作。
不知读者是否注意到,MSR和跨Binder调用的接口(在本章拓展内容中将介绍)都不支持对目录的扫描(除了SD卡的根目录外)。实现这个功能并不复杂,有兴趣的读者可自行完成该功能,如果方便,请将自己实现的代码与大家共享。
大部分的媒体文件都已放在SD卡上了,那么来看收到MEDIA_MOUNTED请求后MSR的工作。还记得第9章中对Vold的分析吗?这个MEDIA_MOUNTED广播就是由MountService发送的,一旦有SD卡被挂载,MSR就会被这个广播唤醒,接着SD卡的媒体文件就会被扫描了。真是一气呵成!
SD卡根目录扫描时调用的函数scan的代码如下:
[—>MediaScannerReceiver.java]
private void scan(Context context,String volume){
//volume的值为/mnt/sdcard。
Bundle args=new Bundle();
args.putString("volume",volume);
//启动MSS。
context.startService(
new Intent(context,MediaScannerService.class).putExtras(args));
}
scan将启动MSS服务。下面来看MSS的工作。