Android N 开机扫描媒体文件流程

2017-10-26  本文已影响0人  xlq

一. 首先需要了解系统扫描媒体文件的目的是什么?
系统的资源文件,被放置在系统特定的目录,需要通过扫描,将资源文件保存在系统数据库中,这样才能方便资源文件被调用。

二. 扫描流程

  1. 流程图:
开机扫描媒体文件流程图.jpg
  1. 首先看这个文件MediaScannerReceiver.java,该文件注册了一个广播接收器,用于接收系统的开机广播,当接收到开机广播后,调用scan()方法:
    private void scan(Context context, String volume) {
        Bundle args = new Bundle();
        args.putString("volume", volume);
        context.startService( new Intent(context, MediaScannerService.class)
                   .putExtras(args));

该方法主要是启动 MediaScannerService.java
先看MediaScannerService的onCreate()方法:

    @Override
        public void onCreate() {
            PowerManager pm =  (PowerManager)getSystemService(Context.POWER_SERVICE);
           mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
             StorageManager storageManager = (StorageManager)getSystemService(Context.STORAGE_SERVICE);
            mExternalStoragePaths = storageManager.getVolumePaths();

        // Start up the thread running the service.  Note that we create a
        // separate thread because the service normally runs in the process's
        // main thread, which we don't want to block.
        Thread thr = new Thread(null, this, "MediaScannerService");
        thr.start();
    }

前面是对各种manager的创建,最后两句启动线程,调用到run()方法,run()方法内开启消息队列,创建mServiceHandler消息处理器。
这是对onCreate的解读,紧接着是onStartCommand()方法。只贴关键代码:

Message msg = mServiceHandler.obtainMessage();  
msg.arg1 = startId;  
msg.obj = intent.getExtras();  
mServiceHandler.sendMessage(msg);

将MediaScannerRecevier传入的参数封装到msg,并发送到线程让mServiceHandler进行处理:(代码太长,只贴关键代码)

if (directories != null) {
                        if (false) Log.d(TAG, "start scanning volume " + volume + ": "
                                + Arrays.toString(directories));
                        scan(directories, volume);
                        if (false) Log.d(TAG, "done scanning volume " + volume);
                    }

handler前面的代码是判断扫描指定路径还是扫描全盘,此处是调用scan()方法。

private void scan(String[] directories, String volumeName) {
        Uri uri = Uri.parse("file://" + directories[0]);
        // don't sleep while scanning
        mWakeLock.acquire();

        try {
            ContentValues values = new ContentValues();
            values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);
            Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);

            sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));

            try {
                if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {
                    openDatabase(volumeName);
                }

                try (MediaScanner scanner = new MediaScanner(this, volumeName)) {
                    scanner.scanDirectories(directories);
                }
            } catch (Exception e) {
                Log.e(TAG, "exception in MediaScanner.scan()", e);
            }

            getContentResolver().delete(scanUri, null, null);

        } finally {
            sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));
            mWakeLock.release();
        }
    }

关键代码在

 try (MediaScanner scanner = new MediaScanner(this, volumeName)) {
                    scanner.scanDirectories(directories);
                }

先初始化一个MediaScanner对象,并调用scanDirecturies()方法。先看MediaScanner的构造方法。

public MediaScanner(Context c, String volumeName) {
        native_setup();
        mContext = c;
        mPackageName = c.getPackageName();
        mVolumeName = volumeName;

        mBitmapOptions.inSampleSize = 1;
        mBitmapOptions.inJustDecodeBounds = true;

        setDefaultRingtoneFileNames();//设置默认铃声名
        //初始化MediaProvider
        mMediaProvider = mContext.getContentResolver()
                .acquireContentProviderClient(MediaStore.AUTHORITY);

        if (sLastInternalScanFingerprint == null) {
            final SharedPreferences scanSettings =
                    mContext.getSharedPreferences(SCANNED_BUILD_PREFS_NAME, Context.MODE_PRIVATE);
            sLastInternalScanFingerprint =
                    scanSettings.getString(LAST_INTERNAL_SCAN_FINGERPRINT, new String());
        }
        //初始化不同类型数据的Uri,供之后根据不同的表进行插值
        mAudioUri = Audio.Media.getContentUri(volumeName);
        mVideoUri = Video.Media.getContentUri(volumeName);
        mImagesUri = Images.Media.getContentUri(volumeName);
        mThumbsUri = Images.Thumbnails.getContentUri(volumeName);
        mFilesUri = Files.getContentUri(volumeName);
        mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();

        if (!volumeName.equals("internal")) {
            // we only support playlists on external media
            mProcessPlaylists = true;
            mProcessGenres = true;
            mPlaylistsUri = Playlists.getContentUri(volumeName);
        } else {
            mProcessPlaylists = false;
            mProcessGenres = false;
            mPlaylistsUri = null;
        }
        //为MediaScanner设置语言环境  
        final Locale locale = mContext.getResources().getConfiguration().locale;
        if (locale != null) {
            String language = locale.getLanguage();
            String country = locale.getCountry();
            if (language != null) {
                if (country != null) {
                    setLocale(language + "_" + country);
                } else {
                    setLocale(language);
                }
            }
        }

        mCloseGuard.open("close");
    }

相关重要的代码,我都已经注释好中文,不用再解释。再看scanDirecturies()方法:
prescan(null, true)方法,扫描预处理,主要是对MediaProvider数据库进行操作,查询并保存旧有的信息。
processDirectory(directories[i], mClient)方法,开始扫描,此方法是一个native方法,在c文件中经过一系列的调用,最终会回调到内部内MyMediaScannerClient的scanFile()方法,进而调用doScanFile()方法。

代码太长,不贴了。分析该方法,首先beginFile(),这个方法主要是根据传入的各项文件参数去解析构造FileEntry 的实例;
最后调用endFile()方法来更新数据库。具体的扫描过程是底层代码实现,太深了,功力不够,我们只是分析在上层代码中,扫描媒体文件的流程。

上一篇下一篇

猜你喜欢

热点阅读