android framework MediaScanner等sd卡u盘扫描流程简要跟踪分析

涉及到的几个类文件:
  1. MediaScanner.java
  2. MediaScannerService.java
  3. MediaProvider.java
  4. MediaScannerReceiver.java
  5. android_media_MediaScanner.cpp
当插入sd卡或者插入U盘时,系统会对其进行文件扫描(音乐,视频,图片等),以及音乐播放器中对音乐文件的扫描获取各种信息也是借助于这几个文件来实现扫描功能的。
下面就针对这几个类源码跟踪一下。
MediaScannerReceiver.java 该类是extends BroadCastReceiver,自定义广播类,接收开机广播ACTION_BOOT_COMPLETED以及ACTION_MEDIA_MOUNTED  ,ACTION_MEDIA_SCANNER_SCAN_FILE
     
     
     
     
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
final Uri uri = intent.getData();
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
// Scan both internal and external storage
//外部存储以及内部存储都扫描
scan(context, MediaProvider.INTERNAL_VOLUME);
scan(context, MediaProvider.EXTERNAL_VOLUME);
 
} else {
if (uri.getScheme().equals("file")) {
// handle intents related to external storage
#################################注意这部分内容
String path = uri.getPath();
// mnt/sdcard/ /storage/usbdisk
String externalStoragePath = Environment.getExternalStorageDirectory().getPath();
String legacyPath = Environment.getLegacyExternalStorageDirectory().getPath();
 
try {
//diff : getPath getAbsolutePath
// recomended use getCanonicalPath()
path = new File(path).getCanonicalPath();
} catch (IOException e) {
Log.e(TAG, "couldn't canonicalize " + path);
return;
}
if (path.startsWith(legacyPath)) {
path = externalStoragePath + path.substring(legacyPath.length());
}
############################################
//eg: action: android.intent.action.MEDIA_MOUNTED path: /storage/usbdisk
Log.d(TAG, "action: " + action + " path: " + path);
if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
// scan whenever any volume is mounted .Scan externalStorage
scan(context, MediaProvider.EXTERNAL_VOLUME);
} else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) &&
path != null && path.startsWith(externalStoragePath + "/")) {
//scan a single folder's files in externalStorage .
//扫描某个特定的文件目录
scanFile(context, path);
}
}
}
MediaScannerReceiver的广播是从MediaService发送过来的,接下来他会去调用scan方法扫描。
MediaScannerReceiver::scan()
     
     
     
     
private void scan(Context context, String volume) { //volume: internal 或者 external
Bundle args = new Bundle();
args.putString("volume", volume);
context.startService( //启动MediaScannerService
new Intent(context, MediaScannerService.class).putExtras(args));
}
因此,接下来我们进入MediaScannerService Service中, MediaScannerService extends Service implements Runnable 实现了Runnable接口,因此我们可以猜测后面的扫描在run方法中进行,否则会阻塞了主线程。
Service 从首次创建到启动需要经历: onCreate() ---- onStartCommand() ...  方法
MediaScannerService::onCreate()
     
     
     
     
@Override
public void onCreate()
{ #################
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
#################保持屏幕亮屏,当android.process.media进程在干活的时候,告诉cpu,你不能熄火
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");//第一个参数是group(null),第二个是runnable(MediaScannerService实现了runnable接口),
第三个是name
thr.start(); // next: 进入run方法
        
        
        
        
public void run()
{
// reduce priority below other background threads to avoid interfering
// with other services at boot time.
//设置线程优先级,如果优先级太高,cpu占用太大会导致系统缓慢。
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND + //Process的最低优先级是 THREAD_PRIORITY_LOWEST
Process.THREAD_PRIORITY_LESS_FAVORABLE);
Looper.prepare();
 
mServiceLooper = Looper.myLooper(); //这个只在onDestroy中调用了,为何调用其quit()方法呢???
mServiceHandler = new ServiceHandler();
 
Looper.loop();
}
         
         
         
         
@Override
public void onDestroy()
{
// Make sure thread has started before telling it to quit.
while (mServiceLooper == null) {
synchronized (this) {
try {
wait(100);
} catch (InterruptedException e) {
}
}
}
mServiceLooper.quit();
}
}
MediaScannerService::onStartCommand
     
     
     
     
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
//等待mServiceHandler创建完成,在子线程run方法中创建,可能会有延迟; // 同步模块,等待mServiceHandler创建完成
while (mServiceHandler == null) {
synchronized (this) {
try {
wait(100);
} catch (InterruptedException e) {
}
}
}
//intent参数不能为null
if (intent == null) {
Log.e(TAG, "Intent is null in onStartCommand: ",
new NullPointerException());
return Service.START_NOT_STICKY; //该标志:如果service在调用onstartCommand后,service被kill掉,则不会重启该service
}
//mServiceHandler 发送消息,交由mServiceHandler.handleMessage方法
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;//该参数在stopSelf()方法中会用到
msg.obj = intent.getExtras();
mServiceHandler.sendMessage(msg);
 
// Try again later if we are killed before we can finish scanning.
return Service.START_REDELIVER_INTENT; // 该标志: 如果在onstartCommand调用,service被kill掉后,则会重启service,并将intent的值传入
接下来的操作在ServiceHandler::handleMessage方法中
在handleMessage方法中一共处理了三个方面扫描:1. 扫描单个文件 2. 扫描某单个目录 3. 扫描internal 磁盘 4.扫描外部external磁盘,所有。
从取出来的参数来看:使用mediaScannerService扫描用到了三个参数(Bundle): volume;filepatch;directorypath;mimetype
     
     
     
     
@Override
public void handleMessage(Message msg)
{
Bundle arguments = (Bundle) msg.obj;
String filePath = arguments.getString("filepath");
try {
if (filePath != null) {
......这部分省略。扫描单个file
} else {
String volume = arguments.getString("volume");//internal external
    //注:directories为何是String数组? 因为:后面扫描有可能是单个磁盘也有可能是多个外部磁盘,因此为了实现代码复用,传递参数使用String数组
                    String[] directories = null
//设置扫描路径。
if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {
// scan internal media storage 内部system/media/
 
directories = new String[] {
Environment.getRootDirectory() + "/media",
Environment.getOemDirectory() + "/media", // 这里 Environment . getOemDirectory () 的路径是???
};
}
else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {
// scan external storage volumes
directories = mExternalStoragePaths;// 所有外部存储路径 mExternalStoragePaths是个String数组,在Service的oncreate方法里创建
}
 
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);
}
}
} catch (Exception e) {
Log.e(TAG, "Exception in handleMessage", e);
}
 
stopSelf(msg.arg1);
}
接下来在scan方法里面.    坎:ContentProvider
     
     
     
     
private void scan(String[] directories, String volumeName) {
Uri uri = Uri.parse("file://" + directories[0]); // file://system/media/***/media
// don't sleep while scanning
mWakeLock.acquire();
 
try {//这里的contentProvider的作用
//###############################################################################
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 {
//使用MediaProvider相关数据库操作
if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {
openDatabase(volumeName);
}
         
         
         
         
private void openDatabase(String volumeName) {
try {
//操作ContentProvider需要使用ContentResolver
ContentValues values = new ContentValues();
values.put("name", volumeName);
getContentResolver().insert(Uri.parse("content://media/"), values); 
//这里的values值为internal 或者 external ===>content://media/internal
} catch (IllegalArgumentException ex) {
Log.w(TAG, "failed to open media database");
}
}

 
MediaScanner scanner = createMediaScanner();
         
         
         
         
private MediaScanner createMediaScanner() { //获得MediaScanner
MediaScanner scanner = new MediaScanner(this);
//根据当前系统语言环境设置MediaScanner
Locale locale = getResources().getConfiguration().locale;
if (locale != null) {
String language = locale.getLanguage();
String country = locale.getCountry();
String localeString = null;
if (language != null) {
if (country != null) {
scanner.setLocale(language + "_" + country);
} else {
scanner.setLocale(language);
}
}
}
return scanner;
}
//接下来在MediaScanner中,这个方法中去调用了本地方法processDirectory底层方法
scanner.scanDirectories(directories, volumeName);
} 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();
}
}
在MediaScanner做了一部分准备工作后,后面转到 MediaScanner.java:scanDirectories方法中
     
     
     
     
public void scanDirectories(String[] directories, String volumeName) {
try {
...
//#############做一些初始化以及扫描前准备工作
initialize(volumeName);
         
         
         
         
private void initialize(String volumeName) {
mMediaProvider = mContext.getContentResolver().acquireProvider("media");
 
mAudioUri = Audio.Media.getContentUri(volumeName);//audio uri
mVideoUri = Video.Media.getContentUri(volumeName);// video uri
mImagesUri = Images.Media.getContentUri(volumeName);// image uri
mThumbsUri = Images.Thumbnails.getContentUri(volumeName);
mFilesUri = Files.getContentUri(volumeName);// file uri
mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
 
if (!volumeName.equals("internal")) {
//外部sd卡
// we only support playlists on external media
mProcessPlaylists = true;
mProcessGenres = true;
mPlaylistsUri = Playlists.getContentUri(volumeName);
 
mCaseInsensitivePaths = true;
}
}
prescan(null, true);
//#############
 
if (ENABLE_BULK_INSERTS) {
// create MediaInserter for bulk inserts
mMediaInserter = new MediaInserter(mMediaProvider, mPackageName, 500);
}
//这里开始扫描,调用本地方法processDirectory
for (int i = 0; i < directories.length; i++) {
processDirectory(directories[i], mClient);
}
 
if (ENABLE_BULK_INSERTS) {
// flush remaining inserts
mMediaInserter.flushAll();
mMediaInserter = null;
}
//这里对扫描的结果做更新操作,删除在sd卡或者u盘中已经不存在的记录
postscan(directories);
//...... delete the log
} catch (SQLException e) {
......
} catch (UnsupportedOperationException e) {
......
} catch (RemoteException e) {
......
} finally {
releaseResources();
}
}
后面的部分在JNI中.  上面部分有个重要的方法是:  prescan(String filePath, boolean prescanFiles)
从上面的分析我们发现,Media的扫描工作主要集中在MediaScanner中,因此重点应该放在MediaScanner.java以及对应的MediaScannerProvider中。
     
     
     
     
static void
android_media_MediaScanner_processDirectory(
JNIEnv *env, jobject thiz, jstring path, jobject client)
{
MediaScanner *mp = getNativeScanner_l(env, thiz);
...
const char *pathStr = env->GetStringUTFChars(path, NULL);
...
MyMediaScannerClient myClient(env, client);
MediaScanResult result = mp->processDirectory(pathStr, myClient);
if (result == MEDIA_SCAN_RESULT_ERROR) {
ALOGE("An error occurred while scanning directory '%s'.", pathStr);
}
env->ReleaseStringUTFChars(path, pathStr);
}
processDirectory该方法的c++声明在MediaScanner.h中
     
     
     
     
MediaScanResult MediaScanner::processDirectory(
const char *path, MediaScannerClient &client) { // const修改的为常量,不能做修改
int pathLength = strlen(path);
//对path的长度有限制。下面几种情况:路径没有找到;打不开;类型不支持
if (pathLength >= PATH_MAX) {
return MEDIA_SCAN_RESULT_SKIPPED;
} // pathBuffer 是个char类型的指针
char* pathBuffer = (char *)malloc(PATH_MAX + 1); // malloc 动态分配内存,分配成功则返回指向被分配内存的指针,分配失败则返回空指针NULL
if (!pathBuffer) { //如果申请不成功则为0 ,0表示false
return MEDIA_SCAN_RESULT_ERROR;
}
 
int pathRemaining = PATH_MAX - pathLength;
strcpy(pathBuffer, path);
if (pathLength > 0 && pathBuffer[pathLength - 1] != '/') {
pathBuffer[pathLength] = '/';
pathBuffer[pathLength + 1] = 0;
--pathRemaining;
}
 
client.setLocale(locale());
 
MediaScanResult result = doProcessDirectory(pathBuffer, pathRemaining, client, false);
 
free(pathBuffer); 调用了malloc 方法后需要调用free方法
 
return result;
}

Vold 相关的源代码在:system目录下

你可能感兴趣的:(Android开发主线)