多媒体扫描是从MediaScannerService开始的。这是一个单独的package。位于
packages/providers/MediaProvider:含以下java文件
l MediaProvider.java
l MediaScannerReceiver.java
l MediaScannerService.java
l MediaThumbRequest.java
分析这个目录的Android.mk文件,发现它运行的进程名字就是android.process.media。
application android:process=android.process.media
1.1 MediaScannerReceiver
这个类从BroadcastReceiver中派生,用来接收任务的。
MediaScannerReceiver extends BroadcastReceiver
在它重载的onRecieve函数内有以下几种走向:
if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
// 收到”启动完毕“广播后,扫描内部存储
scan(context, MediaProvider.INTERNAL_VOLUME);
} else {
……….
if (action.equals(Intent.ACTION_MEDIA_MOUNTED) &&
externalStoragePath.equals(path)) {
/收到MOUNT信息后,扫描外部存储
scan(context, MediaProvider.EXTERNAL_VOLUME);
}
else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) &&
path != null && path.startsWith(externalStoragePath + "/")) {
//收到请求要求扫描某个文件,注意不会扫描内部存储上的文件
scanFile(context, path);
…………………………..
}
……下面是它调用的scan函数:启动MediaScannerService 并附带各种参数
scan(Context context, String volume)
Bundle args = new Bundle();
args.putString("volume", volume);
//直接启动MediaScannerService了,
context.startService(
new Intent(context, MediaScannerService.class).putExtras(args));
MediaStore.getMediaScannerUri() 用于查询mediascanner状态的uri。
MediaStore.MEDIA_SCANNER_VOLUME 表示要返回的信息是每个盘
final Cursor cursor = cr.query(MediaStore.getMediaScannerUri(), new String[] {
MediaStore.MEDIA_SCANNER_VOLUME }, null,
null, null);
if (cursor != null)
{
if (cursor.getCount() == 1)
{
cursor.moveToFirst();
result = "external".equals(cursor.getString(0)) ||
"internal".equals(cursor.getString(0));
}
cursor.close();
}
MediaScannerService :
MSS标准的从Service中派生下来,
MediaScannerService extends Service implements Runnable
//注意:是一个Runnable…,可能有线程之类的东西存在
下面从Service的生命周期的角度来看看它的工作。
1. onCreate
public void onCreate()
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
//获得电源锁,防止在扫描过程中休眠
//单独搞一个线程去跑扫描工作,防止ANR
Thread thr = new Thread(null, this, "MediaScannerService");
thr.start();
看看run做了什么。
public void run()
{
// reduce priority below other background threads to avoid interfering
// with other services at boot time.
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +
Process.THREAD_PRIORITY_LESS_FAVORABLE);
//不明白的去看看Looper和handler的实现
Looper.prepare();//把这个looper对象设置到线程本地存储
mServiceLooper = Looper.myLooper();
mServiceHandler = new ServiceHandler();//创建handler,默认会把这个looper
//的消息队列赋值给handler的消息队列,这样往handler中发送消息就是往这个线程的looper发
Looper.loop();//消息循环,内部会处理消息队列中的消息
//也就是handleMessage函数
}
2. onStartCommand
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
//注意这个handler,是在另外一个线程中创建的,往这个handler里sendMessage
//都会在那个线程里边处理
//不明白的可以去查看handler和Looper机制
//这里就是线程同步机制,等待mServiceHandler在另外那个线程创建完毕
while (mServiceHandler == null) {
synchronized (this) {
try {
wait(100);
} catch (InterruptedException e) {
}
}
}
if (intent == null) {
Log.e(TAG, "Intent is null in onStartCommand: ",
new NullPointerException());
return Service.START_NOT_STICKY;
}
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent.getExtras();
//把MediaScannerReceiver发出的消息传递到另外那个线程去处理。启动本service即能够把消息发到mServiceHandler 处理。
mServiceHandler.sendMessage(msg);
………….
基本上MSR(MediaScannerReceiver)发出的请求都会传到onStartCommand中处理。如果有多个存储的话,
也只能一个一个扫描了。而 mServiceHandler.sendMessage(msg); 这句话表示处理在 mServiceHandler
的类中,
4. handleMessage
private final class ServiceHandler extends Handler
{
@Override
public void handleMessage(Message msg)
{
Bundle arguments = (Bundle) msg.obj;
String filePath = arguments.getString("filepath");
try {
if (filePath != null) {
IBinder binder = arguments.getIBinder("listener");
IMediaScannerListener listener =
(binder == null ? null : IMediaScannerListener.Stub.asInterface
(binder));
Uri uri = null;
try {
//根据路径和minetype去创建MediaScanner类
uri = scanFile(filePath, arguments.getString("mimetype"));
} catch (Exception e) {
Log.e(TAG, "Exception scanning file", e);
}
if (listener != null) {//扫描完成的监听处理
listener.scanCompleted(filePath, uri);
}
}
else {
String volume = arguments.getString("volume");
String[] directories = null;
if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {
//是扫描内部存储的请求?
// scan internal media storage
directories = new String[] {
Environment.getRootDirectory() + "/media",
};
}
else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {
//是扫描外部存储的请求?获取外部存储的路径
directories = new String[] {
Environment.getExternalStorageDirectory().getPath(),
};
}
if (directories != null) {
//真正的扫描开始了,上面只不过是把存储路径取出来罢了.
scan(directories, volume);
…..
//扫描完了,就把service停止了
stopSelf(msg.arg1);
}
};
private Uri scanFile(String path, String mimeType) {
String volumeName = MediaProvider.EXTERNAL_VOLUME;
将外部存储设备的名字和deviceID插入到content://media/none/mounted 即改变设备的挂载状态
openDatabase(volumeName, null);
/*MediaScanner*/ scanner = createMediaScanner();
return scanner.scanSingleFile(path, volumeName, mimeType);
}
5. scan函数
private void scan(String[] directories, String volumeName) {
mWakeLock.acquire();
//下面这三句话很深奥…
//从 getContentResolver获得一个ContentResover,然后直接插入
//根据AIDL,这个ContentResover的另一端是MediaProvider。只要去看看它的
//insert函数就可以了
//反正这里知道获得了一个扫描URI即可。
ContentValues values = new ContentValues();
values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);
Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);
Uri uri = Uri.parse("file://" + directories[0]);
//发送广播,通知扫描开始了
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));
说说上面那个深奥的地方,在MediaProvider中重载了insert函数,insert函数会调用insertInternal函数。
如下:
private Uri insertInternal(Uri uri, ContentValues initialValues) {
long rowId;
int match = URI_MATCHER.match(uri);
// handle MEDIA_SCANNER before calling getDatabaseForUri()
//刚才那个insert只会走下面这个分支,其实就是获得一个地址….
//太绕了!!!!!
if (match == MEDIA_SCANNER) {
mMediaScannerVolume = initialValues.getAsString(MediaStore.MEDIA_SCANNER_VOLUME);
return MediaStore.getMediaScannerUri();
}
……..
二 MediaScanner
MediaScanner位置在
frameworks/base/media/下,包括jni和java文件。
先看看java实现。
这个类巨复杂,而且和MediaProvider交互频繁。在分析的时候要时刻回到MediaProvider去看看。
1. 初始化
public class MediaScanner
{
static {
//libmedia_jni.so的加载是在MediaScanner类中完成的
//这么重要的so为何放在如此不起眼的地方加载???
System.loadLibrary("media_jni");
native_init();
}
public MediaScanner(Context c) {
native_setup();//调用jni层的初始化,暂时不用看了,无非就是一些
//初始化工作,待会在再进去看看
……..
}
刚才MSS中是调用scanDirectories函数,我们看看这个。
2. scanDirectories
public void scanDirectories(String[] directories, String volumeName) {
try {
long start = System.currentTimeMillis();
initialize(volumeName);//初始化
prescan(null);//扫描前的预处理
long prescan = System.currentTimeMillis();
for (int i = 0; i < directories.length; i++) {
//扫描文件夹,这里有一个很重要的参数 mClient
// processDirectory是一个native函数
processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
}
long scan = System.currentTimeMillis();
postscan(directories);//扫描后处理
long end = System.currentTimeMillis();
…..打印时间,异常处理…没了…
下面简单讲讲initialize ,preScan和postScan都干嘛了。
private void initialize(String volumeName) {
//打开MediaProvider,获得它的一个实例
mMediaProvider = mContext.getContentResolver().acquireProvider("media");
//得到一些uri
mAudioUri = Audio.Media.getContentUri(volumeName);
mVideoUri = Video.Media.getContentUri(volumeName);
mImagesUri = Images.Media.getContentUri(volumeName);
mThumbsUri = Images.Thumbnails.getContentUri(volumeName);
//外部存储的话,可以支持播放列表之类的东西,搞了一些个缓存池之类的
//如mGenreCache等
if (!volumeName.equals("internal")) {
// we only support playlists on external media
mProcessPlaylists = true;
mGenreCache = new HashMap<String, Uri>();
…
preScan,这个函数很复杂:
大概就是创建一个FileCache,用来缓存扫描文件的一些信息,例如last_modified等。这个FileCache是从MediaProvider中已有信息构建出来的,也就是历史信息。后面根据扫描得到的新信息来对应更新历史信息。
postScan,这个函数做一些清除工作,例如以前有video生成了一些缩略图,现在video文件被干掉了,则对应的缩略图也要被干掉。
另外还有一个mClient,这个是从MediaScannerClient派生下来的一个东西,里边保存了一个文件的一些信息。后续再分析。