美文网首页
Android N 开机扫描媒体文件流程

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

作者: xlq | 来源:发表于2017-10-26 17:47 被阅读0次

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

    二. 扫描流程

    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()方法来更新数据库。具体的扫描过程是底层代码实现,太深了,功力不够,我们只是分析在上层代码中,扫描媒体文件的流程。

    相关文章

      网友评论

          本文标题:Android N 开机扫描媒体文件流程

          本文链接:https://www.haomeiwen.com/subject/uypkpxtx.html