转自:http://blog.csdn.net/jgdu1981/article/details/6771031


http://www.cnblogs.com/halzhang/archive/2011/03/10/1980319.html

神马是MediaScanner呢?在Android的SDK里面是看不到这个类的,因为被google隐藏了。通过Android的源码我们可以看到MediaScanner的类注解多了一个@hide的标注。所以对于一般应用开发者,此文意义不是很大,大家可以绕道。

在前两篇文章中,最后我们都了解了Android的媒体文件的扫描是在MediaScannerService中调用MediaScanner的scanDirectories或者scanSingleFile完成最终的扫描的。那么MediaScanner是如何工作的呢?

转载请注明出处

google对MediaScanner写了一大堆的类注释,如下:

   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: */

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

这时序图好像不是很规范!点击看大图!请见谅。 开始看代码把……

1,scanDirectories。

初始化数据并调用processDirectory处理扫描。

   1: public void scanDirectories(String[] directories, String volumeName) {
   2:         try {
   3:             long start = System.currentTimeMillis();
   4:             //初始化
   5:             initialize(volumeName);
   6:             //将数据库中的数据缓存到mFileCache
   7:             /*
   8:              * mFileCache.put(key, new FileCacheEntry(uri, rowId, path, lastModified));
   9:              */
  10:             prescan(null);
  11:             long prescan = System.currentTimeMillis();
  12: 
  13:             for (int i = 0; i < directories.length; i++) {
  14:                 //扫描处理
  15:                 processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
  16:             }
  17:             long scan = System.currentTimeMillis();
  18:             //处理后续数据
  19:             postscan(directories);
  20:             long end = System.currentTimeMillis();

2,processDirectory

这是一个native方法,所以我们直接转向jni,代码如下:

   1: static void
   2: android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client)
   3: {   //获取MediaScanner
   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:     if (extensions == NULL) {
  11:         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
  12:         return;
  13:     }
  14:     
  15:     const char *pathStr = env->GetStringUTFChars(path, NULL);
  16:     if (pathStr == NULL) {  // Out of memory
  17:         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
  18:         return;
  19:     }
  20:     const char *extensionsStr = env->GetStringUTFChars(extensions, NULL);
  21:     if (extensionsStr == NULL) {  // Out of memory
  22:         env->ReleaseStringUTFChars(path, pathStr);
  23:         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
  24:         return;
  25:     }
  26:     //初始化client实例
  27:     MyMediaScannerClient myClient(env, client);
  28:     //mp调用processDirectory
  29:     mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env);
  30:     //gc
  31:     env->ReleaseStringUTFChars(path, pathStr);
  32:     env->ReleaseStringUTFChars(extensions, extensionsStr);
  33: }

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:  }

此致,此文结束,累。

三篇有关MediaScanner的文章,希望对大家有所帮助。

对C/C++不是很熟悉,如发现分析有误,请告知。


更多相关文章

  1. “罗永浩抖音首秀”销售数据的可视化大屏是怎么做出来的呢?
  2. Nginx系列教程(三)| 一文带你读懂Nginx的负载均衡
  3. 不吹不黑!GitHub 上帮助人们学习编码的 12 个资源,错过血亏...
  4. Android(安卓)build文件的删除的方法
  5. Android:定时的实现
  6. Android(安卓)面试题中高级
  7. android 多余文字显示省略号
  8. Android隐藏标题栏、状态栏、导航栏及全屏方法总结
  9. Android(安卓)Service中的startService或者bindService的区别

随机推荐

  1. Android(安卓)Design Support Library学
  2. Android(安卓)自定义CheckBox 样式
  3. 通过 按钮 bundle 传过来 变换Q币图片
  4. 申请android google map API key
  5. Android(安卓)日志打印工具类 可显示打印
  6. android 发送短信的两种方式
  7. android.support.v4.widget.DrawerLayout
  8. Android(安卓)命令行手动编译打包详解
  9. 关于Android证书申请配置
  10. Ubuntu 14.10+NDK+ffmpeg编译+Android