JNI WARNING: input is not valid Modified UTF-8: illegal start byte 0xfe

W/dalvikvm( 1473):              in Landroid/media/MediaScanner;.processFile (Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V (NewStringUTF)
I/dalvikvm( 1473): "MediaScannerService" prio=5 tid=10 NATIVE
I/dalvikvm( 1473):   | group="main" sCount=0 dsCount=0 s=N obj=0x45f1ea18 self=0x22f0d0
I/dalvikvm( 1473):   | sysTid=1647 nice=11 sched=3/0 cgrp=unknown handle=2229456
I/dalvikvm( 1473):   at android.media.MediaScanner.processFile(Native Method)
I/dalvikvm( 1473):   at android.media.MediaScanner.access$500(MediaScanner.java:103)

这个就是蛋疼的地方,

造成该问题的原因是没有通过虚拟机的checkjni检查。代码在dalvik/vm/CheckJni.c

/*
 * Verify that "bytes" points to valid "modified UTF-8" data.
 */
static void checkUtfString(JNIEnv* env, const char* bytes, bool nullOk,
    const char* func)
{
    const char* origBytes = bytes;

    if (bytes == NULL) {
        if (!nullOk) {
            LOGW("JNI WARNING: unexpectedly null UTF string/n");
            goto fail;
        }

        return;
    }

    while (*bytes != '/0') {
        u1 utf8 = *(bytes++);
        // Switch on the high four bits.
        switch (utf8 >> 4) {
            case 0x00:
            case 0x01:
            case 0x02:
            case 0x03:
            case 0x04:
            case 0x05:
            case 0x06:
            case 0x07: {
                // Bit pattern 0xxx. No need for any extra bytes.
                break;
            }
            case 0x08:
            case 0x09:
            case 0x0a:
            case 0x0b:
            case 0x0f: {
                /*
                 * Bit pattern 10xx or 1111, which are illegal start bytes.
                 * Note: 1111 is valid for normal UTF-8, but not the
                 * modified UTF-8 used here.
                 */
                LOGW("JNI WARNING: illegal start byte 0x%x/n", utf8);
                goto fail;
            }
            case 0x0e: {
                // Bit pattern 1110, so there are two additional bytes.
                utf8 = *(bytes++);
                if ((utf8 & 0xc0) != 0x80) {
                    LOGW("JNI WARNING: illegal continuation byte 0x%x/n", utf8);
                    goto fail;
                }
                // Fall through to take care of the final byte.
            }
            case 0x0c:
            case 0x0d: {
                // Bit pattern 110x, so there is one additional byte.
                utf8 = *(bytes++);
                if ((utf8 & 0xc0) != 0x80) {
                    LOGW("JNI WARNING: illegal continuation byte 0x%x/n", utf8);
                    goto fail;
                }
                break;
            }
        }
    }

    return;

fail:
    LOGW("             string: '%s'/n", origBytes);
    showLocation(dvmGetCurrentJNIMethod(), func);
    abortMaybe();
}

解决方法:

是否进行checkjni检查是由ro.kernel.android.checkjni决定。

在eng版本中ro.kernel.android.checkjni=1.而在user, userdebug版本不做检查

In build/core/main.mk


user_variant := $(filter user userdebug,$(TARGET_BUILD_VARIANT))
enable_target_debugging := true
tags_to_install :=
ifneq (,$(user_variant))  

...

ro.kernel.android.checkjni=0

else 

...

ro.kernel.android.checkjni=1



note: 遇到了一次这个问题,kernel层上传的字符窜格式是S8, 长层接收转换时出错。

<think>我们在处理JNI中将C语言的char*转换为Java字符串时,可能会遇到非法ModifiedUTF-8编码的问题,具体错误如“inputisnotvalidModifiedUTF-8:illegalstartbyte0xa0”。这是因为JNI的`NewStringUTF`函数要求传入的字符串必须是ModifiedUTF-8编码,而C语言中的字符串通常是标准UTF-8或普通ASCII(也是UTF-8的子集),但有些字符在标准UTF-8中是合法的,而在ModifiedUTF-8中则不是。###问题原因ModifiedUTF-8与标准UTF-8的主要区别在于:1.**空字符(Null字符)**:标准UTF-8中空字符用单字节`0x00`表示,而ModifiedUTF-8使用双字节`0xC00x80`表示,这样字符串中就不会出现真正的0字节,可以兼容C语言字符串处理(因为C字符串以`\0`结尾)。2.**补充字符(SurrogatePairs)**:在标准UTF-8中,大于0xFFFF的字符(即补充平面字符)用4字节表示,而ModifiedUTF-8使用与UTF-16相同的代理对(SurrogatePairs)方式,即用两个3字节序列表示(共6字节)。3.**某些字节值不允许**:比如0xa0(非中断空格,在标准UTF-8中是合法的)在ModifiedUTF-8中也是合法的,但是错误提示指出0xa0是非法的,这通常是因为该字节出现在了一个不合法的上下文中(例如,它可能是一个不完整的多字节序列的一部分,或者单独出现一个非法的字节)。但实际上,0xa0(即U+00A0,不间断空格)在ModifiedUTF-8中应该被编码为两个字节:`0xC20xA0`。所以,如果我们的字符串中直接出现了0xa0,并且它前面没有0xC2,那么就会被认为非法。然而,错误信息中的0xa0通常表示我们直接传递了一个包含0xa0字节的字符串,而这个0xa0字节在ModifiedUTF-8中是不允许单独出现的(它必须作为两字节序列的一部分)。实际上,0xa0单独出现是合法的,因为它是一个单字节的ASCII扩展字符(在ISO-8859-1中)?但ModifiedUTF-8规定,大于0x7F的字符必须用多字节表示。实际上,标准UTF-80xa0是合法的(U+00A0),它被编码为`0xC20xA0`。所以,如果我们直接传递一个包含0xA0的字符串,而它没有被正确编码为两字节序列,那么就会出错。但是,这里出现0xa0错误的原因可能是:我们的字符串实际上是使用ISO-8859-1(Latin-1)或其他单字节编码,而不是UTF-8。例如,在ISO-8859-1中,0xA0代表不换行空格,而如果我们直接将它作为UTF-8字节传递,它不是一个有效的UTF-8序列(因为UTF-8要求大于127的字符必须由多字节表示)。因此,我们需要确保整个字符串是有效的UTF-8编码。###解决方案1.**确保源字符串是UTF-8编码**:如果源字符串是其他编码(如ISO-8859-1、GBK等),我们需要先将其转换为UTF-8编码(标准UTF-8),然后再传递给`NewStringUTF`。2.**手动转换非法字符**:如果我们知道字符串中可能包含一些非UTF-8字符,我们可以尝试过滤或转换这些字符。3.**使用`NewString`代替`NewStringUTF`**:我们可以使用`NewString`函数,它接受UTF-16编码的字符数组(jchar数组)。这样,我们可以将C字符串先转换为UTF-16,然后再创建Java字符串。这种方法更灵活,因为我们可以使用标准库(如iconv)进行转换。###具体实现####方法1:转换源字符串为UTF-8使用如`iconv`库将源字符串从当前编码(如ISO-8859-1)转换为UTF-8。示例代码:```c#include<iconv.h>#include<stdlib.h>#include<string.h>//将字符串从指定编码转换为UTF-8char*convert_to_utf8(constchar*input,constchar*from_encoding){iconv_tcd=iconv_open("UTF-8",from_encoding);if(cd==(iconv_t)-1){//处理错误returnNULL;}size_tinbytes=strlen(input);size_toutbytes=inbytes*4;//最大可能为4倍char*output=(char*)malloc(outbytes+1);if(!output){iconv_close(cd);returnNULL;}char*inptr=(char*)input;char*outptr=output;if(iconv(cd,&inptr,&inbytes,&outptr,&outbytes)==(size_t)-1){free(output);iconv_close(cd);returnNULL;}//结束转换iconv_close(cd);*outptr='\0';returnoutput;}```然后在JNI中使用转换后的字符串:```cjstringstr;constchar*c_str=...;//原始字符串char*utf8_str=convert_to_utf8(c_str,"ISO-8859-1");//假设原始编码是ISO-8859-1if(utf8_str){str=(*env)->NewStringUTF(env,utf8_str);free(utf8_str);}else{//转换失败处理}```####方法2:使用`NewString`和UTF-16我们可以将C字符串转换为UTF-16编码(jchar数组),然后使用`NewString`。示例代码(使用iconv转换为UTF-16):```c#include<iconv.h>#include<stdlib.h>#include<string.h>//将字符串转换为UTF-16(大端序或小端序,JNI使用平台字节序)jstringconvert_to_jstring(JNIEnv*env,constchar*input,constchar*from_encoding){iconv_tcd=iconv_open("UTF-16LE",from_encoding);//或者UTF-16BE,根据平台。通常小端序(LE)是常见的。if(cd==(iconv_t)-1){returnNULL;}size_tinbytes=strlen(input);size_toutbytes=(inbytes+1)*2;//每个字符最多2字节,加上BOM可能,但通常不需要BOMchar*output=(char*)malloc(outbytes);if(!output){iconv_close(cd);returnNULL;}char*inptr=(char*)input;char*outptr=output;if(iconv(cd,&inptr,&inbytes,&outptr,&outbytes)==(size_t)-1){free(output);iconv_close(cd);returnNULL;}iconv_close(cd);//计算转换后的字符数(UTF-16字符数,注意每个字符2字节)jsizelen=(outptr-output)/2;jstringresult=(*env)->NewString(env,(constjchar*)output,len);free(output);returnresult;}```注意:`NewString`要求传入的数组是jchar数组(即UTF-16编码),并且我们转换时使用`UTF-16LE`(小端序)或`UTF-16BE`(大端序)需要与平台匹配。JNI环境通常使用平台字节序,所以使用`UTF-16LE`在x86系统上是安全的。####方法3:使用Java的String构造函数(如果字符串较短)如果字符串较短,我们可以先将其转换为一个字节数组,然后在Java端使用`String`构造函数指定编码来创建字符串。JNI部分:```c//假设我们有一个字节数组和它的长度,以及编码名称jbyteArraybyteArray=(*env)->NewByteArray(env,len);(*env)->SetByteArrayRegion(env,byteArray,0,len,(jbyte*)c_str);//然后调用Java的String构造函数jclassstringClass=(*env)->FindClass(env,"java/lang/String");jmethodIDctor=(*env)->GetMethodID(env,stringClass,"<init>","([BLjava/lang/String;)V");jstringencoding=(*env)->NewStringUTF(env,"ISO-8859-1");//原始编码jstringresult=(jstring)(*env)->NewObject(env,stringClass,ctor,byteArray,encoding);```###总结遇到`NewStringUTF`因非法字节0xa0而失败时,最可能的原因是字符串不是有效的UTF-8编码(例如,它是ISO-8859-1编码)。解决方案包括:1.将字符串转换为有效的UTF-8编码(使用iconv等库)。2.使用`NewString`并转换为UTF-16编码。3.在Java端使用指定编码构造字符串。选择哪种方法取决于具体情况。如果性能要求高且字符串较长,方法1或2更优;如果字符串较短且不想在本地代码中处理编码转换,方法3更简单。[^1]:参考JNI内部检查函数`checkUtfString`的代码,它对传入的字符串进行ModifiedUTF-8有效性检查。§§相关问题§§1.如何检测C字符串的编码类型?2.JNI中除了`NewStringUTF`和`NewString`,还有哪些创建Java字符串的方法?3.在JNI中处理字符串时,如何避免内存泄漏?</think>### JNI中char*转换为Java字符串时解决非法Modified UTF-8编码问题 在JNI中将C语言的`char*`转换为Java字符串时遇到`NewStringUTF`报错"illegal start byte 0xa0",这是因为标准UTF-8JNI要求的**Modified UTF-8**编码存在差异。Modified UTF-8的特殊规则包括: 1. 空字符`\0`必须用双字节`0xC080`表示(而非单字节`0x00`) 2. 补充字符使用代理对编码(surrogate pairs) 3. 禁止单独出现某些高位字节(如`0xA0`需作为多字节序列的一部分) #### 解决方案(C语言端处理) ##### 方法1:编码转换(推荐) 使用`iconv`库将字符串转换为Modified UTF-8: ```c #include <iconv.h> #include <stdlib.h> jstring charToJString(JNIEnv* env, const char* nativeStr) { iconv_t cd = iconv_open("UTF-8//IGNORE", "ISO-8859-1"); // 根据实际编码调整 if (cd == (iconv_t)-1) return NULL; size_t in_len = strlen(nativeStr); size_t out_len = in_len * 4; // 预留足够空间 char* utf8_buf = (char*)malloc(out_len); char* in_ptr = (char*)nativeStr; char* out_ptr = utf8_buf; if (iconv(cd, &in_ptr, &in_len, &out_ptr, &out_len) == (size_t)-1) { free(utf8_buf); iconv_close(cd); return NULL; } jstring jstr = (*env)->NewStringUTF(env, utf8_buf); free(utf8_buf); iconv_close(cd); return jstr; } ``` ##### 方法2:手动过滤非法字节 ```c jstring safeNewStringUTF(JNIEnv* env, const char* str) { char* buf = (char*)malloc(strlen(str) * 2 + 1); // 双倍缓冲区 char* p = buf; while (*str) { if ((*str & 0x80) == 0) { // ASCII字符 *p++ = *str++; } else if ((*str & 0xE0) == 0xC0) { // 2字节序列 *p++ = *str++; *p++ = *str++; } else if ((*str & 0xF0) == 0xE0) { // 3字节序列 *p++ = *str++; *p++ = *str++; *p++ = *str++; } else { // 非法字节(如0xA0)替换为? str++; *p++ = '?'; } } *p = '\0'; jstring jstr = (*env)->NewStringUTF(env, buf); free(buf); return jstr; } ``` #### 解决方案(Java端处理) ##### 方法3:通过字节数组构造字符串 ```c // JNI部分 jbyteArray bytes = (*env)->NewByteArray(env, strlen(c_str)); (*env)->SetByteArrayRegion(env, bytes, 0, strlen(c_str), (jbyte*)c_str); // Java部分 public native byte[] getNativeData(); // 调用处 byte[] data = getNativeData(); String result = new String(data, "ISO-8859-1"); // 指定正确编码 ``` #### 关键注意事项 1. **编码识别**:确认C字符串的实际编码(如ISO-8859-1、Windows-1252等) 2. **内存管理**:及时释放临时分配的内存 3. **错误处理**:检查`NewStringUTF`返回的`NULL`值 4. **代理字符**:包含补充字符时需特殊处理代理对 > 当遇到`0xA0`错误时,通常表示字符串包含ISO-8859-1中的**不换行空格**(NO-BREAK SPACE),需确保其被正确转换为UTF-8的双字节序列`0xC2A0`[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值