MediaScanner 详解

来源:互联网 发布:如何查淘宝宝贝权重 编辑:程序博客网 时间:2024/06/05 15:52

多媒体扫描是从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派生下来的一个东西,里边保存了一个文件的一些信息。后续再分析。


0 0
原创粉丝点击