MediaScanner 分析
一 MediaScannerService
多媒体扫描是从 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
这个类从 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 函数:
scan(Context context, String volume)
Bundle args = new Bundle();
args.putString("volume", volume);
// 直接启动MediaScannerService 了,
context.startService(
new Intent(context, MediaScannerService.class).putExtras(args));
总结:
MediaScannerReceiver 是用来接收任务的,它收到广播后,会启动 MediaService 进行扫描工作。
下面看看 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();
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 发出的消息传递到另外那个线程去处理。
mServiceHandler.sendMessage(msg);
………….
基本上 MSR(MediaScannerReceiver) 发出的请求都会传到 onStartCommand 中处理。如果有多个存储的话,也只能一个一个扫描了。
下面看看那个线程的主函数
3. 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 函数
}
上面 handler 中加入了一个扫描请求(假设是外部存储的),所以要分析 handleMessage 函数。
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 {
……… 这里不讲了
} 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);
}
};
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));
try {
if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {
openDatabase(volumeName);
}
// 创建真正的扫描器
MediaScanner scanner = createMediaScanner();
// 交给扫描器去扫描文件夹 scanDirectories
scanner.scanDirectories(directories, volumeName);
} catch (Exception e) {
Log.e(TAG, "exception in MediaScanner.scan()", e);
}
// 删除扫描路径
getContentResolver().delete(scanUri, null, null);
// 通知扫描完毕
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));
mWakeLock.release();
}
说说上面那个深奥的地方,在 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();
}
……..
再看看它创建了什么样的 Scanner ,这就是 MSS 中的 createMediaScanner
private MediaScanner createMediaScanner() {
// 下面这个MediaScanner 在framework/base/ 中,待会再分析
MediaScanner scanner = new MediaScanner(this);
// 设置当前的区域,这个和字符编码有重大关系。
Locale locale = getResources().getConfiguration().locale;
if (locale != null) {
String language = locale.getLanguage();
String country = locale.getCountry();
String localeString = null;
if (language != null) {
if (country != null) {
// 给扫描器设置当前国家和语言。
scanner.setLocale(language + "_" + country);
} else {
scanner.setLocale(language);
}
}
}
return scanner;
}
至此, MSS 的任务完成了。接下来是 MediaScanner 的工作了。
6. 总结
MSS 的工作流程如下:
l 1 单独启动一个带消息循环的工作线程。
l 2 主线程接收系统发来的任务,然后发送给工作线程去处理。
l 3 工作线程接收任务,创建一个 MediaScanner 去扫描。
l 4 MSS 顺带广播一下扫描工作启动了,扫描工作完毕了。
二 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 派生下来的一个东西,里边保存了一个文件的一些信息。后续再分析。
刚才说到,具体扫描工作是在 processDirectory 函数中完成的。这个是一个 native 函数。
在 frameworks\base\media\jni\android_media_MediaScanner.cpp 中。
三 MediaScanner JNI 层分析
MediaScanner JNI 层内容比较多,单独搞一节分析吧。
先看看 android_media_MediaScanner 这个文件。
1. native_init 函数, jni 对应的函数如下
static void
android_media_MediaScanner_native_init(JNIEnv *env)
{
jclass clazz;
clazz = env->FindClass("android/media/MediaScanner");
// 得都JAVA 类中mNativeContext 这个成员id
fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
// 不熟悉JNI 的自己去学习下吧
}
3. native_setup 函数, jni 对应函数如下:
android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz)
{
// 创建MediaScanner 对象
MediaScanner *mp = createMediaScanner();
// 太变态了,自己不保存这个对象指针.
// 却把它设置到java 对象的mNativeContext 去保存
env->SetIntField(thiz, fields.context, (int)mp);
}
// 创建MediaScanner 函数
static MediaScanner *createMediaScanner() {
#if BUILD_WITH_FULL_STAGEFRIGHT
..
// 使用google 自己的
return new StagefrightMediaScanner;
#endif
#ifndef NO_OPENCORE
// 使用opencore 提供的
….
return new PVMediaScanner();
#endif
4. processDirectories 函数, jni 对应如下:
android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client)
{
MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);
// 每次都要回调到JAVA 中去取这个Scanner !!
………
const char *pathStr = env->GetStringUTFChars(path, NULL);
const char *extensionsStr = env->GetStringUTFChars(extensions, NULL);
…….
// 又在C++ 这里搞一个client ,然后把java 的client 放到C++Client 中去保存
<>
联系客服