jni开发初试
来源:互联网 发布:网络新闻报道的特点 编辑:程序博客网 时间:2024/04/27 16:02
简单jni流程初试
JNI java本地开发接口
JNI 是一个协议
这个协议用来沟通java代码和外部的本地代码(c/c++)。
通过这个协议,java代码就可以调用外部的c/c++ 代码,
外部的c/c++代码也可以调用java代码。
首先学习熟悉下简单的jni开发流程:
1.创建一个android工程
这个工程实现一个简单的功能:
使用jni这种技术打印一个由底层C语言返回的一个字符串2.JAVA代码中声明native 方法如下;
//第一步,在java代码中定义一个c方法的接口//相当于在java代码中定义了一个接口,接口的实现是由c语言来实现的public native String helloWorldFromC();
3. 创建jni目录,编写c代码,方法名字要对应
- 在jni目录下,新建一个hello.c文件
首先引入两个头文件
#include <stdio.h>#include <jni.h>
- 在java中声明的方法是下面这样的
public native String helloWorldFromC();
- 实现该方法,方法名要对应
方法名:需要在方法名前面加上Java表示是java中的方法,另外,需要使用类的全名,将.替换为下划线
jstring Java_com_example_hellowordfromc_MainActivity_helloWorldFromC(JNIEnv * env,jobject obj){}
这里的方法名采用的是手动写的方式,由于这种书写方式容易出错,另一种比较简单的方式就是以cmd模式进入到工程的src目录,使用javah + native方法的全类名,就可以自动生成头文件,这样native方法的c语言实现就不用手动写了,避免了不必要的错误。
+ 现在需要返回一个java String类型的字符串
注意:要想实现jni这类的程序,必须下载安装ndk,因为将需要使用该工具将c语言编译生成动态库
在ndk的ndk\platforms\android-8\arch-arm\usr\include目录下,有个jni.h文件
我们在前面引入了这个这个头文件后,可以使用里面声明的方法来实现我们想要的功能。
查看jni.h文件,可以找到==jstring (NewStringUTF)(JNIEnv, const char*);==
这个方法能够以UTF-8格式返回一个字符串
那么如何获得这个方法呢?
注意到方法中第一个参数JNIEnv * env,通过这个参数就可以拿到上面的方法了。
在jni.h中,有下面个声明:
//表示JNIEnv其实是JNINativeInterface这个结构体的指针类型typedef const struct JNINativeInterface* JNIEnv;
在JNINativeInterface这个结构体中,有(NewStringUTF)(JNIEnv, const char*)这个方法,
当我们通过传过来的参数env就可以拿到JNINativeInterface,再通过JNINativeInterface去调用这个构造字符串的方法
/* * Table of interface function pointers. */struct JNINativeInterface { ....· jstring (*NewStringUTF)(JNIEnv*, const char*); ....}
代码的实现逻辑如下:
jstring Java_com_example_hellowordfromc_MainActivity_helloWorldFromC(JNIEnv * env,jobject obj) { //(*env) 相当于JNINativeInterface* JNIEnv //*(*env) 相当于JNINativeInterface //return (**env).NewStringUTF(env,"helloworldfromc"); //另一种写法 return (*env)->NewStringUTF(env,"helloworldfromc");}
4.编写Android.mk文件
在完成了C语言的实现后,需要编写Android.mk文件,使得ndk能够编译C语言生成函数库
在这个路径的文档介绍了如何编写Android.mk文件:ndk目录/android-ndk-r7b/docs/ANDROID-MK.html,可以看这个文档,写的很详细。
下面是最简单的mk文件的写法:
#将当前路径赋值给LOCAL_PATH这个变量 LOCAL_PATH := $(call my-dir) #清空之前定义的变量 include $(CLEAR_VARS) #对应的打包函数库的名字 LOCAL_MODULE := hello #对应的C代码的文件 LOCAL_SRC_FILES := hello.c #表示编译成动态库,在linux下的动态库一般后缀名为.so include $(BUILD_SHARED_LIBRARY)
5.Ndk编译生成动态库
在完成上面的步骤后,就可以使用ndk将c程序打包成函数库了。
另外,编译之前需要配置下ndk的环境变量,这样就可以在任意目录使用ndk-build这个工具了。
进入到对应android工程下,以cmd模式输入ndk-build运行,这样就可以生成动态库了。6.Java代码load 动态库.调用native代码
在java代码中引入库函数,然后就可以直接调用native方法名就可以了。
//在java代码中引入库函数 static { System.loadLibrary("hello"); }
这样就可以运行程序看效果了。
最后贴一下整个工程完整的程序:
MainActivity
package com.example.hellowordfromc;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.widget.Toast;public class MainActivity extends Activity { //第一步,在java代码中定义一个c方法的接口 //相当于在java代码中定义了一个接口,接口的实现是由c语言来实现的 public native String helloWorldFromC(); //第五步,在java代码中引入库函数 static { System.loadLibrary("hello"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void click(View view) { Toast.makeText(getApplicationContext(), helloWorldFromC(), 0).show(); }}
hello.c文件
#include <stdio.h>#include <jni.h>// public native String helloWorldFromC();//jstring 方法的返回值类型//方法名:需要在方法名前面加上Java表示是java中的方法,另外,需要使用类的取名,将.替换为下划线/** //Reference types, in C. typedef void* jobject; typedef jobject jclass; typedef jobject jstring; */jstring Java_com_example_hellowordfromc_MainActivity_helloWorldFromC(JNIEnv * env,jobject obj) { //第二步,生成c代码 //第三步,编写Android.mk文件 //第四步,使用ndk将c程序打包成函数库 //现在需要返回一个java String类型的字符串 //在ndk的ndk\platforms\android-8\arch-arm\usr\include目录下,有个jni.h文件 //在引入了jni.h后,我们需要利用里面声明的下面这个方法来构造一个字符串 //jstring (*NewStringUTF)(JNIEnv*, const char*); //注意到这个方法jstring (*NewStringUTF)(JNIEnv*, const char*)是 //定义在JNINativeInterface这个结构体中的 //当我们通过传过来的参数env就可以拿到JNINativeInterface,再通过JNINativeInterface去调用这个构造字符串的方法 //(*env) 相当于JNINativeInterface* JNIEnv //*(*env) 相当于JNINativeInterface //return (**env).NewStringUTF(env,"helloworldfromc"); //另一种写法 return (*env)->NewStringUTF(env,"helloworldfromc");}
java向c语言传递数据
接下来我们看java如何向c语言传递数据
首先创建一个类,主要用于声明java的native方法
现在主要实现以下三个方法:
第一个,使用jni技术实现两个数的相加
第二个,在字符串后面拼接字符串,应用场景为web url的拼接或者一些加密运算,由于java语言的特性,应用程序容易被反编译,对于有些敏感信息需要进行加密,使用c语言实现一般很难被反编译。
第三个,由java传递int数组,C语言对其进行操作后进行返回,这个应用的场景一般为图像的处理方面
package com.example.ndkpassdata;public class DataProvider { /** * 计算x和y的加法 * 315 * @param x * @param y * @return */ public native int add(int x ,int y); // char String short kiss keep it simple and stupid String[] "123:234" /** * 给字符串后面拼装字符 加密运算 web url * @param s * @return */ public native String sayHelloInC(String s); // /** * 给c代码传递int数组 让c代码给这个数组进行操作 * 图形 声音的处理 * @param iNum * @return */ public native int[] intMethod(int[] iNum); }
接着以windows命令行模式,进入到该android工程的src目录,
运行如下命令:javah com.example.ndkpassdata.DataProvider
这样生成如下的头文件:
/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_example_ndkpassdata_DataProvider */#ifndef _Included_com_example_ndkpassdata_DataProvider#define _Included_com_example_ndkpassdata_DataProvider#ifdef __cplusplusextern "C" {#endif/* * Class: com_example_ndkpassdata_DataProvider * Method: add * Signature: (II)I */JNIEXPORT jint JNICALL Java_com_example_ndkpassdata_DataProvider_add (JNIEnv *, jobject, jint, jint);/* * Class: com_example_ndkpassdata_DataProvider * Method: sayHelloInC * Signature: (Ljava/lang/String;)Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_example_ndkpassdata_DataProvider_sayHelloInC (JNIEnv *, jobject, jstring);/* * Class: com_example_ndkpassdata_DataProvider * Method: intMethod * Signature: ([I)[I */JNIEXPORT jintArray JNICALL Java_com_example_ndkpassdata_DataProvider_intMethod (JNIEnv *, jobject, jintArray);#ifdef __cplusplus}#endif#endif
这样在工程目录创建jni目录,然后将生成的头文件剪切到jni目录下,新建hello.c文件,引入#include “com_example_ndkpassdata_DataProvider.h”头文件
#include <stdio.h>#include "com_example_ndkpassdata_DataProvider.h"//要实现在C语言中打印log,需要加入如下代码//并且需要在Android.mk文件中引入log相应的函数库#include <android/log.h>#include <string.h>#define LOG_TAG "clog"#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)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;}//注意JNIEXPORT和JNICALL都是由javah自动生成的,可以删掉,不影响JNIEXPORT jint JNICALL Java_com_example_ndkpassdata_DataProvider_add (JNIEnv * env, jobject jobject, jint x, jint y){ // 想在logcat控制台上 打印日志 LOGD("x=%d",x); LOGI("y=%d",y); // log.i(TAG,"sss"); return x+y;}JNIEXPORT jstring JNICALL Java_com_example_ndkpassdata_DataProvider_sayHelloInC (JNIEnv * env, jobject jobject, jstring str){ char* c="hello"; // 在C语言中不能直接操作java中的字符串 // 把java中的字符串转换成c语言中 char数组 char* cstr=Jstring2CStr(env,str); strcat(cstr,c); LOGD("%s",cstr); return (*env)->NewStringUTF(env,cstr);}JNIEXPORT jintArray JNICALL Java_com_example_ndkpassdata_DataProvider_intMethod (JNIEnv * env, jobject jobject, jintArray jarray){ // jArray 遍历数组 jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*); // 数组的长度 jsize (*GetArrayLength)(JNIEnv*, jarray); // 对数组中每个元素 +5 int length=(*env)->GetArrayLength(env,jarray); int* array=(*env)->GetIntArrayElements(env,jarray,0); int i=0; for(;i<length;i++){ *(array+i)+=5; } return jarray;}
最后在android工程中loadLibrary,就可以使用这些方法了。
c语言回调java方法
==接下来我们看如何在C语言回调java方法==
这种应用场景相对比较少,另外这种实现的方式也比较复杂点。
同样,前面的步骤都是一样的,声明native方法。首先在创建一个DataProvider,我们在这里面声明native方法。
这里不同的是,要想实现c语言回调java方法,需要通过声明native方法,通过实现该native方法,然后再回调会java代码中
package com.example.ndkcallback;public class DataProvider { //C调用java空方法 public void helloFromJava(){ System.out.println("哈哈哈 我被调用了"); } //C调用java中的带两个int参数的方法 public int Add(int x,int y){ int result=x+y; System.out.println("result:"+result); return result; } //C调用java中参数为string的方法 public void printString(String s){ System.out.println(s); } public static void demo(){ System.out.println("哈哈哈,我是静态方法"); } public native void callMethod1(); public native void callMethod2(); public native void callMethod3(); public native void callMethod4(); public native void callMethod5();}
这里回调的机制类似于java的反射机制,通过jni.h中声明的FindClass方法获得java类的字节码,然后获得方法的ID,将字节码参数,方法名,以及方法签名传到CallVoidMethod这样的方法中,就可以实现在c语言中回调java方法。
注意,可以使用javap来获得对应类内部方法的签名,签名一般由方法的参数和赶回值组成。
javap -s 打印方法的签名 注意要cd到 C:\workspace\HelloWorldFromC\bin\classes 传全类名
#include "com_example_ndkcallback_DataProvider.h"JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod1 (JNIEnv * env, jobject jobject){ /* * Class<?> forName = Class.forName("com.example.ndkcallback.DataProvider"); Method declaredMethod = forName.getDeclaredMethod("helloFromJava", new Class[]{}); declaredMethod.invoke(forName.newInstance(), new Object[]{}); * * */ ///jclass (*FindClass)(JNIEnv*, const char*); jclass clazz=(*env)->FindClass(env,"com/example/ndkcallback/DataProvider"); // jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); // 方法签名 参数和返回值 jmethodID methodId=(*env)->GetMethodID(env,clazz,"helloFromJava","()V"); // void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); (*env)->CallVoidMethod(env,jobject,methodId);}JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod2 (JNIEnv * env, jobject jobject){ jclass clazz=(*env)->FindClass(env,"com/example/ndkcallback/DataProvider"); jmethodID methodId=(*env)->GetMethodID(env,clazz,"Add","(II)I"); // jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...); (*env)->CallIntMethod(env,jobject,methodId,3,5);}JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod3 (JNIEnv * env, jobject jobject){ // 参数 object 就是native方法所在的类 jclass clazz=(*env)->FindClass(env,"com/example/ndkcallback/DataProvider"); jmethodID methodId=(*env)->GetMethodID(env,clazz,"printString","(Ljava/lang/String;)V"); // jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...); jstring str=(*env)->NewStringUTF(env,"hello"); (*env)->CallVoidMethod(env,jobject,methodId,str);}JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod4 (JNIEnv * env, jobject j){ jclass clazz=(*env)->FindClass(env,"com/example/ndkcallback/MainActivity"); // jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); // 方法签名 参数和返回值 jmethodID methodId=(*env)->GetMethodID(env,clazz,"helloFromJava","()V"); // void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); // 需要创建DataProvider的 对象 // jobject (*AllocObject)(JNIEnv*, jclass); jobject obj=(*env)->AllocObject(env,clazz); // new MainActivity(); (*env)->CallVoidMethod(env,obj,methodId);}JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod5 (JNIEnv * env, jobject j){ jclass clazz=(*env)->FindClass(env,"com/example/ndkcallback/DataProvider"); // jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*); jmethodID methodid=(*env)->GetStaticMethodID(env,clazz,"demo","()V"); //void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...); (*env)->CallStaticVoidMethod(env,clazz,methodid);}
那么这个JNIEnv是干什么用的?
其实从这个参数的名称就可以看到,就是指JNI的运行环境,我觉得它就是对Java虚拟环境的一个引用,在Android中,就是指Dalvik VM。
- jni开发初试
- jni初试
- 初试jni
- JNI初试
- eclipse下jni初试
- eclipse下jni初试
- ubuntu下JNI初试
- 初试Jni & NDK
- JNI初试牛刀
- eclipse下jni初试[转]
- android studio初试ndk,jni
- 初试BV561EVB开发板
- 初试IDEA插件开发
- MVC开发模式初试
- Lucene 搜索引擎开发初试 (1)
- 初试swift语言ios开发
- Android开发之初试MPAndroidChart
- JNI开发
- 扔玻璃球
- 玛雅人的密码(C#)
- XML解析---PULL解析
- php curl 下载文件
- Unity5 如何做资源管理和增量更新
- jni开发初试
- Windows 10下进行Apache24 php7 MySQL环境搭建
- 网络流三·二分图多重匹配
- lgP3392涂国旗
- ACM学习历程30——回溯算法
- leetcode:22. Generate Parentheses QuestionEditorial Solution
- 全面理解Unity加载和内存管理
- H.264视频码流解析
- linux网络查看命令