JNI开发总结

作者: SYfarming | 来源:发表于2017-08-18 14:19 被阅读562次

    Android Ndk开发常用网站收集,真正的高手并不是掌握所有的API而是需要的时候可以快速的找到要使用的API。


    基础知识请移步:http://blog.csdn.net/xyang81/article/details/41777471
    JNI动态加载: http://www.cnblogs.com/skywang12345/archive/2013/05/23/3092491.html
    JNI中C调用java的方法:http://www.cnblogs.com/xitang/p/4174619.html
    JNI读取应用签名:https://www.pocketdigi.com/20141129/1398.html
    NDK开发之日志打印:http://blog.csdn.net/u012702547/article/details/48222859


    以上是学习和使用jni常用的几种方式,上述文章内容并不完全正确,稍加修改可正常使用,有需要的可以收藏下。
    这篇文章主要介绍JNI开发中遇到的坑以及解决的方法。



    一.静态注册和动态注册

    为什么需要注册?其实就是给Java的native函数找到底层C,C++实现的函数指针。

    • 静态注册:

    通过包名类名一致来确认,Java有一个命令javah,专门生成某一个JAVA文件所有的native函数的头文件(h文件),步骤如下,我们只说Android项目下如何实施,其实理解了都一样
    静态方法注册JNI有哪些缺点?

    1.必须遵循某些规则
    2.名字过长
    3,多个class需Javah多遍,
    4.运行时去找效率不高

    • 动态注册 :

    在JNi层实现的,JAVA层不需要关心,因为在system.load时就会去掉JNI_OnLoad,有就注册,没就不注册。

    • 区别:

    静态注册是用到时加载,动态注册一开始就加载好了,这个可以从DVM的源代码看出来。


    二.C反射JAVA 的各种方法

    TestClass类包一个构造方法、一个成员方法,一个静态方法,一个内部类,大多数的类都是由这三种方法组成的。下面要做的就是怎么在JNI调用这些方法。

    package com.lb6905.jnidemo;
    
    import android.util.Log;
    
    public class TestClass {
        private final static String TAG = "TestClass";
    
        public TestClass(){
            Log.i(TAG, "TestClass");
        }
    
        public void test(int index) {
            Log.i(TAG, "test : " + index);
        }
    
        public static void testStatic(String str) {
            Log.i(TAG, "testStatic : " + str);
        }
    
        public static class InnerClass {
            private int num;
            public InnerClass() {
                Log.i(TAG, "InnerClass");
            }
    
            public void setInt(int n) {
                num = n;
                Log.i(TAG, "setInt: num = " + num);
            }
        }
    }
    

    查看方法签名

    进入到classpath目录下:
    命令: cd app/build/intermediates/classes/debug

    查看外部类的签名
    javap -s -p com.lb6905.jnidemo.TestClass

    查看内部类的签名
    javap -s -p com.lb6905.jnidemo.TestClass$InnerClass

    结果如下:

    F:\Apps\jniDemo\JNIDemo\app\build\intermediates\classes\debug>javap -s -p com.lb6905.jnidemo.TestClass
    Compiled from "TestClass.java"
    public class com.lb6905.jnidemo.TestClass {
      private static final java.lang.String TAG;
        descriptor: Ljava/lang/String;
      public com.lb6905.jnidemo.TestClass();
        descriptor: ()V
    
      public void test(int);
        descriptor: (I)V
    
      public static void testStatic(java.lang.String);
        descriptor: (Ljava/lang/String;)V
    }
    
    F:\Apps\jniDemo\JNIDemo\app\build\intermediates\classes\debug>javap -s -p com.lb6905.jnidemo.TestClass$InnerClass
    Compiled from "TestClass.java"
    public class com.lb6905.jnidemo.TestClass$InnerClass {
      private int num;
        descriptor: I
      public com.lb6905.jnidemo.TestClass$InnerClass();
        descriptor: ()V
    
      public void setInt(int);
        descriptor: (I)V
    }
    

    在JNI中反射调用上述方法

    JNIEXPORT void JNICALL Java_com_lb6905_jnidemo_MainActivity_JNIReflect
    (JNIEnv *env, jobject thiz)
    {
        //实例化Test类
        jclass testclass = (*env)->FindClass(env, "com/lb6905/jnidemo/TestClass");
        //构造函数的方法名为<init>
        jmethodID testcontruct = (*env)->GetMethodID(env, testclass, "<init>", "()V");
        //根据构造函数实例化对象
        jobject testobject = (*env)->NewObject(env, testclass, testcontruct);
    
        //调用成员方法,需使用jobject对象
        jmethodID test = (*env)->GetMethodID(env, testclass, "test", "(I)V");
        (*env)->CallVoidMethod(env, testobject, test, 1);
    
        //调用静态方法
        jmethodID testStatic = (*env)->GetStaticMethodID(env, testclass, "testStatic", "(Ljava/lang/String;)V");
        //创建字符串,不能在CallStaticVoidMethod中直接使用"hello world!",会报错的
        jstring str = (*env)->NewStringUTF(env, "hello world!");
        //调用静态方法使用的是jclass,而不是jobject
        (*env)->CallStaticVoidMethod(env, testclass, testStatic, str);
    
        //实例化InnerClass子类
        jclass innerclass = (*env)->FindClass(env, "com/lb6905/jnidemo/TestClass$InnerClass");
        jmethodID innercontruct = (*env)->GetMethodID(env, innerclass, "<init>", "()V");
        jobject innerobject = (*env)->NewObject(env, innerclass, innercontruct);
    
        //调用子类的成员方法
        jmethodID setInt = (*env)->GetMethodID(env, innerclass, "setInt", "(I)V");
        (*env)->CallVoidMethod(env, innerobject, setInt, 2);
    }
    

    此处需要注意
    在C中:
    (*env)->方法名(env,参数列表)
    在C++中:
    env->方法名(参数列表)

    三.NewStringUTF函数请慎用

    经常在使用 jstring (NewStringUTF)(JNIEnv, const char*);函数的过程中遇到如下错误
    (1) .JNI DETECTED ERROR IN APPLICATION: input is not valid Modified UTF-8: illegal start byte 0x80

    JNI调用newStringUTF时遇到不认识的字符串就直接出错退出~~,网上原因是dalvik/vm/CheckJni.c里面的checkUtfString函数检查通不过.

    遇到问题时请排查以下几种问题

    • 1. 检查包名反射引用是否正确
    • 2. 检查方法签名,参数签名是否正确
    • 3. 将char * 定义更换为const char *

    (2). JNI DETECTED ERROR IN APPLICATION: use of deleted weak global reference 0xb305f57b

    string 对象和jstring对象从java传值或者新new 的字符串不能够直接引用,必须经过NewStringUTF进行转换,才可

    四.推荐几种修改过的类型转换

    (1)jstring转换为char*

    //jstring转为char* NewStringUTF所需要的内容位char*格式
    const char *jstringTochar(JNIEnv *env, jstring jstr) {
        char *rtn = NULL;
        jclass clsstring = (*env)->FindClass(env, "java/lang/String");
        jstring strencode = (*env)->NewStringUTF(env, "utf-8");
        jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
        jbyteArray barr = (jbyteArray) (*env)->CallObjectMethod(env, jstr, mid, strencode);
        jsize alen = (*env)->GetArrayLength(env, barr);
        jbyte *ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
        if (alen > 0) {
            rtn = (char *) malloc(alen + 1);
            memcpy(rtn, ba, alen);
            rtn[alen] = 0;
        }
        (*env)->ReleaseByteArrayElements(env, barr, ba, 0);
        return rtn;
    }
    

    (2)char * 转为jstring

    jstring chartoJstring(JNIEnv *env, const char *pat) {
        jclass strClass = (*env)->FindClass(env, "java/lang/String");
        jmethodID ctorID = (*env)->GetMethodID(env, strClass, "<init>", "([BLjava/lang/String;)V");
        jbyteArray bytes = (*env)->NewByteArray(env, (jsize) strlen(pat));
        (*env)->SetByteArrayRegion(env, bytes, 0, (jsize) strlen(pat), (jbyte *) pat);
        jstring encoding = (*env)->NewStringUTF(env, "utf-8");
        return (jstring) (*env)->NewObject(env, strClass, ctorID, bytes, encoding);
    }
    

    (3)jstring 转为jbyte*

    // java中的jstring, 转化为c的一个字符数组
    jbyte *Jstring2Jbyte(JNIEnv *env, jstring jstr) {
        jclass clsstring = (*env)->FindClass(env, "java/lang/String");
        jstring strencode = (*env)->NewStringUTF(env, "UTF-8");
        jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
    
        jbyte *barr = (jbyteArray) (*env)->CallObjectMethod(env, jstr, mid,
                                                            strencode); // String .getByte("UTF-8");
        return barr;
    }
    

    以上格式转换大家看到基本上都c反射java进行的格式转换,所以反射这块还是要多多了解的。


    五. 内存管理

    JNI内存管理请参考:https://www.ibm.com/developerworks/cn/java/j-lo-jnileak/
    JNI 编程实现了 native code 和 Java 程序的交互,因此 JNI 代码编程既遵循 native code 编程语言的编程规则,同时也遵守 JNI 编程的文档规范。在内存管理方面,native code 编程语言本身的内存管理机制依然要遵循,同时也要考虑 JNI 编程的内存管理。

    1、什么需要释放?

    什么需要什么呢 ? JNI 基本数据类型是不需要释放的 , 如 jint , jlong , jchar 等等 。 我们需要释放是引用数据类型,当然也包括数组家族。如:jstring ,jobject ,jobjectArray,jintArray 等等。
    当然,大家可能经常忽略掉的是 jclass ,jmethodID , 这些也是需要释放的哦

    2、如何去释放?

    1. 释放String
    jstring jstr = NULL;
    char* cstr = NULL;
    //调用方法
    jstr = (*jniEnv)->CallObjectMethod(jniEnv, mPerson, getName);
    cstr = (char*) (*jniEnv)->GetStringUTFChars(jniEnv,jstr, 0);
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "getName  ---- >  %s",cstr );
    //释放资源
    (*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, cstr);
    (*jniEnv)->DeleteLocalRef(jniEnv, jstr);
    
    1. 释放 类 、对象、方法
    (*jniEnv)->DeleteLocalRef(jniEnv, XXX);//“XXX” 代表 引用对象
    
    1. 释放 数组家族
    jobjectArray arrays = NULL;
    jclass jclsStr = NULL;
    jclsStr = (*jniEnv)->FindClass(jniEnv, "java/lang/String");
    arrays = (*jniEnv)->NewObjectArray(jniEnv, len, jclsStr, 0);
    (*jniEnv)->DeleteLocalRef(jniEnv, jclsStr);  //释放String类
    (*jniEnv)->DeleteLocalRef(jniEnv, arrays); //释放jobjectArray数组
    

    native method 调用 DeleteLocalRef() 释放某个 JNI Local Reference 时,首先通过指针 p 定位相应的 Local Reference 在 Local Ref 表中的位置,然后从 Local Ref 表中删除该 Local Reference,也就取消了对相应 Java 对象的引用(Ref count 减 1)。


    end:以上就是开发中对jni的一些总结,有错误的地方请及时支出。本文仅供参考学习,转载请注明出处。谢谢


    关注微信公众号 Android历练记 或扫一扫二维码:
    让我们一起来搞事情。

    Android历练记

    相关文章

      网友评论

        本文标题:JNI开发总结

        本文链接:https://www.haomeiwen.com/subject/spvorxtx.html