博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JNI 最佳实践
阅读量:6191 次
发布时间:2019-06-21

本文共 14635 字,大约阅读时间需要 48 分钟。

阅读本文前,请先阅读

简介

本文demo功能如下,如需源码,点击下载

实践一:从C里返回String给java

之前我们实现了从C代码里返回了一个字符串,代码如下:

  1. java代码,定义native函数

    public class Jni {    static {        System.loadLibrary("best");    }    public native String sayHello();}复制代码
  2. javah 生成jni样式的标准头文件

    这里略去了生成jni_study_com_jnibsetpractice_Jni.h的具体,下面会再次提到

  3. c代码,实现java里的native方法

    # include "jni_study_com_jnibsetpractice_Jni.h"JNIEXPORT jstring JNICALL Java_jni_study_com_jnibsetpractice_Jni_sayHello        (JNIEnv *env, jobject instance) {    return (*env)->NewStringUTF(env, "Hello from C");}复制代码
  4. java里调用native方法

    bt_1.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                String hello = jni.sayHello();                Toast.makeText(MainActivity.this,hello,Toast.LENGTH_LONG).show();            }        });复制代码

实践二:java传int参数给C处理,并且返回int

  1. java代码,定义native函数

    public class Jni {    static {        System.loadLibrary("best");    }    public native int add(int x,int y);}复制代码
  2. javah 生成jni样式的标准头文件

    切换到src/main/java目录下执行

    javah -d ../jni jni.study.com.jnibsetpractice.Jni复制代码

    注意:javah 后半段跟的是 这个native方法所在的类的全路径名称

    打开这个文件,发现生成了java里的这个native方法的jni方法签名

    JNIEXPORT jint JNICALL Java_jni_study_com_jnibsetpractice_Jni_add  (JNIEnv *, jobject, jint, jint);复制代码
    public native int add(int x,int y);复制代码

    对比一下:

    1. 返回值:int对应jint
    2. 方法名:add对应Java_jni_study_com_jnibsetpractice_Jni_add
    3. 参数列表(int ,int )对应(JNIEnv *, jobject, jint, jint)

      这里说明一下

      参数JNIEnv * :jni的环境,通过它来调用内置的很多jni方法

      参数jobject:代表在java里调用这个native方法的实例对象(如果是静态方法代表的是类) 参数jint, jint:对应 int x,int y

      JNIEnv是什么在文章里有详细解释

      jint和int关系在文章里也有详细解释

  3. c代码,实现java里的native方法

    # include "jni_study_com_jnibsetpractice_Jni.h"JNIEXPORT jint JNICALL Java_jni_study_com_jnibsetpractice_Jni_add        (JNIEnv * env, jobject instance, jint x, jint y){    return x+y;}复制代码
  4. Java调用native方法

    bt_2.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                int add = jni.add(1, 2);                Toast.makeText(MainActivity.this, "1+2=" + add, Toast.LENGTH_LONG).show();            }        });复制代码

ok,完成了,是不是很简单,如果java传递string给C也是这么简单吗,记得吗java里的string与c里的string可是不一样哟

实践三:java传String参数给C处理,并且返回String

  1. java代码,定义native函数
public class Jni {    static {        System.loadLibrary("best");    }    public native String transe_string(String str);}复制代码
  1. javah 生成jni样式的标准头文件

生成文件的方法略,直接分析这个文件

JNIEXPORT jstring JNICALL Java_jni_study_com_jnibsetpractice_Jni_transe_1string  (JNIEnv *, jobject, jstring);复制代码
public native String transe_string(String str);复制代码

对比一下: jstring对应String

看看jstring是什么

  1. c代码,实现java里的native方法

java传给c一个string,javah生成了方法名后, 发现传递来的是一个jstring(因为在c里,是没有string的), jstring其实是void*(任意类型), 我们需要调用一个方法,把jstring转为C语言的char*类型,先看下这个工具方法:

#include 
/** * 把一个jstring转换成一个c语言的char* 类型. */char* _JString2CStr(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = (*env)->FindClass(env, "java/lang/String"); jstring strencode = (*env)->NewStringUTF(env,"GB2312"); jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312"); jsize alen = (*env)->GetArrayLength(env, barr); jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE); if(alen > 0) { rtn = (char*)malloc(alen+1); //"\0" memcpy(rtn, ba, alen); rtn[alen]=0; } (*env)->ReleaseByteArrayElements(env, barr, ba,0); return rtn;}复制代码

实现native方法

JNIEXPORT jstring JNICALL Java_jni_study_com_jnibsetpractice_Jni_transe_1string        (JNIEnv *env, jobject instance, jstring jstr) {    //把一个jstring转换成一个c语言的char* 类型    char *cStr = _JString2CStr(env, jstr);    //c语言拼接字符串    char *cNewStr = strcat(cStr, "简单加密一下哈哈哈!!!");    // 把c语言里的char* 字符串转成java认识的字符串    return (*env)->NewStringUTF(env, cNewStr);}复制代码
  1. Java调用native方法
bt_3.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                String str = jni.transe_string("abc");                Toast.makeText(MainActivity.this, "abc   ====c转换=====>   " + str, Toast.LENGTH_LONG).show();            }        });复制代码

实践四:java传递给C int数组

  1. java代码,定义native函数
public class Jni {    static {        System.loadLibrary("best");    }    public native int[] transeIntArray(int[] intArray);}复制代码
  1. javah 生成jni样式的标准头文件

生成jni头文件的方法 略

JNIEXPORT jintArray JNICALL Java_jni_study_com_jnibsetpractice_Jni_transeIntArray  (JNIEnv *, jobject, jintArray);复制代码
public native int[] transeIntArray(int[] intArray);复制代码

jintArray对应int[]

  1. c代码,实现java里的native方法
JNIEXPORT jintArray JNICALL Java_jni_study_com_jnibsetpractice_Jni_transeIntArray        (JNIEnv *env, jobject instance, jintArray jArray) {//    得到从java传递来的int[]的长度    jsize length = (*env)->GetArrayLength(env, jArray);//    得到数组指针    int *arrayPointer = (*env)->GetIntArrayElements(env, jArray, NULL);//    开始遍历数组,把每个元素加100    int i;    for (i = 0; i < length; i++) {        *(arrayPointer + i) += 100;    }// ★★★★将arrayPointer这个int *中值复制到jArray数组中,别忘了这一步骤★★★    (*env)->SetIntArrayRegion(env,jArray, 0, length, arrayPointer);//    返回数组    return jArray;}复制代码
  1. Java调用native方法
bt_4.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                int[] intArray = new int[]{1, 2, 3};                int[] intArrayTranse = jni.transeIntArray(intArray);                for (int i = 0; i < intArray.length; i++) {                    //发现在这里遍历旧的数组,打印出来也是加过100后的数字,101,102,103                    // 因为C是通过指针操作内存地址,改变了intArray的内存地址的值,                    // 所以C里面不返回int[]也是可以实现目的的                    Log.e("abc",intArray[i]+"old");                }                for (int i = 0; i < intArrayTranse.length; i++) {                    Log.e("abc",intArrayTranse[i]+"new");                }//这里打印的是101,102,103            }        });复制代码

log如下

实践五:C调Java 空参数方法

前面讲的都是java调用C的方法,有时候我们需C里调用Java的方法,

例如:我们有一个操作图片的方法是C写的,在C操作图片是,需要让android弹出一个进度条,展示图片操作的进度,这时候,就需要C代码里调用java代码,来改变UI了

C调java,其实用的反射,先复习一下java的反射

public class Animal {    public String say(String s) {        Log.e("tag", s);        return s;    }}复制代码
// 1. 获取字节码对象Class
clazz = Animal.class;// 2. 获取method方法的对象try { Method say = clazz.getDeclaredMethod("say", String.class); // 3. 通过字节码获取Animal的实例 Animal animal = clazz.newInstance(); // 4.通过实例调用方法 say.invoke(animal, "hello!");} catch (Exception e) { e.printStackTrace();}复制代码

ok,现在正式开始C调用java

  1. java代码,定义native函数
public class Jni {    static {        System.loadLibrary("best");    }        //    这个方法要去调用C代码    public native void callBackMethodVoid();        //  我是java实现的方法,等待C里的callBackMethodVoid方法回调我    public void helloFromJava() {        Log.e("tag", "hello from java");    }    }复制代码
  1. javah 生成jni样式的标准头文件

生成jni头文件的方法 略

  1. c代码,实现java里的native方法
JNIEXPORT void JNICALL Java_jni_study_com_jnibsetpractice_Jni_callBackMethodVoid        (JNIEnv *env, jobject instance) {    // 1. 获取字节码对象    jclass clazz = (*env)->FindClass(env, "jni/study/com/jnibsetpractice/Jni");    // 2. 获取method方法的对象    jmethodID methodID = (*env)->GetMethodID(env, clazz, "helloFromJava", "()V");    // ★因为java方法有重载,所以只是靠方法名找不到这个唯一的函数,所以需要最后一个参数    // 注意最后一个参数"()V",这个代表了方法的参数,这里无参数用(),V代表了方法的返回值,这里是void,所以是V    //   ★★★★★★★★★★★重点理解  第二个参数 instance★★★★★★★★★★★★★    // 3. 通过字节码获取Animal的实例,    // 这里不要创建这个实例,因为上面的第二个参数 jobject instance,    // 代表的就是调用该方法的对象    // 在本例中是通过 jni.callBackMethodVoid()调用Java_jni_study_com_jnibsetpractice_Jni_callBackMethodVoid的    //那么instance就是jni(Jni的对象)    //   ★★★★★★★★★★★重点理解  第二个参数 instance★★★★★★★★★★★★★    //  4.通过实例调用java里的方法    (*env)->CallVoidMethod(env, instance, methodID);}复制代码
  1. Java调用native方法
bt_6.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                jni.callBackMethodVoid();            }        });复制代码

实践六:C调Java int类型参数方法

  1. java代码,定义native函数
public class Jni {    static {        System.loadLibrary("best");    }    //    这个方法要去调用C代码    public native int callBackMethodInt();    //  我是java实现的方法,等待C里的callBackMethodInt方法回调我    public int addInJava(int x, int y) {        return x + y;    }}复制代码
  1. javah 生成jni样式的标准头文件

  2. c代码,实现java里的native方法

JNIEXPORT jint JNICALL Java_jni_study_com_jnibsetpractice_Jni_callBackMethodInt        (JNIEnv *env, jobject instance) {    // 1. 获取字节码对象    jclass clazz = (*env)->FindClass(env, "jni/study/com/jnibsetpractice/Jni");    // 2. 获取method方法的对象    jmethodID methodID = (*env)->GetMethodID(env, clazz, "addInJava", "(II)I");    // 3. 通过字节码获取调用者的实例,  这里不需要,因为就是instance    // 4.通过实例调用方法    jint add = (*env)->CallIntMethod(env, instance, methodID, 9, 9);    return add;}复制代码
  1. Java调用native方法
bt_7.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                int i = jni.callBackMethodInt();                Toast.makeText(MainActivity.this, "C——》java" + i, Toast.LENGTH_LONG).show();            }        });复制代码

实践七:C调用java string参数

  1. java代码,定义native函数
public class Jni {    static {        System.loadLibrary("best");    }    //    这个方法要去调用C代码    public native void callbackMethodString();    //我是java实现的方法,等待C里的callbackMethodString方法回调我    // C 是无法直接打印log的,我们可以,通过此方法,在C里打印log    // 当然在C里通过配置也是可以打印log的,以后再说        public void printString(String s){        System.out.println(s);        Log.e("tag",s);    }}复制代码
  1. javah 生成jni样式的标准头文件

  1. c代码,实现java里的native方法
JNIEXPORT void JNICALL Java_jni_study_com_jnibsetpractice_Jni_callbackMethodString        (JNIEnv *env, jobject instance){    // 1. 获取字节码对象    jclass clazz = (*env)->FindClass(env, "jni/study/com/jnibsetpractice/Jni");    // 2. 获取method方法的对象    jmethodID methodID = (*env)->GetMethodID(env, clazz, "printString", "(Ljava/lang/String;)V");    // 3. 通过字节码获取调用者的实例,  这里不需要,因为就是instance    // 4.通过实例调用方法    // ★★★4.1调用之前,记得在C里的"字符串",java不识别,要转换一下★★★    jstring result =(*env)->NewStringUTF(env,"hello from c");    // 4.2 放心去调用吧    (*env)->CallVoidMethod(env,instance,methodID,result);}复制代码
  1. Java调用native方法
bt_8.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                jni.callbackMethodString();            }        });复制代码

实践八:C调java 弹出吐司

说明

有两种写法

  1. 我们可以把一个native方法,写到了Activity里,这样 JNIEXPORT jstring JNICALL xxxxxx (JNIEnv *env, jobject instance)中的第二个参数instance,就是Activity,第三步不需要《3. 通过字节码获取调用者的实例》,直接使用instance就好了
  1. 我们还可以像之前写的一样,把这个native方法写在Jni类里,但是请看截图内的注释

应该在构造方法里传入context,让toast方法不要带上context参数,总之一句话context(上下文)的实例是不能通过反射获得的,切记!切记!切记!切记!切记!

Context mContext;    private Jni(Context context) {        mContext = context;    }    //    这个方法要去调用C代码    public native void callbackMethodToast();    //我是java实现的方法,等待C里的callbackMethodToast方法回调我    public void toast(String s) {        Toast.makeText(mContext, s, Toast.LENGTH_LONG).show();    }复制代码

方法一:写在Activity里

  1. java代码,定义native函数
public class MainActivity extends AppCompatActivity {//    C调java的toast方法,写到activity里    public native String callbackToastInActivity();    public void toastJavaInativity(String s){        Toast.makeText(MainActivity.this, s , Toast.LENGTH_LONG).show();    }}复制代码
  1. javah 生成jni样式的标准头文件

略,注意是javah -d ../jni jni.study.com.jnibsetpractice.MainActivity

  1. c代码,实现java里的native方法
JNIEXPORT jstring JNICALL Java_jni_study_com_jnibsetpractice_MainActivity_callbackToastInActivity        (JNIEnv *env, jobject instance){    // 1. 获取字节码对象    jclass clazz = (*env)->FindClass(env, "jni/study/com/jnibsetpractice/MainActivity");    // 2. 获取method方法的对象    jmethodID methodID = (*env)->GetMethodID(env, clazz, "toastJavaInativity", "(Ljava/lang/String;)V");    // 3. 通过字节码获取调用者的实例,  这里不需要,因为就是instance    // 4.通过实例调用方法    // 4.1调用之前,记得在C里的"字符串",java不识别,要转换一下    jstring result =(*env)->NewStringUTF(env,"写在Activity的toast");    // 4.2 放心去调用吧    (*env)->CallVoidMethod(env,instance,methodID,result);    return (*env)->NewStringUTF(env, "写在Activity的toast");}复制代码
  1. Java调用native方法
bt_9_1.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                String toast = callbackToastInActivity();                Log.e("tag",toast);            }        });复制代码

方法二:写在单独的Jni类里

  1. java代码,定义native函数
public class Jni {    static {        System.loadLibrary("best");    }    Context mContext;    // 把context通过构造方法,传递进来,避免在toast()方法里传递context    public Jni(Context context) {        mContext = context;    }        //    这个方法要去调用C代码    public native void callbackMethodToast();    //我是java实现的方法,等待C里的callbackMethodToast方法回调我    public void toast(String s) {    //这里不要要传递context参数,context不能通过反射获得        Toast.makeText(mContext, s, Toast.LENGTH_LONG).show();    }}复制代码
  1. javah 生成jni样式的标准头文件

略 注意是javah -d ../jni jni.study.com.jnibsetpractice.Jni

  1. c代码,实现java里的native方法
JNIEXPORT void JNICALL Java_jni_study_com_jnibsetpractice_Jni_callbackMethodToast        (JNIEnv *env, jobject instance){    // 1. 获取字节码对象    jclass clazz = (*env)->FindClass(env, "jni/study/com/jnibsetpractice/Jni");    // 2. 获取method方法的对象    jmethodID methodID = (*env)->GetMethodID(env, clazz, "toast", "(Ljava/lang/String;)V");    // 3. 通过字节码获取调用者的实例,  这里不需要,因为就是instance    // 4.通过实例调用方法    // 4.1调用之前,记得在C里的"字符串",java不识别,要转换一下    jstring result =(*env)->NewStringUTF(env,"写在Jni类的toast");    // 4.2 放心去调用吧,    // ★★★★★★★注意我们保证了toast方法没传入context对象,因为context对象在C里反射出来是不能用的,会空指针异常★★★★★★★★★    (*env)->CallVoidMethod(env,instance,methodID,result);}复制代码
  1. Java调用native方法
bt_9_2.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                jni.callbackMethodToast();            }        });复制代码

C调用java小结

  • ① 找到字节码对象
    • //jclass (FindClass)(JNIEnv, const char*);
    • //第二个参数 要回调的java方法所在的类的路径 "jni/study/com/jnibsetpractice/Jni"
  • ② 通过字节码对象找到方法对象
    • //jmethodID (GetMethodID)(JNIEnv, jclass, const char*, const char*);
    • 第二个参数 字节码对象 第三个参数 要反射调用的java方法名 第四个参数 要反射调用的java方法签名
    • javap -s 要获取方法签名的类的全类名 项目/bin/classes 运行javap
  • ③ 通过字节码创建 java对象(可选) 如果本地方法和要回调的java方法在同一个类里可以直接用 jni传过来的java对象 调用创建的Method
    • jobject obj =(*env)->AllocObject(env,claz);
    • 当回调的方法跟本地方法不在一个类里 需要通过刚创建的字节码对象手动创建一个java对象
    • 再通过这个对象来回调java的方法
    • 需要注意的是 如果创建的是一个activity对象 回调的方法还包含上下文 这个方法行不通!!!回报空指针异常
  • ④ 反射调用java方法
    • //void (CallVoidMethod)(JNIEnv, jobject, jmethodID, ...);
    • 第二个参数 调用java方法的对象 第三个参数 要调用的jmethodID对象 可选的参数 调用方法时接收的参数

在C里输出log的办法

  1. 在C 里输入

#include 
#define LOG_TAG "System.out"#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)复制代码
  1. 在Android.mk里输入

LOCAL_LDLIBS += -llog复制代码
  1. 使用log

LOGD("length = %d", length);复制代码

转载于:https://juejin.im/post/5d0345e9518825166f36b90d

你可能感兴趣的文章
IIS配置错误信息输出
查看>>
excel使用技巧
查看>>
netstat -aon|findstr 8888 终止进程
查看>>
转-临界区对象TCriticalSection与TRTLCriticalSection的区别
查看>>
Flymeos插桩适配教程
查看>>
MySQL备份 博客---MYSQLDBA 黄杉
查看>>
Mysql 修改数据库,mysql修改表类型,Mysql增加表字段,Mysql删除表字段,Mysql修改字段名,Mysql修改字段排列顺序,Mysql修改表名...
查看>>
GuozhongCrawler系列教程 (1) 三大PageDownloader
查看>>
《JavaScript高级程序设计》笔记:引用类型(五)
查看>>
开放产品开发(OPD):OPD框架
查看>>
Ubuntu 14.04下单节点Ceph安装(by quqi99)
查看>>
java uuid第一次性能
查看>>
java-信息安全(二)-对称加密算法DES,3DES,AES,Blowfish,RC2,RC4
查看>>
[Python] Handle Exceptions to prevent crashes in Python
查看>>
jsoup入门
查看>>
73.node.js开发错误——TypeError: Cannot set property 'XXX' of undefined
查看>>
Java设计模式23种(搞笑版) (转)
查看>>
HDFS High Availability Using the Quorum Journal Manager
查看>>
Linux鸟哥(总)
查看>>
centos虚拟机安装,配置静态ip可以访问网络
查看>>