JNI开发总结

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开发中遇到的坑以及解决的方法。


JNI开发总结_第1张图片

一.静态注册和动态注册

为什么需要注册?其实就是给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");
    //构造函数的方法名为
    jmethodID testcontruct = (*env)->GetMethodID(env, testclass, "", "()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, "", "()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, "", "([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历练记 或扫一扫二维码:
让我们一起来搞事情。

JNI开发总结_第2张图片
Android历练记

你可能感兴趣的:(JNI开发总结)