一. 首先需要了解系统扫描媒体文件的目的是什么?
系统的资源文件,被放置在系统特定的目录,需要通过扫描,将资源文件保存在系统数据库中,这样才能方便资源文件被调用。
二. 扫描流程
- 流程图:
- 首先看这个文件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()方法来更新数据库。具体的扫描过程是底层代码实现,太深了,功力不够,我们只是分析在上层代码中,扫描媒体文件的流程。