MediaScanner位置在frameworks\base\media\下,包括jni和java文件,
在Android的SDK里面是看不到这个类的,因为被google隐藏了。通过Android的源码我们可以看到MediaScanner的类注解多了一个@hide的标注。所以对于一般应用开发者,此文意义不是很大,大家可以绕道。在前两篇文章中,最后我们都了解了Android的媒体文件的扫描是在MediaScannerService中调用MediaScanner的scanDirectories或者scanSingleFile完成最终的扫描的。那么MediaScanner是如何工作的呢?

先来看看google对该类的注释:

1: /* In summary:
   2: * Java MediaScannerService calls
   3: * Java MediaScanner scanDirectories, which calls
   4: * Java MediaScanner processDirectory (native method), which calls
   5: * native MediaScanner processDirectory, which calls
   6: * native MyMediaScannerClient scanFile, which calls
   7: * Java MyMediaScannerClient scanFile, which calls
   8: * Java MediaScannerClient doScanFile, which calls
   9: * Java MediaScanner processFile (native method), which calls
  10: * native MediaScanner processFile, which calls
  11: * native parseMP3, parseMP4, parseMidi, parseOgg or parseWMA, which calls
  12: * native MyMediaScanner handleStringTag, which calls
  13: * Java MyMediaScanner handleStringTag.
  14: * Once MediaScanner processFile returns, an entry is inserted in to the database.
  15: *
  16: * {@hide}
  17: */

下面为调用时序图,如下:


先看看java实现。
这个类巨复杂,而且和MediaProvider交互频繁。在分析的时候要时刻回到MediaProvider去
看看。
1. 初始化

public class MediaScanner{static {//libmedia_jni.so的加载是在MediaScanner类中完成的//这么重要的so为何放在如此不起眼的地方加载???System.loadLibrary("media_jni");native_init();}public MediaScanner(Context c) {native_setup();//调用jni层的初始化,暂时不用看了,无非就是一些//初始化工作,待会在再进去看看……..}

刚才MSS中是调用scanDirectories函数,我们看看这个。


2. scanDirectories

public void scanDirectories(String[] directories, String volumeName) {try {long start = System.currentTimeMillis();initialize(volumeName);//初始化prescan(null);//扫描前的预处理long prescan = System.currentTimeMillis();for (int i = 0; i < directories.length; i++) {//扫描文件夹,这里有一个很重要的参数 mClient// processDirectory是一个native函数processDirectory(directories[i], MediaFile.sFileExtensions, mClient);}long scan = System.currentTimeMillis();postscan(directories);//扫描后处理long end = System.currentTimeMillis();…..打印时间,异常处理…没了…

下面简单讲讲initialize ,preScan和postScan都干嘛了。

private void initialize(String volumeName) {//打开MediaProvider,获得它的一个实例mMediaProvider = mContext.getContentResolver().acquireProvider("media");//得到一些urimAudioUri = Audio.Media.getContentUri(volumeName);mVideoUri = Video.Media.getContentUri(volumeName);mImagesUri = Images.Media.getContentUri(volumeName);mThumbsUri = Images.Thumbnails.getContentUri(volumeName);//外部存储的话,可以支持播放列表之类的东西,搞了一些个缓存池之类的//如mGenreCache等if (!volumeName.equals("internal")) {// we only support playlists on external mediamProcessPlaylists = true;mGenreCache = new HashMap<String, Uri>();…

preScan,这个函数很复杂:
大概就是创建一个FileCache,用来缓存扫描文件的一些信息,例如last_modified等。这个
FileCache是从MediaProvider中已有信息构建出来的,也就是历史信息。后面根据扫描得到的新
信息来对应更新历史信息。
postScan,这个函数做一些清除工作,例如以前有video生成了一些缩略图,现在video文件
被干掉了,则对应的缩略图也要被干掉。
另外还有一个mClient,这个是从MediaScannerClient派生下来的一个东西,里边保存了一
个文件的一些信息。后续再分析。
刚才说到,具体扫描工作是在processDirectory函数中完成的。这个是一个native函数。
在frameworks\base\media\jni\android_media_MediaScanner.cpp中。

3,mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env);

   1: status_t MediaScanner::processDirectory(const char *path, const char* extensions,
   2:         MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv)
   3: {//这方法不知道干吗的,估计跟线程有关
   4:     InitializeForThread();
   5: 
   6:     int pathLength = strlen(path);
   7:     if (pathLength >= PATH_MAX) {
   8:         return PVMFFailure;
   9:     }
  10:     char* pathBuffer = (char *)malloc(PATH_MAX + 1);
  11:     if (!pathBuffer) {
  12:         return PVMFFailure;
  13:     }
  14: 
  15:     int pathRemaining = PATH_MAX - pathLength;
  16:     strcpy(pathBuffer, path);
  17:     if (pathBuffer[pathLength - 1] != '/') {
  18:         pathBuffer[pathLength] = '/';
  19:         pathBuffer[pathLength + 1] = 0;
  20:         --pathRemaining;
  21:     }
  22: 
  23:     client.setLocale(mLocale);
  24:     //有是一个关键点
  25:     status_t result = doProcessDirectory(pathBuffer, pathRemaining, extensions, client, exceptionCheck, exceptionEnv);
  26:     //释放内存
  27:     free(pathBuffer);
  28:     return result;
  29: }
       

4,doProcessDirectory

   1: status_t MediaScanner::doProcessDirectory(char *path, int pathRemaining, const char* extensions,
   2:         MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv)
   3: {
   4:     ……
   5:      ……
   6:         if (type == DT_REG || type == DT_DIR) {
   7:             int nameLength = strlen(name);
   8:             bool isDirectory = (type == DT_DIR);
   9: 
  10:             if (nameLength > pathRemaining || (isDirectory && nameLength + 1 > pathRemaining)) {
  11:                 // path too long!
  12:                 continue;
  13:             }
  14: 
  15:             strcpy(fileSpot, name);
  16:             if (isDirectory) {
  17:                 // ignore directories with a name that starts with '.'
  18:                 // for example, the Mac ".Trashes" directory
  19:                 if (name[0] == '.') continue;
  20: 
  21:                 strcat(fileSpot, "/");
  22:                 //文件夹,递归调用
  23:                 int err = doProcessDirectory(path, pathRemaining - nameLength - 1, extensions, client, exceptionCheck, exceptionEnv);
  24:                 if (err) {
  25:                     // pass exceptions up - ignore other errors
  26:                     if (exceptionCheck && exceptionCheck(exceptionEnv)) goto failure;
  27:                     LOGE("Error processing '%s' - skipping\n", path);
  28:                     continue;
  29:                 }
  30:             } else if (fileMatchesExtension(path, extensions)) {
  31:                 //文件,扩展名符合
  32:                 struct stat statbuf;
  33:                 stat(path, &statbuf);
  34:                 if (statbuf.st_size > 0) {
  35:                     //调用client的scanFile方法
  36:                     client.scanFile(path, statbuf.st_mtime, statbuf.st_size);
  37:                 }
  38:                 if (exceptionCheck && exceptionCheck(exceptionEnv)) goto failure;
  39:             }
  40:         }
  41: ……
  42: ……
       5,client.scanFile           
   1: // returns true if it succeeded, false if an exception occured in the Java code
   2:  virtual bool scanFile(const char* path, long long lastModified, long long fileSize)
   3:  {
   4:      jstring pathStr;
   5:      if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;
   6:      //有点反射的感觉,调用java里面mClient中的scanFile方法
   7:      mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);
   8: 
   9:      mEnv->DeleteLocalRef(pathStr);
  10:      return (!mEnv->ExceptionCheck());
  11:  }
    
       

6,mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);让我们回到Java

在android.media.MediaScanner.MyMediaScannerClient中的scanFile方法是直接调用doScanFile的,来看看doScanFile

   1: 
   2:         public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize,
   3:                 boolean scanAlways) {
   4:             Uri result = null;
   5:             // long t1 = System.currentTimeMillis();
   6:             try {
   7:                 FileCacheEntry entry = beginFile(path, mimeType, lastModified, fileSize);
   8:                 // rescan for metadata if file was modified since last scan
   9:                 if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
  10:                     String lowpath = path.toLowerCase();
  11:                     boolean ringtones = (lowpath.indexOf(RINGTONES_DIR) > 0);
  12:                     boolean notifications = (lowpath.indexOf(NOTIFICATIONS_DIR) > 0);
  13:                     boolean alarms = (lowpath.indexOf(ALARMS_DIR) > 0);
  14:                     boolean podcasts = (lowpath.indexOf(PODCAST_DIR) > 0);
  15:                     boolean music = (lowpath.indexOf(MUSIC_DIR) > 0)
  16:                             || (!ringtones && !notifications && !alarms && !podcasts);
  17: 
  18:                     if (isMetadataSupported(mFileType)) {
  19:                         // 调用jni方法
  20:                         processFile(path, mimeType, this);
  21:                     } else if (MediaFile.isImageFileType(mFileType)) {
  22:                         // we used to compute the width and height but it's not
  23:                         // worth it
  24:                     }
  25: 
  26:                     result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
  27:                 }
  28:             } catch (RemoteException e) {
  29:                 Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
  30:             }
  31:             // long t2 = System.currentTimeMillis();
  32:             // Log.v(TAG, "scanFile: " + path + " took " + (t2-t1));
  33:             return result;
  34:         }
补充:result = endFile(entry, ringtones, notifications, alarms, music, podcasts);就是在这里将媒体数据信息存放到数据库的


7,接着是native的 processFile

   1: static void
   2: android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client)
   3: {
   4:     MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);
   5: 
   6:     if (path == NULL) {
   7:         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
   8:         return;
   9:     }
  10:     
  11:     const char *pathStr = env->GetStringUTFChars(path, NULL);
  12:     if (pathStr == NULL) {  // Out of memory
  13:         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
  14:         return;
  15:     }
  16:     const char *mimeTypeStr = (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);
  17:     if (mimeType && mimeTypeStr == NULL) {  // Out of memory
  18:         env->ReleaseStringUTFChars(path, pathStr);
  19:         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
  20:         return;
  21:     }
  22: 
  23:     MyMediaScannerClient myClient(env, client);
  24:     //调用MediaScanner的processFile
  25:     mp->processFile(pathStr, mimeTypeStr, myClient);
  26:     env->ReleaseStringUTFChars(path, pathStr);
  27:     if (mimeType) {
  28:         env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
  29:     }
  30: }
    
          8,mp->processFile(pathStr, mimeTypeStr, myClient);       在此方法中根据不同的文件扩展名调用更加底层的解析方法,我想主要是ID3信息解析           
   1: status_t MediaScanner::processFile(const char *path, const char* mimeType, MediaScannerClient& client)
   2: {
   3:     status_t result;
   4:     InitializeForThread();
   5:     //初始化client
   6:     client.setLocale(mLocale);
   7:     client.beginFile();
   8:     
   9:     //LOGD("processFile %s mimeType: %s\n", path, mimeType);
  10:     const char* extension = strrchr(path, '.');
  11:     //根据扩展名调用不同的解析方法
  12:     if (extension && strcasecmp(extension, ".mp3") == 0) {
  13:         result = parseMP3(path, client);
  14:     } else if (extension &&
  15:         (strcasecmp(extension, ".mp4") == 0 || strcasecmp(extension, ".m4a") == 0 ||
  16:          strcasecmp(extension, ".3gp") == 0 || strcasecmp(extension, ".3gpp") == 0 ||
  17:          strcasecmp(extension, ".3g2") == 0 || strcasecmp(extension, ".3gpp2") == 0)) {
  18:         result = parseMP4(path, client);
  19:     } else if (extension && strcasecmp(extension, ".ogg") == 0) {
  20:         result = parseOgg(path, client);
  21:     } else if (extension &&
  22:         ( strcasecmp(extension, ".mid") == 0 || strcasecmp(extension, ".smf") == 0
  23:         || strcasecmp(extension, ".imy") == 0)) {
  24:         result = parseMidi(path, client);
  25:     } else if (extension &&
  26:        (strcasecmp(extension, ".wma") == 0 || strcasecmp(extension, ".aac") == 0)) {
  27:         //TODO: parseWMA needs to be renamed to reflect what it is really doing,
  28:         //ie. using OpenCORE frame metadata utility(FMU) to retrieve metadata.
  29:         result = parseWMA(path, client);
  30:     } else {
  31:         result = PVMFFailure;
  32:     }
  33:     //调用client
  34:     client.endFile();
  35: 
  36:     return result;
  37: }
    
       

9,client.endFile()

   1: void MediaScannerClient::endFile()
   2: {
   3:     if (mLocaleEncoding != kEncodingNone) {
   4:         int size = mNames->size();
   5:         uint32_t encoding = kEncodingAll;
   6:         
   7:         // compute a bit mask containing all possible encodings
   8:         for (int i = 0; i < mNames->size(); i++)
   9:             encoding &= possibleEncodings(mValues->getEntry(i));
  10:         
  11:         // if the locale encoding matches, then assume we have a native encoding.
  12:         if (encoding & mLocaleEncoding)
  13:             convertValues(mLocaleEncoding);
  14:         
  15:         // finally, push all name/value pairs to the client
  16:         for (int i = 0; i < mNames->size(); i++) {
  17:             //在handleStringTag中是通过类反射的方法调用java中的handleStringTag
  18:             if (!handleStringTag(mNames->getEntry(i), mValues->getEntry(i)))
  19:                 break;
  20:         }
  21:     }
  22:     // else addStringTag() has done all the work so we have nothing to do
  23:     
  24:     delete mNames;
  25:     delete mValues;
  26:     mNames = NULL;
  27:     mValues = NULL;
  28: }
    
       

10,java中的handleStringTag,这个方法主要处理那些在底层解析后的数据返回到java层

   1: public void handleStringTag(String name, String value) {
   2:      if (name.equalsIgnoreCase("title") || name.startsWith("title;")) {
   3:          // Don't trim() here, to preserve the special \001 character
   4:          // used to force sorting. The media provider will trim() before
   5:          // inserting the title in to the database.
   6:          mTitle = value;
   7:      } else if (name.equalsIgnoreCase("artist") || name.startsWith("artist;")) {
   8:          mArtist = value.trim();
   9:      } else if (name.equalsIgnoreCase("albumartist") || name.startsWith("albumartist;")) {
  10:          mAlbumArtist = value.trim();
  11:      } else if (name.equalsIgnoreCase("album") || name.startsWith("album;")) {
  12:          mAlbum = value.trim();
  13:      } else if (name.equalsIgnoreCase("composer") || name.startsWith("composer;")) {
  14:          mComposer = value.trim();
  15:      } else if (name.equalsIgnoreCase("genre") || name.startsWith("genre;")) {
  16:          // handle numeric genres, which PV sometimes encodes like "(20)"
  17:          if (value.length() > 0) {
  18:              int genreCode = -1;
  19:              char ch = value.charAt(0);
  20:              if (ch == '(') {
  21:                  genreCode = parseSubstring(value, 1, -1);
  22:              } else if (ch >= '0' && ch <= '9') {
  23:                  genreCode = parseSubstring(value, 0, -1);
  24:              }
  25:              if (genreCode >= 0 && genreCode < ID3_GENRES.length) {
  26:                  value = ID3_GENRES[genreCode];
  27:              }
  28:          }
  29:          mGenre = value;
  30:      } else if (name.equalsIgnoreCase("year") || name.startsWith("year;")) {
  31:          mYear = parseSubstring(value, 0, 0);
  32:      } else if (name.equalsIgnoreCase("tracknumber") || name.startsWith("tracknumber;")) {
  33:          // track number might be of the form "2/12"
  34:          // we just read the number before the slash
  35:          int num = parseSubstring(value, 0, 0);
  36:          mTrack = (mTrack / 1000) * 1000 + num;
  37:      } else if (name.equalsIgnoreCase("discnumber") ||
  38:              name.equals("set") || name.startsWith("set;")) {
  39:          // set number might be of the form "1/3"
  40:          // we just read the number before the slash
  41:          int num = parseSubstring(value, 0, 0);
  42:          mTrack = (num * 1000) + (mTrack % 1000);
  43:      } else if (name.equalsIgnoreCase("duration")) {
  44:          mDuration = parseSubstring(value, 0, 0);
  45:      } else if (name.equalsIgnoreCase("writer") || name.startsWith("writer;")) {
  46:          mWriter = value.trim();
  47:      }
  48:  }

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Python list sort方法的具体使用
  3. python list.sort()根据多个关键字排序的方法实现
  4. Android的webview研究
  5. Android(安卓)Camera 系统框架分析
  6. Android中的事件分发机制
  7. android IBeacon 开发(一)搜索IBeacon基站
  8. Android(安卓)之 Window、WindowManager 与窗口管理
  9. 利用HTML5开发Android笔记

随机推荐

  1. android图片异步加载 列表
  2. Android LOG机制流程图
  3. Android之2D图形(圆、直线、点)工具类 (持
  4. android的ontouch事件
  5. Android:MediaMetadataRetriever之setData
  6. 遍历Android SD卡
  7. [Android] RatingBar详解
  8. android:inputType的XML与Java代码对应关
  9. Android 获取SIM卡内信息(TelephonyManage
  10. android解决UI阻塞问题——创建AsyncTask