http://www.apkbus.com/android-5376-1-1.html
MediaScanner分析
一 MediaScannerService
多媒体扫描是从MediaScannerService开始的。这是一个单独的package。位于packagesprovidersMediaProvider:含以下java文件
java代码:
- MediaProvider.java
- MediaScannerReceiver.java
- MediaScannerService.java
- MediaThumbRequest.java
分析这个目录的Android.mk文件,发现它运行的进程名字就是android.process.media。
application android:process=android.process.media
1.1 MediaScannerReceiver
这个类从BroadcastReceiver中派生,用来接收任务的。MediaScannerReceiver extends BroadcastReceiver
在它重载的onRecieve函数内有以下几种走向:
java代码:
- 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函数:
java代码:
- 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.
1.2 MediaScannerService
MSS标准的从Service中派生下来,
MediaScannerService extends Service implements Runnable
//注意:是一个Runnable…,可能有线程之类的东西存在
下面从Service的生命周期的角度来看看它的工作。
1. onCreate
java代码:
- 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
java代码:
- @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);
3. run
java代码:
- 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
java代码:
- 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函数
java代码:
- 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函数。
如下:
java代码:
- 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
java代码:
- 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的工作流程如下:
1 单独启动一个带消息循环的工作线程。
2 主线程接收系统发来的任务,然后发送给工作线程去处理。
3 工作线程接收任务,创建一个MediaScanner去扫描。
4 MSS顺带广播一下扫描工作启动了,扫描工作完毕了。
二 MediaScanner
MediaScanner位置在
frameworks asemedia下,包括jni和java文件。
先看看java实现。
这个类巨复杂,而且和MediaProvider交互频繁。在分析的时候要时刻回到MediaProvider去看看。
1. 初始化
java代码:
- 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
java代码:
- 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();
2. scanDirectories
java代码:
- 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")) {
- mProcessPlaylists = true;
- mGenreCache = new HashMap();
preScan,这个函数很复杂:
大概就是创建一个FileCache,用来缓存扫描文件的一些信息,例如last_modified等。这个FileCache是从MediaProvider中已有信息构建出来的,也就是历史信息。后面根据扫描得到的新信息来对应更新历史信息。
postScan,这个函数做一些清除工作,例如以前有video生成了一些缩略图,现在video文件被干掉了,则对应的缩略图也要被干掉。
另外还有一个mClient,这个是从MediaScannerClient派生下来的一个东西,里边保存了一个文件的一些信息。后续再分析。
在frameworks asemediajniandroid_media_MediaScanner.cpp中。
刚才说到,具体扫描工作是在processDirectory函数中完成的。这个是一个native函数。
三 MediaScanner JNI层分析
MediaScanner JNI层内容比较多,单独搞一节分析吧。先看看android_media_MediaScanner这个文件。
1. native_init函数,jni对应的函数如下
java代码:
- 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的自己去学习下吧
- }
2. native_setup函数,jni对应函数如下:
java代码:
- 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();
3. processDirectories函数,jni对应如下:
java代码:
- 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中去保存
- //而且还是栈上的临时变量..
- MyMediaScannerClient myClient(env, client);
- //scanner扫描文件夹,用得是C++的client
- mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env);
- env->ReleaseStringUTFChars(path, pathStr);
- env->ReleaseStringUTFChars(extensions, extensionsStr);
- }
到这里似乎就没有了,那么扫描后的数据库是怎么更新的呢?为什么要传入一个client进去呢?看来必须得trace到scanner中去才知道了
四 PVMediaScanner
这个在externalopencoreandroidmediascanner.cpp中。
1. processDirectory
java代码:
- status_t MediaScanner::processDirectory(const char *path, const char* extensions,
- MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv)
- {
- InitializeForThread();
- int error = 0;
- status_t result = PVMFSuccess;
- //调用client的设置区域函数
- client.setLocale(mLocale);
- //扫描文件夹,咋还没开始??
- result = doProcessDirectory(pathBuffer, pathRemaining, extensions, client, exceptionCheck, exceptionEnv);
2. doProcessDirectory
java代码:
- status_t MediaScanner::doProcessDirectory(char *path, int pathRemaining, const char* extensions,
- MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv)
- {
- //终于看到点希望了
- //打开这个文件夹,枚举其中的内容。
- //题外话,这个时候FileManager肯定删不掉这个文件夹!!
- DIR* dir = opendir(path);
- while ((entry = readdir(dir))) {
- const char* name = entry->d_name;
- //不处理.和..文件夹
- if (isDirectory) {
- //不处理.开头的文件夹。如果是文件夹,递归调用doProcessDirectory
- //深度优先啊!
- int err = doProcessDirectory(path, pathRemaining - nameLength - 1, extensions, client, exceptionCheck, exceptionEnv);
- if (err) {
- LOGE("Error processing '%s' - skipping ", path);
- continue;
- }
- } else if (fileMatchesExtension(path, extensions)) {
- //是一个可以处理的文件,交给client处理
- //彻底疯掉了….这是干嘛呢???
- client.scanFile(path, statbuf.st_mtime, statbuf.st_size);
这里要解释下,刚才createMediaScanner中,明明创建的是PVMediaScanner,为何这里看得是MediaScanner代码呢?
因为PVMediaScanner从MediaScanner中派生下来的,而且没有重载processDirectory函数Eclaire没有PVMediaScanner这样的东西,估计是froyo又改了点啥吧。
FT,processDirctory无非是列举一个目录内容,然后又反回去调用client的scanFile处理了。为何搞这么复杂?只有回去看看C++的client干什么了。
MediaScannerClient---JNI层
JNI中的这个类是这样的:
java代码:
- class MyMediaScannerClient : public MediaScannerClient
- //这是它的scanFile实现
- virtual bool scanFile(const char* path, long long lastModified, long long fileSize){
- //再次崩溃了,C++的client调用了刚才传进去的java Client的
- //scanFile函数…不过这次还传进去了该文件的路径,最后修改时间和文件大小。
- mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);
- //想死,,,
- }
没办法了,只能再去看看MediaScanner.java传进去的那个client了。
MediaScannerClient----JAVA层
这个类在MediaScanner.java中实现。
java代码:
- private class MyMediaScannerClient implements MediaScannerClient:
- public void scanFile(String path, long lastModified, long fileSize) {
- //调用doScanFile..很烦..
- doScanFile(path, null, lastModified, fileSize, false);
- //下面是doScanFile
- public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean scanAlways) {
- //预处理,看看之前创建的文件缓存中有没有这个文件
- FileCacheEntry entry = beginFile(path, mimeType, lastModified, fileSize);
- // rescan for metadata if file was modified since last scan
- if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
- //如果事先有这个文件的信息,则需要修改一些信息,如长度,最后修改时间等
- //真正的扫描文件
- processFile(path, mimeType, this);
- //扫描完了,做最后处理
- endFile(entry, ringtones, notifications, alarms, music, podcasts);
- //processFile又是jni层的。
- //对应android_media_MediaScanner_processFile函数
- android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client){
- MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);
- //无语了,又搞一个 MyMediaScannerClient
- MyMediaScannerClient myClient(env, client);
- mp->processFile(pathStr, mimeTypeStr, myClient);
- }