Bug资料:
平台:Android 4.1(JellyBean)
芯片:高通
手机型号:笨蛋(开发中,不能写啦。笨蛋是我给起的代号。我这里还有小红、小黄之类的。这一台屏幕大,但比较厚,我喊它笨蛋)
问题描述:如本人标题所述,批量导入一批MP3格式的文件到手机的存储空间中,进入Music app查看歌曲列表,里面会有一些歌曲不显示duration。
Debug过程:
Music app里面Duration是从数据库里面读取的,所以首先查看数据库中是否有相关的讯息.
数据库位置:
data/data/com.android.providers.media/databases/external.db
数据库情况如下:
很明显,这些异常的歌曲在数据库中就没有duration讯息.
到目前为止,根据已经获得的讯息,我推定如下:
1. 问题发生在Framework层而非App层.
2. 最可能的位置是MediaScanner模块.(这里的推定我是"根据经验",省去推导步骤.后面的过程可以印证这里的推定是正确的.)
可能的情况猜测有两种:
1.MediaScanner获取媒体duration讯息失败.
2.duration讯息获取成功,但写入数据库失败.
下面就上述两种猜测进入debug.
根据流程,先获取duration讯息,再写入数据库.所以debug反向进行,先看是否有写入数据库.
MediaScanner在准备写入资料的时候,会先创建一条记录,初始化各个字段的值.duration初始化的时候是0,我关心的就是,在其初始化之后,是否发生过赋值.这关系到异常文件duration=0的原因.到底是直接使用了初始化的值,还是初始化之后有赋值过,只是第二次赋值为0.
StagefrightMediaScanner.cpp中相关代码如下:
static const KeyMap kKeyMap[] = {
{ "tracknumber", METADATA_KEY_CD_TRACK_NUMBER },
{ "discnumber", METADATA_KEY_DISC_NUMBER },
{ "album", METADATA_KEY_ALBUM },
{ "artist", METADATA_KEY_ARTIST },
{ "albumartist", METADATA_KEY_ALBUMARTIST },
{ "composer", METADATA_KEY_COMPOSER },
{ "genre", METADATA_KEY_GENRE },
{ "title", METADATA_KEY_TITLE },
{ "year", METADATA_KEY_YEAR },
{ "duration", METADATA_KEY_DURATION },
{ "writer", METADATA_KEY_WRITER },
{ "compilation", METADATA_KEY_COMPILATION },
{ "isdrm", METADATA_KEY_IS_DRM },
{ "width", METADATA_KEY_VIDEO_WIDTH },
{ "height", METADATA_KEY_VIDEO_HEIGHT },
};
static const size_t kNumEntries = sizeof(kKeyMap) / sizeof(kKeyMap[0]);
for (size_t i = 0; i < kNumEntries; ++i) {
ALOGV("Vinky|processFileInternal()|current is [%d]/[%d]",i,kNumEntries);//这里我输出循环的当前次数和总次数,顺便看看是否有完成所有循环.每次循环取的就是上面的kKeyMap里面写的tag值
const char *value;
if ((value = mRetriever->extractMetadata(kKeyMap[i].key)) != NULL) {//如果对应的Tag有值,就把该值写入前面提到的创建出来的数据库记录中.
status = client.addStringTag(kKeyMap[i].tag, value);
ALOGV("Vinky|processFileInternal()|client.addString tag[%s] value[%s]", kKeyMap[i].tag, value);//如果读到duration,这里会有反应的.根据kKeyMap的记录,duration的下标应该是9号.
if (status != OK) {
ALOGV("Vinky|processFileInternal()|ERROR! current is [%d]/[%d]",i,kNumEntries);//这里是可能导致错误的地方,如果出错,把当前的次数打出来,可以知道在解析哪个tag的时候出错
return MEDIA_SCAN_RESULT_ERROR;
}
}
加入上述log之后,重现编译libmedia.so,替换进手机里面,重启.
触发这段函数的方法很简单,找任何一个第三方的文件管理器,修改一个MP3文件的名字,都会触发MediaScanner对其进行扫描.
后面的log我当时忘记截图了,我直接说结果.
1. 正常的MP3文件,可以走完上面的15次循环.
2. 不正常的MP3文件,走到i==3的时候,就触发了if (status != OK) 这个条件,导致返回了MEDIA_SCAN_RESULT_ERROR.这个返回导致整个循环终止,这个时候我们的duration还没轮上呢.前面说过,它是可怜的9号.
所以,问题符合上面的情况1,MediaScanner获取媒体duration讯息失败.后面的music app使用的duration==0的值,是初始化的时候生成的.
根据刚刚的log讯息,问题就归纳为,i==3,即解析artist的时候,导致status!=OK.(顺带一提,这里OK==0,status==1).
i==3的时候,打出来的log如下:
Vinky|processFileInternal()|client.addString tag[artist] value[¥j¥‥°o]
value是一个乱码(这里根据编译器设置的编码,乱得可能不一样,但结论是一样的,这不是一个ASCII码)
下面就转到client.addStringTag(kKeyMap[i].tag, value);里面看看发生了什麽吧.
文件是MediaScannerClient.cpp
函数如下:
bool MediaScannerClient::addStringTag(const char* name, const char* value)
...
if (nonAscii) {
// save the strings for later so they can be used for native encoding detection
mNames->push_back(name);
mValues->push_back(value);
return true;
}
...
很明显,如果value不是一个ASCII码的话,会暂存起来,到最后一起根据当前的编码做转换.(分析到这里,我看了一下这个MP3的artist,是中文的"古巨基",的确不是ASCII码)
这里的return true很奇怪,正是这里导致了我们刚刚的status!=OK.(OK==0,status==1)
只看逻辑的话,这里就很有问题,如果有非ASCII码的value过来,这里一定会导致外层循环中断而读不到duration的.所以我断定这里被其他同事改过,虽然这家伙很不厚道的没有留下任何comment.
找来Google官方代码的同名文件,以及MTK平台某项目的同名文件做对比,其他的文件在这里都是return OK的.return OK的话,就不会触发外层的status!=OK,循环当然是可以走完的.
测试return OK,bug解除.
总结:
1. 非ASCII码的value,是会在最后集中转换的.
2. 永远不要以为没有加comment的地方,都是和官方原始代码一样的.
声明:
本文所使用代码,均来自Google官方release的Android JellyBean.
如需转载,烦请保留本文链接,谢谢您的尊重.