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

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

二. 扫描流程

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容