10.3.3 PVMediaScanner分析
最后更新于:2022-04-02 05:54:16
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中,这时它会把文件信息组织起来,然后存入媒体数据库。
';