涉及到的几个类文件:
- MediaScanner.java
- MediaScannerService.java
- MediaProvider.java
- MediaScannerReceiver.java
- android_media_MediaScanner.cpp
当插入sd卡或者插入U盘时,系统会对其进行文件扫描(音乐,视频,图片等),以及音乐播放器中对音乐文件的扫描获取各种信息也是借助于这几个文件来实现扫描功能的。
下面就针对这几个类源码跟踪一下。
MediaScannerReceiver.java 该类是extends BroadCastReceiver,自定义广播类,接收开机广播ACTION_BOOT_COMPLETED以及ACTION_MEDIA_MOUNTED ,ACTION_MEDIA_SCANNER_SCAN_FILE
@Overridepublic 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/usbdiskString 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/usbdiskLog.d(TAG, "action: " + action + " path: " + path);if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {// scan whenever any volume is mounted .Scan externalStoragescan(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 或者 externalBundle args = new Bundle();args.putString("volume", volume);context.startService( //启动MediaScannerServicenew Intent(context, MediaScannerService.class).putExtras(args));}
因此,接下来我们进入MediaScannerService Service中,
MediaScannerService extends Service implements Runnable 实现了Runnable接口,因此我们可以猜测后面的扫描在run方法中进行,否则会阻塞了主线程。
Service 从首次创建到启动需要经历: onCreate() ---- onStartCommand() ... 方法
MediaScannerService::onCreate()
@Overridepublic 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接口),第三个是namethr.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_LOWESTProcess.THREAD_PRIORITY_LESS_FAVORABLE);Looper.prepare();mServiceLooper = Looper.myLooper(); //这个只在onDestroy中调用了,为何调用其quit()方法呢???mServiceHandler = new ServiceHandler();Looper.loop();}@Overridepublic 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
@Overridepublic int onStartCommand(Intent intent, int flags, int startId){//等待mServiceHandler创建完成,在子线程run方法中创建,可能会有延迟;// 同步模块,等待mServiceHandler创建完成while (mServiceHandler == null) {synchronized (this) {try {wait(100);} catch (InterruptedException e) {}}}//intent参数不能为nullif (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
@Overridepublic 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 volumesdirectories = 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 scanningmWakeLock.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需要使用ContentResolverContentValues 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() { //获得MediaScannerMediaScanner scanner = new MediaScanner(this);//根据当前系统语言环境设置MediaScannerLocale 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 urimVideoUri = Video.Media.getContentUri(volumeName);// video urimImagesUri = Images.Media.getContentUri(volumeName);// image urimThumbsUri = Images.Thumbnails.getContentUri(volumeName);mFilesUri = Files.getContentUri(volumeName);// file urimFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();if (!volumeName.equals("internal")) {//外部sd卡// we only support playlists on external mediamProcessPlaylists = true;mProcessGenres = true;mPlaylistsUri = Playlists.getContentUri(volumeName);mCaseInsensitivePaths = true;}}prescan(null, true);//#############if (ENABLE_BULK_INSERTS) {// create MediaInserter for bulk insertsmMediaInserter = new MediaInserter(mMediaProvider, mPackageName, 500);}//这里开始扫描,调用本地方法processDirectoryfor (int i = 0; i < directories.length; i++) {processDirectory(directories[i], mClient);}if (ENABLE_BULK_INSERTS) {// flush remaining insertsmMediaInserter.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 voidandroid_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 动态分配内存,分配成功则返回指向被分配内存的指针,分配失败则返回空指针NULLif (!pathBuffer) { //如果申请不成功则为0 ,0表示falsereturn 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 framework MediaScanner等sd卡u盘扫描流程简要跟踪分析的全部内容,更多相关android内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复