【问题标题】:Android ICS 4.0 NDK NewStringUTF is crashing down the AppAndroid ICS 4.0 NDK NewStringUTF 正在崩溃应用程序
【发布时间】:2012-08-21 02:31:15
【问题描述】:

我在 JNI C/C++ 中有一个方法,它接受 jstring 并返回 jstring,如下所示,

  NATIVE_CALL(jstring, method)(JNIEnv * env, jobject obj, jstring filename)
  {

// Get jstring into C string format.
  const char* cs = env->GetStringUTFChars (filename, NULL);
  char *file_path = new char [strlen (cs) + 1]; // +1 for null terminator
  sprintf (file_path, "%s", cs);
  env->ReleaseStringUTFChars (filename, cs);


  reason_code = INTERNAL_FAILURE;
  char* info = start_module(file_path);  


  jstring jinfo ;


  if(info==NULL)
  {
      jinfo = env->NewStringUTF(NULL);
  }
  else
  {
      jinfo = env->NewStringUTF(info);

  }


  delete info;

  info = NULL;
  return jinfo;
  }

该代码与之前的 android 4.0 版本(如 2.2、2.3 等)完美配合。使用 ICS 4.0 检查 JNI 默认情况下处于启用状态,因此应用程序崩溃并引发以下错误

 08-25 22:16:35.480: W/dalvikvm(24027): **JNI WARNING: input is not valid Modified UTF-8: illegal  continuation byte 0x40**
08-25 22:16:35.480: W/dalvikvm(24027):              
08-25 22:16:35.480: W/dalvikvm(24027): ==========
08-25 22:16:35.480: W/dalvikvm(24027): /tmp/create
08-25 22:16:35.480: W/dalvikvm(24027): ==========
08-25 22:16:35.480: W/dalvikvm(24027): databytes,indoorgames,drop
08-25 22:16:35.480: W/dalvikvm(24027): ==========���c_ag����ϋ@�ډ@�����@'
 08-25 22:16:35.480: W/dalvikvm(24027):              in Lincom/inter       /ndk/comNDK;.rootNDK:(Ljava/lang/String;)Ljava/lang/String; **(NewStringUTF)**
08-25 22:16:35.480: I/dalvikvm(24027): "main" prio=5 tid=1 NATIVE
08-25 22:16:35.480: I/dalvikvm(24027):   | group="main" sCount=0 dsCount=0 obj=0x40a4b460   self=0x1be1850
08-25 22:16:35.480: I/dalvikvm(24027):   | sysTid=24027 nice=0 sched=0/0 cgrp=default handle=1074255080
08-25 22:16:35.490: I/dalvikvm(24027):   | schedstat=( 49658000 26700000 48 ) utm=1 stm=3 core=1
08-25 22:16:35.490: I/dalvikvm(24027):   at comrootNDK(Native Method)

我不知道我错在哪里。如果您在上面看到 NewStringUTF 正在向 c Char* 字节添加一些垃圾值。

  1. 知道为什么会这样
  2. 欢迎任何实现上述目标的替代解决方案

如果你们中的任何一个可以帮助我,我真的很感激。提前致谢

规则 我

【问题讨论】:

  • 由于在未初始化的 char 数组上调用 strcat(),我遇到了同样的错误,但问题在数组初始化后得到解决。

标签: android android-ndk java-native-interface android-4.0-ice-cream-sandwich android-ndk-r7


【解决方案1】:

此问题的原因与known UTF-8 bug in the NDK/JNI GetStringUTFChars() function 直接相关(可能还与NewStringUTF 等相关函数有关)。这些 NDK 函数无法正确转换 supplementary Unicode characters(即 U+10000 及以上值的 Unicode 字符)。这会导致错误的 UTF-8 和随后的崩溃。

我在处理包含表情符号字符 (see the corresponding Unicode chart) 的用户输入文本时遇到了崩溃。表情符号字符位于补充 Unicode 字符范围内。

问题分析

  1. Java 客户端传递一个包含补充 Unicode 的字符串 JNI/NDK 的字符。
  2. JNI 使用 NDK 函数 GetStringUTFChars() 来提取 Java 字符串的内容。
  3. GetStringUTFChars() 将字符串数据返回为不正确且无效的 UTF-8。

There is a known NDK bug 由此 GetStringUTFChars() 错误地转换补充 Unicode 字符,产生不正确且无效的 UTF-8 序列。

在我的例子中,生成的字符串是一个 JSON 缓冲区。当缓冲区被传递给 JSON 解析器时,解析器立即失败,因为提取的 UTF-8 的 UTF-8 字符之一具有无效的 UTF-8 前缀字节。

可能的解决方法

我用过的方案可以总结如下:

  1. 目标是防止 GetStringUTFChars() 执行 补充 Unicode 字符的 UTF-8 编码不正确。
  2. 这是由 Java 客户端将请求字符串编码为 Base64。
  3. Base64 编码的请求被传递给 JNI。
  4. JNI 调用 GetStringUTFChars(),它提取 Base64 编码的 字符串而不执行任何 UTF-8 编码。
  5. JNI 代码然后解码 Base-64 数据,生成原始 UTF-16(宽字符)请求字符串,包括补充 Unicode 字符。

通过这种方式,我们规避了从 Java 字符串中提取补充 Unicode 字符的问题。相反,我们在调用 GetStringUTFChars() 之前将数据转换为 Base-64 ASCII,使用 GetStringUTFChars() 提取 Base-64 ASCII 字符,然后将 Base-64 数据转换回宽字符。

【讨论】:

  • 非常有帮助,谢谢!查看Lollipop source code,看起来 NewStringUTF() 进行了检查(entry == true),而 GetStringUTFChars() 没有,因此它的行为不同。
  • byte[] byteArray =Base64.encodeBase64(messageText.getText().toString().trim().getBytes());字符串编码字符串 = 新字符串(字节数组);这种方式将字符串转换为 Base64 并将字符串传递给 jni 但我的应用程序仍然崩溃。有什么想法吗?
  • (1) 您的崩溃的性质是什么?任何带有符号的堆栈跟踪? (2) 转base-64前的十六进制转储,以及JNI中GetStringUTFChars()返回的数据的十六进制转储,能否发布?
  • 应用程序中检测到 JNI 错误:输入无效修改的 UTF-8:非法继续字节 0xc5 字符串:'Accepted�Ś�7' 调用 NewStringUTF 我正在发送 v
  • 同一个文本应用有时会崩溃,有时不会。
【解决方案2】:

我就是这样做的。

1- 字符数组到 JByteArray。

2- JByteArray 到 JString。

3- 将 jstring 返回到 java 端。

JNI 代码; (.c) 格式

jstring Java_com_x_y_z_methodName(JNIEnv *env, jobject thiz) {
    int size = 16;
    char r[] = {'P', 'K', 'd', 'h', 't', 'X', 'M', 'm', 'r', '1', '8', 'n', '2', 'L', '9', 'K'};
    jbyteArray array = (*env)->NewByteArray(env, size);
    (*env)->SetByteArrayRegion(env, array, 0, size, r);
    jstring strEncode = (*env)->NewStringUTF(env, "UTF-8");
    jclass cls = (*env)->FindClass(env, "java/lang/String");
    jmethodID ctor = (*env)->GetMethodID(env, cls, "<init>", "([BLjava/lang/String;)V");
    jstring object = (jstring) (*env)->NewObject(env, cls, ctor, array, strEncode);

    return object;
}

Java 代码;

native String methodName();

其他方法不适合我;

我也尝试过return (*env)-&gt;NewStringUTF(env, r),但返回一些不在 char 数组中的字符,在字符串的末尾带有 JNI WARNING: input is not valid 修改的 UTF-8: 非法延续字节的警告0x40

示例; PKdhtXMmr18n2L9K………………-DL

编辑:

C++ 版本

jstring clientStringFromStdString(JNIEnv *env,const std::string &str){
//    return env->NewStringUTF(str.c_str());
    jbyteArray array = env->NewByteArray(str.size());
    env->SetByteArrayRegion(array, 0, str.size(), (const jbyte*)str.c_str());
    jstring strEncode = env->NewStringUTF("UTF-8");
    jclass cls = env->FindClass("java/lang/String");
    jmethodID ctor = env->GetMethodID(cls, "<init>", "([BLjava/lang/String;)V");
    jstring object = (jstring) env->NewObject(cls, ctor, array, strEncode);
    return object;
}

【讨论】:

  • 上述解决方案为不同的 ABI 返回不同的结果,例如对于支持 arm64-v8a,armeabi-v7a,armeabi 的设备,它返回完美字符串,但其他类型的设备返回字符串,如 PKdhtXMmr18n2L9K�ؾ�����-DL
【解决方案3】:

我通过返回字节数组而不是字符串解决了这个问题。在 Java 方面,我现在将 Byte 数组转换为 Strings 。工作正常!避免在 Android 4.0 及更高版本中使用 NewStringUTF(),因为 Google Android NDK 上已经报告了一个错误。

【讨论】:

  • @rana 请提供代码 - 如何在 java 端将 byte[] 转换为 String。我总是遇到编码问题。
  • 如果你不想到处更改你的 jni 代码,你可以从 jni 中做 new String(byte_array, "utf-8") (在 jni 中获取 String 构造函数,并从 jni 中调用它) .
  • Jni 文件在 .so 中,所以我无法编辑它们以接受 byearry 而不是字符串。所以我需要对字符串进行编码或做一些工作。请问有什么解决办法吗?
【解决方案4】:

我在更改文件 Application.mk 时遇到了这个问题

从这一行开始:

APP_STL := stlport_static

收件人:

APP_STL := gnustl_static

一旦我把它改回来,它就解决了这个问题。

【讨论】:

  • 好吧,事情可能在两年半内发生了变化:)但我再也没有遇到过这个问题。
【解决方案5】:

您传递给 NewStringUTF() 的字符串需要有效修改 UTF-8。看起来您的 start_Inauthroot() 函数返回的字符串采用其他编码,或者只是返回无效字符串。在将字符串传递给 JNI 函数之前,您需要将其转换为 UTF-8。或者您可以使用 charset-aware String constructors 之一来构建 String 对象。

【讨论】:

  • 很抱歉再次打扰您..但是如何做到这一点..您对我的以下代码有什么建议吗,jclass strClass = env->FindClass("java/lang/String"); jmethodID ctorID = env->GetMethodID(strClass, "", "([BLjava/lang/String;)V"); NewStringUTF("UTF-8"); jbyteArray bytes = env->NewByteArray(strlen(rInfo)); env->SetByteArrayRegion(bytes, 0, strlen(rInfo), (jbyte*)rootInformation); grootInfo = (jstring)env->NewObject(strClass, ctrId, bytes,"UTF-8");
  • 是的,看起来不错,只是您需要将“编码”jstring var 传递给 NewObject(),而不是原始字符串“UTF-8”。当然,您需要传递字符串的实际编码,而不仅仅是“UTF-8”。
  • 但是它在这一行抛出一个错误, env->GetMethodID(strClass, "", "([BLjava/lang/String;)V");知道应该是什么原因吗?
  • 在这一行抛出错误, env-&gt;GetMethodID(strClass, "&lt;init&gt;", "([BLjava/lang/String;)V"); 请注意,在 4.0 及更高版本中,FindClass() 的答案是本地引用。
  • 是的,我明白了;这就是@rana 所说的。但是,当我运行它时,那段代码工作得很好。我的问题是: what 抛出错误? NoSuchMethodError?还有什么? FindClass() 返回本地引用不是问题,因为 strClass 在本地范围内立即使用。
【解决方案6】:

在我看来,这不是错误。

NewStringUTF 从修改后的 UTF-8 编码的字符数组构造一个新的 java.lang.String 对象。

修改后的 UTF-8 不是标准的 UTF-8。见Modified UTF-8

在大多数情况下,UTF-8 编码的字符串是有效的修改后的 UTF-8。因为修改后的 UTF-8 和 UTF-8 非常相似。但是,当涉及到基本多语言平面之外的 Unicode 字符串时,它们是不兼容的。

解决方案: 将 UTF-8 字节传递给 Java 层和 new String(bytes, "UTF-8") 然后将 jstring 传递给 JNI。

【讨论】:

    【解决方案7】:

    对我来说,解决方案是将内容放在 const char* 上:

    const char* string = name_sin.c_str();
    jstring utf8 = env_r->NewStringUTF(string);
    

    和功能:

    jclass cls_Env = env_r->FindClass(CLASS_ACTIVITY_NAME); 
    jmethodID mid = env_r->GetMethodID(cls_Env, "Delegate",
                                     "(Ljava/lang/String;)V");
    
    
    //todo importante hacerlo asi, si pasas directamente c_str a veces da error de carater no UTF 8
    const char* string = name_sin.c_str();
    jstring utf8 = env_r->NewStringUTF(string);
    
    env_r->CallVoidMethod(*object_r, mid, utf8);
    
    env_r->DeleteLocalRef(utf8);
    

    【讨论】:

      【解决方案8】:

      从前一天起,我也遇到了同样的问题。一天后终于想出了一个解决方案..我希望这个回复可以拯救某人的一天..

      问题是我在本机函数中调用了另一个函数,直接使用返回的字符串,导致 android 旧版本崩溃

      所以我首先将另一个函数返回的字符串保存到一个变量中,然后使用它,问题就消失了:D

      下面的例子可能会清除你的概念

      //older code with error
      //here key_ is the string from java code
      
      const char *key = env->GetStringUTFChars(key_, 0);
      const char *keyx = getkey(key).c_str();
      return env->NewStringUTF(keyx);
      

      这是我解决这个错误的方法

      //newer code which is working
      //here key_ is the string from java code
      
      const char *key = env->GetStringUTFChars(key_, 0);
      string k = getkey(key);
      const char *keyx = k.c_str();
      return env->NewStringUTF(keyx);
      

      快乐编码:D

      【讨论】:

        【解决方案9】:

        这在 C++ 中适用于我

        extern "C" JNIEXPORT
        jstring Java_com_example_ndktest_MainActivity_TalkToJNI(JNIEnv* env, jobject javaThis, jstring strFromJava)
        {
            jboolean isCopy;
            const char* szHTML = env->GetStringUTFChars(strFromJava, &isCopy);
        
            std::string strMine;
            strMine = szHTML;
            strMine += " --- Hello from the JNI!!";
        
            env->ReleaseStringUTFChars(strFromJava, szHTML);
            return env->NewStringUTF(strMine.c_str());
        }
        

        【讨论】:

          【解决方案10】:

          candroid ndk 工作如下

          JNIEXPORT jstring JNICALL
          Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                                            jobject thiz,jstring str )
          {
          
              jboolean isCopy;
              const char* szHTML = (*env)->GetStringUTFChars(env, str, &isCopy);
               return (*env)->NewStringUTF(env, szHTML);
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-04-20
            • 1970-01-01
            • 2012-09-26
            • 2012-10-04
            • 1970-01-01
            相关资源
            最近更新 更多