android在Java层对 utf 编码是支持得很好了,非常全面;但当你从事一些c/c++工程的开发时可就没有这么幸运了。 笔者最近在使用v8 javascript 引擎时便碰到了一个问题:
有些用户在昵称中使用了 emoji 表情,v8 引擎内部默认会使用utf-16编码,通过 v8 API 取到这个值之后转为utf-8的字符串,进而通过 JNI 的 API JNIEnv->NewStringUTF 往 Java 传递时会被系统的 checkJNI 给拦截住而报错。原因在为了让字符串中不包含任何 null 字节,JNI 以及 Java VM 内部都是使用的Modified UTF-8格式来编码字符串。
后来找到一个办法可以通过将字符串转为 utf-16 编码后传递给 JNI API JNIEnv->NewString 解决之:
- 使用 v8 API 将 utf-8 的字符串转为 utf-16 编码
size_t utf8_to_utf16(const char *src, const uint16_t **dest) { if (src == NULL || dest == NULL) { return 0; } Isolate::Scope scope(Isolate::GetCurrent()); HandleScope handle_scope; Local<String> str = String::New(src); String::Value val(str); // String::Value的内部编码是 utf-16 const size_t len = (val.length()) * sizeof(uint16_t); uint16_t* target = (uint16_t*)calloc(val.length() + 1, sizeof(uint16_t)); if (target == NULL) { return 0; } memcpy(target, *val, len); *dest = target; return val.length(); }
- 将生成的 utf-16 字符串通过 JNI 传递到 Java 层
uint16_t *utf16_action = NULL; size_t len = utf8_to_utf16(action, &utf16_action); jstring jText = (*env)->NewString(env, utf16_action, len); if (utf16_action != NULL) { free(utf16_action); } if (len == 0) { jText = (*env)->NewStringUTF(env, ""); // 降级到使用NewStringUTF来创建一个""字符串 }
另外一种解决方法是通过 byteArray 来将 utf-8 编码的字符串传送到java层,