🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
1. PVMS的processDirectory分析 来看PVMediaScanner(以后简称为PVMS,它就是Native层的MS)的processDirectory函数。这个函数是由它的基类MS实现的。注意,源码中有两个MediaScanner.cpp,它们的位置分别是: - framework/base/media/libmedia - external/opencore/android/ 看libmedia下的那个MediaScanner.cpp,其中processDirectory函数的代码如下所示: **MediaScanner.cpp** ~~~ status_t MediaScanner::processDirectory(constchar *path, const char *extensions, MediaScannerClient&client, ExceptionCheckexceptionCheck, void *exceptionEnv) { ......//做一些准备工作 client.setLocale(locale()); //给Native的MyMSC设置locale信息 //调用doProcessDirectory函数扫描文件夹 status_tresult = doProcessDirectory(pathBuffer,pathRemaining, extensions, client,exceptionCheck, exceptionEnv); free(pathBuffer); returnresult; } //下面直接看这个doProcessDirectory函数 status_t MediaScanner::doProcessDirectory(char*path, int pathRemaining, const char *extensions,MediaScannerClient&client, ExceptionCheck exceptionCheck,void *exceptionEnv) { ......//忽略.nomedia文件夹 DIR*dir = opendir(path); ...... while((entry = readdir(dir))) { //枚举目录中的文件和子文件夹信息 const char* name = entry->d_name; ...... int type = entry->d_type; ...... if(type == DT_REG || type == DT_DIR) { int nameLength = strlen(name); bool isDirectory = (type == DT_DIR); ...... strcpy(fileSpot, name); if (isDirectory) { ...... //如果是子文件夹,则递归调用doProcessDirectory int err = doProcessDirectory(path, pathRemaining - nameLength - 1, extensions, client, exceptionCheck, exceptionEnv); ...... } else if (fileMatchesExtension(path, extensions)) { //如果该文件是MS支持的类型(根据文件的后缀名来判断) struct stat statbuf; stat(path, &statbuf); //取出文件的修改时间和文件的大小 if (statbuf.st_size > 0) { //如果该文件大小非零,则调用MyMSC的scanFile函数!!? client.scanFile(path,statbuf.st_mtime, statbuf.st_size); } if (exceptionCheck && exceptionCheck(exceptionEnv)) gotofailure; } } } ...... } ~~~ 假设正在扫描的媒体文件的类型是属于MS支持的,那么,上面代码中最不可思议的是,它竟然调用了MSC的scanFile来处理这个文件,也就是说,MediaScanner调用MediaScannerClient的scanFile函数。这是为什么呢?还是来看看这个MSC的scanFile吧。 2. MyMSC的scanFile分析 (1)JNI层的scanFile 其实,在调用processDirectory时,所传入的MSC对象的真实类型是MyMediaScannerClient,下面来看它的scanFile函数,代码如下所示: **android_media_MediaScanner.cpp** ~~~ virtual bool scanFile(const char* path, longlong lastModified, long long fileSize) { jstring pathStr; if((pathStr = mEnv->NewStringUTF(path)) == NULL) return false; //mClient是Java层的那个MyMSC对象,这里调用它的scanFile函数 mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr,lastModified, fileSize); mEnv->DeleteLocalRef(pathStr); return (!mEnv->ExceptionCheck()); } ~~~ 太没有天理了!Native的MyMSCscanFile主要的工作就是调用Java层MyMSC的scanFile函数。这又是为什么呢? (2)Java层的scanFile 现在只能来看Java层的这个MyMSC对象了,它的scanFile代码如下所示: **MediaScanner.java** ~~~ public void scanFile(String path, longlastModified, long fileSize) { ...... //调用doScanFile函数 doScanFile(path, null, lastModified, fileSize, false); } //直接来看doScanFile函数 publicUri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean scanAlways) { /* 上面参数中的scanAlways用于控制是否强制扫描,有时候一些文件在前后两次扫描过程中没有 发生变化,这时候MS可以不处理这些文件。如果scanAlways为true,则这些没有变化 的文件也要扫描。 */ Uriresult = null; long t1 = System.currentTimeMillis(); try{ /* beginFile的主要工作,就是将保存在mFileCache中的对应文件信息的 mSeenInFileSystem设为true。如果这个文件之前没有在mFileCache中保存, 则会创建一个新项添加到mFileCache中。另外它还会根据传入的lastModified值 做一些处理,以判断这个文件是否在前后两次扫描的这个时间段内被修改,如果有修改,则 需要重新扫描 */ FileCacheEntryentry = beginFile(path, mimeType,lastModified, fileSize); if(entry != null && (entry.mLastModifiedChanged || scanAlways)) { String lowpath = path.toLowerCase(); ...... if (!MediaFile.isImageFileType(mFileType)) { //如果不是图片,则调用processFile进行扫描,而图片不需要扫描就可以处理 //注意在调用processFile时把这个Java的MyMSC对象又传了进去。 processFile(path, mimeType, this); } //扫描完后,需要把新的信息插入数据库,或者要将原有的信息更新,而endFile就是做这项工作的。 result = endFile(entry, ringtones, notifications,alarms, music, podcasts); } } ...... return result; } ~~~ 下面看这个processFile,这又是一个native的函数。 上面代码中的beginFile和endFile函数比较简单,读者可以自行研究。 (3)JNI层的processFile分析 MediaScanner的代码有点绕,是不是?总感觉我们像追兵一样,追着MS在赤水来回地绕,现在应该是二渡赤水了。来看这个processFile函数,代码如下所示: **android_media_MediaScanner.cpp** ~~~ android_media_MediaScanner_processFile(JNIEnv*env, jobject thiz, jstring path, jstring mimeType, jobject client) { //Native的MS还是那个MS,其真实类型是PVMS。 MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz,fields.context); //又构造了一个新的Native的MyMSC,不过它指向的Java层的MyMSC没有变化。 MyMediaScannerClient myClient(env, client); //调用PVMS的processFile处理这个文件。 mp->processFile(pathStr,mimeTypeStr, myClient); } ~~~ 看来,现在得去看看PVMS的processFile函数了。 3. PVMS的processFile分析 (1)扫描文件 这是我们第一次进入到PVMS的代码中进行分析: **PVMediaScanner.cpp** ~~~ status_t PVMediaScanner::processFile(const char*path, const char* mimeType, MediaScannerClient& client) { status_t result; InitializeForThread(); //调用Native MyMSC对象的函数做一些处理 client.setLocale(locale()); /* beginFile由基类MSC实现,这个函数将构造两个字符串数组,一个叫mNames,另一个叫mValues。 这两个变量的作用和字符编码有关,后面会碰到。 */ client.beginFile(); ...... constchar* extension = strrchr(path, '.'); //根据文件后缀名来做不同的扫描处理 if(extension && strcasecmp(extension, ".mp3") == 0) { result = parseMP3(path, client);//client又传进去了,我们看看对MP3文件的处理 ...... } /* endFile会根据client设置的区域信息来对mValues中的字符串做语言转换,例如一首MP3 中的媒体信息是韩文,而手机设置的语言为简体中文,endFile会尽量对这些韩文进行转换。 不过语言转换向来是个大难题,不能保证所有语言的文字都能相互转换。转换后的每一个value都 会调用handleStringTag做后续处理。 */ client.endFile(); ...... } ~~~ 下面再到parseMP3这个函数中去看看,它的代码如下所示: **PVMediaScanner.cpp** ~~~ static PVMFStatus parseMP3(const char *filename,MediaScannerClient& client) { //对MP3文件进行解析,得到诸如duration、流派、标题的TAG(标签)信息。在Windows平台上 //可通过千千静听软件查看MP3文件的所有TAG信息 ...... //MP3文件已经扫描完了,下面将这些TAG信息添加到MyMSC中,一起看看 if(!client.addStringTag("duration", buffer)) ...... } ~~~ (2)添加TAG信息 文件扫描完了,现在需要把文件中的信息通过addStringTag函数告诉给MyMSC。下面来看addStringTag的工作。这个函数由MyMSC的基类MSC处理。 **MediaScannerClient.cpp** ~~~ bool MediaScannerClient::addStringTag(constchar* name, const char* value) { if(mLocaleEncoding != kEncodingNone) { bool nonAscii = false; const char* chp = value; char ch; while ((ch = *chp++)) { if (ch & 0x80) { nonAscii = true; break; } } /* 判断name和value的编码是不是ASCII,如果不是的话则保存到 mNames和mValues中,等到endFile函数的时候再集中做字符集转换。 */ if(nonAscii) { mNames->push_back(name); mValues->push_back(value); return true; } } //如果字符编码是ASCII的话,则调用handleStringTag函数,这个函数由子类MyMSC实现。 returnhandleStringTag(name, value); } ~~~ **android_media_MediaScanner.cpp::MyMediaScannerClient类** ~~~ virtual bool handleStringTag(const char* name,const char* value) { ...... //调用Java层MyMSC对象的handleStringTag进行处理 mEnv->CallVoidMethod(mClient, mHandleStringTagMethodID, nameStr,valueStr); } ~~~ **MediaScanner.java** ~~~ publicvoid handleStringTag(String name, String value) { //保存这些TAG信息到MyMSC对应的成员变量中去。 if (name.equalsIgnoreCase("title") ||name.startsWith("title;")) { mTitle = value; } else if (name.equalsIgnoreCase("artist") || name.startsWith("artist;")) { mArtist = value.trim(); } else if (name.equalsIgnoreCase("albumartist") || name.startsWith("albumartist;")) { mAlbumArtist = value.trim(); } ...... } ~~~ 到这里,一个文件的扫描就算做完了。不过,读者还记得是什么时候把这些信息保存到数据库的吗? 是在Java层MyMSC对象的endFile中,这时它会把文件信息组织起来,然后存入媒体数据库。