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中,这时它会把文件信息组织起来,然后存入媒体数据库。