10.3.3 PVMediaScanner分析
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(const char*path,
const char*extensions,MediaScannerClient&client,
ExceptionCheck exceptionCheck,void*exceptionEnv){
……//做一些准备工作。
client.setLocale(locale());//给Native的MyMSC设置locale信息。
//调用doProcessDirectory函数扫描文件夹。
status_t result=doProcessDirectory(pathBuffer,pathRemaining,
extensions,client,exceptionCheck,exceptionEnv);
free(pathBuffer);
return result;
}
//下面直接看这个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))goto failure;
}
}
}
……
}
假设正在扫描的媒体文件类型是属于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,long long 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的MyMSC scanFile主要的工作就是调用Java层MyMSC的scanFile函数。这又是为什么呢?
(2)Java层的scanFile
现在只能来看Java层的这个MyMSC对象了,它的scanFile代码如下所示:
[—>MediaScanner.java]
public void scanFile(String path,long lastModified,long fileSize){
……
//调用doScanFile函数。
doScanFile(path,null,lastModified,fileSize,false);
}
//直接来看doScanFile函数。
public Uri doScanFile(String path,String mimeType,long lastModified,
long fileSize,boolean scanAlways){
/*
上面参数中的scanAlways用于控制是否强制扫描,有时候一些文件在前后两次扫描过程中没有发生变化,这时候MS可以不处理这些文件。如果scanAlways为true,则这些没有变化的文件也要扫描。
*/
Uri result=null;
long t1=System.currentTimeMillis();
try{
/*
beginFile的主要工作,就是将保存在mFileCache中的对应文件信息的mSeenInFileSystem设为true。如果这个文件之前没有在mFileCache中保存,则会创建一个新项添加到mFileCache中。另外它还会根据传入的lastModified值做一些处理,以判断这个文件是否在前后两次扫描的这个时间段内被修改,如果有修改,则需要重新扫描。
*/
FileCacheEntry entry=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。
MediaScannermp=(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 charpath,const charmimeType,
MediaScannerClient&client)
{
status_t result;
InitializeForThread();
//调用Native MyMSC对象的函数做一些处理。
client.setLocale(locale());
/*
beginFile由基类MSC实现,这个函数将构造两个字符串数组,一个叫mNames,另一个叫mValues。
这两个变量的作用和字符编码有关,后面会碰到。
*/
client.beginFile();
……
const char*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]
staticPVMFStatusparseMP3(constchar*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(const charname,const charvalue)
{
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实现。
return handleStringTag(name,value);
}
[—>android_media_MediaScanner.cpp:MyMediaScannerClient类]
virtual bool handleStringTag(const charname,const charvalue)
{
……
//调用Java层MyMSC对象的handleStringTag进行处理。
mEnv->CallVoidMethod(mClient,mHandleStringTagMethodID,nameStr,valueStr);
}
[—>MediaScanner.java]
public void 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中,这时它会把文件信息组织起来,然后存入媒体数据库。