分享

Android MediaScanner 详尽分析...

 昵称128678 2011-06-10

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

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 函数:

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

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 ,是在另外一个线程中创建的,往这个handlersendMessage

     // 都会在那个线程里边处理

  // 不明白的可以去查看handlerLooper 机制

// 这里就是同步机制,等待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);

// 不明白的去看看Looperhandler 的实现

        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() {

// 下面这个MediaScannerframework/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 ,然后把javaclient 放到C++Client 中去保存

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多