基于android4.2,主要出现在mips架构,arm架构上没有此现象。
猜测与MediaScanner有关,一直跟代码,跟啊跟,跟啊跟。跟到frameworks/base/media/jni/android_media_MediaScanner.cpp文件的handleStringTag函数,它的实现如下:
119 virtual status_t handleStringTag(const char* name, const char* value)
120 {
121 ALOGE("handleStringTag: name(%s) and value(%s)", name, value);
122 jstring nameStr, valueStr;
123 if ((nameStr = mEnv->NewStringUTF(name)) == NULL) {
124 mEnv->ExceptionClear();
125 return NO_MEMORY;
126 }
127
128 // Check if the value is valid UTF-8 string and replace
129 // any un-printable characters with '?' when it's not.
130 char *cleaned = NULL;
131 if (utf8_length(value) == -1) {
132 cleaned = strdup(value);
133 char *chp = cleaned;
134 char ch;
135 while ((ch = *chp)) {
136 if (ch & 0x80) {
137 *chp = '?';
138 }
139 ALOGE("handleStringTag: value(%x)",ch);
140 chp++;
141 }
142 value = cleaned;
143 }
144 valueStr = mEnv->NewStringUTF(value);
145 free(cleaned);
146 if (valueStr == NULL) {
147 mEnv->DeleteLocalRef(nameStr);
148 mEnv->ExceptionClear();
149 return NO_MEMORY;
150 }
151
152 mEnv->CallVoidMethod(
153 mClient, mHandleStringTagMethodID, nameStr, valueStr);
154
155 mEnv->DeleteLocalRef(nameStr);
156 mEnv->DeleteLocalRef(valueStr);
157 return checkAndClearExceptionFromCallback(mEnv, "handleStringTag");
158 }
看131行,就是utf8_length()这个函数调用返回-1,才导致歌曲的名字都设置成了“?”。其实已经测试过了,arm架构返回的值不等于-1.
那就看下函数utf8_length()的实现,找到在frameworks/native/libs/utils/Unicode.cpp中:
364 ssize_t utf8_length(const char *src)
365 {
366 const char *cur = src;
367 size_t ret = 0;
368 while (*cur != '\0') {
369 const char first_char = *cur++;
370 if ((first_char & 0x80) == 0) { // ASCII
371 ret += 1;
372 continue;
373 }
374 // (UTF-8's character must not be like 10xxxxxx,
375 // but 110xxxxx, 1110xxxx, ... or 1111110x)
376 if ((first_char & 0x40) == 0) {
377 return -1;
378 }
379
380 int32_t mask, to_ignore_mask;
381 size_t num_to_read = 0;
382 char32_t utf32 = 0;
383 for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0x80;
384 num_to_read < 5 && (first_char & mask);
385 num_to_read++, to_ignore_mask |= mask, mask >>= 1) {
386 if ((*cur & 0xC0) != 0x80) { // must be 10xxxxxx
387 return -1;
388 }
389 // 0x3F == 00111111
390 utf32 = (utf32 << 6) + (*cur++ & 0x3F);
391 }
392 // "first_char" must be (110xxxxx - 11110xxx)
393 if (num_to_read == 5) {
394 return -1;
395 }
396 to_ignore_mask |= mask;
397 utf32 |= ((~to_ignore_mask) & first_char) << (6 * (num_to_read - 1));
398 if (utf32 > kUnicodeMaxCodepoint) {
399 return -1;
400 }
401
402 ret += num_to_read;
403 }
404 return ret;
405 }
看起来像是判断utf-8的格式,返回-1的地方有多个,测试了一下是在399行返回的,utf32的值大于kUnicodeMaxCodepoint,kUnicodeMaxCodepoint的定义如下:
static const char32_t kUnicodeMaxCodepoint = 0x0010FFFF;
打印了一下(~to_ingnore_mask)、first_char和num_to_read的16进制的值,发现first_char的高位全是F,这个非常不正常。first_char的定义在369行:
const char first_char = *cur++;
一个正常的char类型的高位不是F的,为何有这样的结果?慢慢分析后发现first_char与mask以及to_ingnore_mask这两个变量运算过,它们的类型是int32_t,类型不一样就涉及到类型转换。原来,在char类型扩展到32位时,高位要补0。在arm架构里,char类型的默认扩展是无符号扩展,而在mips是有符号扩展,所以高位全补了1,导致运算出来的utf32的高位全是F,所以它的值大于kUnicodeMaxCodepoint。
如何解决?其实很简单,把first_char声明为无符号类型就可以了:
const unsigned char *first_char = *cur++;
其实这个是google程序员写程序时没注意到的细微的地方,但是在goldfish上跑明显有中文歌曲乱码的bug存在,在这种情况下也把源码发布,这个就有点说不过去。结合之前TraceView Issue 这篇文章,可以看出android在支持mips架构时已经不想花太多的精力。不过市场上的android设备跑mips的确实不多。君正不知道还有没有搞mips的soc!mips注定要没落吗!?