零基础 JNI 入门 + 进阶
2015-09-01 14:18
337 查看
JNI入门
简介
文档:Java Native Interface Specificationhttp://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html
什么是JNI?
Java Native Interface (JNI). The JNI is a native programming interface. It allows Java code that runs inside a Java Virtual Machine (VM) to interoperate with applications and libraries written in other programming languages, such as C, C++, and assembly.
JNI是本地编程接口,它允许java应用程序和C/C++/asm的库在java虚拟机上交互。
动手写一个简单调用JNI的apk
搭建开发环境下载eclips
/article/10598227.html
根据PC机的平台下载其中的
ADT Bundle
里面包含了:sdk + 特定版本platform + eclipse + adt + 兼容包, 解压缩即可使用。
开发jni需要安装NDK工具
为什么eclips中编译JNI要安装NDK?
NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。
NDK介绍:
/article/4783948.html
工具下载:
Android NDK
/article/10598226.html
NDK工具自带的hello-jni的例子
Hello-jni:http://download.csdn.net/detail/data_backups/9050683
下载后把hello-jni工程导入eclips中去,并把NDK集成到eclips中。
集成NDK开发环境
/article/10598224.html
OK一切准备就绪,跑一下试试。
(图一)
代码分析
(图二)
结构上看比正常开发apk多了一个jni目录,Java可以调用JNI的接口。
(图三)
代码:
HelloJni.java
public class HelloJni extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* Create a TextView and set its content. * the text is retrieved by calling a native * function. */ TextView tv = new TextView(this); tv.setText( stringFromJNI() ); setContentView(tv); } /* A native method that is implemented by the * 'hello-jni' native library, which is packaged * with this application. */ public native String stringFromJNI(); /* This is another native method declaration that is *not* * implemented by 'hello-jni'. This is simply to show that * you can declare as many native methods in your Java code * as you want, their implementation is searched in the * currently loaded native libraries only the first time * you call them. * * Trying to call this function will result in a * java.lang.UnsatisfiedLinkError exception ! */ public native String unimplementedStringFromJNI(); /* this is used to load the 'hello-jni' library on application * startup. The library has already been unpacked into * /data/data/com.example.hellojni/lib/libhello-jni.so at * installation time by the package manager. */ static { System.loadLibrary("hello-jni"); } }
如果java要调用native函数,就必须通过一个位于JNI层的动态库来实现,那么在调用这个native函数前必须加载这个动态库。通常是在类的static语句中加载,调用System.loadLibrary方法就可以了,加载时只需要调用库的名字,系统会自动扩展成hello-jni.so。
如何调用呢?在java中函数声明前面带有native的代表此函数是jni层实现的代码,java中只要声明就可以使用了。
Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c include $(BUILD_SHARED_LIBRARY) #Jni的文件编译成动态库就好,名字无所谓
Application.mk
APP_ABI := all
编译成那个平台的动态库,有armeabi-v7a/NEON (hard-float), armeabi-v7a/NEON, armeabi-v7a (hard-float), armeabi-v7a, armeabi, x86, x86_64, mips64, mips, arm64-v8a这些,根据自己的平台替换all以防止编译出全部的动态库,一般arm平台的用armeabi-v7a或armeabi就可以。
hello-jni.c
jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ) { return (*env)->NewStringUTF(env, "Hello from JNI ! Compiled with ABI."); }
调用Java层的Native函数时,如何能调用到JNI层对应的函数呢,这里有两种方法调用,静态方法和动态方法,NDK的例子显然是用了静态方法注册的。
静态注册
使用javah生成jni头文件,javah的用法如下:C:\Users\slz\Desktop>javah --help
用法:
javah [options] <classes>
其中, [options] 包括:
-o <file> 输出文件 (只能使用 -d 或 -o 之一)
-d <dir> 输出目录
-v -verbose 启用详细输出
-h --help -? 输出此消息
-version 输出版本信息
-jni 生成 JNI 样式的标头文件 (默认值)
-force 始终写入输出文件
-classpath <path> 从中加载类的路径
-bootclasspath <path> 从中加载引导类的路径
<classes> 是使用其全限定名称指定的
(例如, java.lang.Object)。
正常应该是
javah -classpath 路径 -jni 包名.类名
但我怎么做也不成功,索性就到工程的src目录下直接执行:
Javah -d 目录 包名.类名
D:\BaiduYunDownload\android-ndk32-r10-windows-x86\android-ndk-r10\samples\hello-jni\src>javah -d C:\Users\slz\Desktop com.example.hellojni.HelloJni
头文件就直接生成在了桌面上
com_example_hellojni_HelloJni.h
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_hellojni_HelloJni */ #ifndef _Included_com_example_hellojni_HelloJni #define _Included_com_example_hellojni_HelloJni #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_hellojni_HelloJni * Method: stringFromJNI * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI (JNIEnv *, jobject); /* * Class: com_example_hellojni_HelloJni * Method: unimplementedStringFromJNI * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
可见带有Native声明的函数都生成到了jni的头文件中。
如何建立关系是由虚拟机完成的,原理大概是java层调用stringFromJNI() 函数时,它会从对应的JNI库中寻找Java_com_example_hellojni_HelloJni_stringFromJNI()函数,如果没有就会报错,如果找到,则会为这两个函数建立一个关联关系,这样以后再调用stringFromJNI()时就直接去找建立好关系的JNI的函数指针就可以了。
静态方法写起来一大串,用起来也不灵活。
动态注册
写一个简单的动态注册的例子代码下载:http://download.csdn.net/detail/data_backups/9053917
hello-jni.c
#include <string.h> #include <jni.h> jstring stringFromJNI_native( JNIEnv* env, jobject thiz ) { return (*env)->NewStringUTF(env, "Hello from JNI dynamic registration!"); } /** * 方法对应表 */ static JNINativeMethod gMethods[] = { {"stringFromJNI", "()Ljava/lang/String;", (void*)stringFromJNI_native}, }; /* * 为某一个类注册本地方法 */ static int registerNativeMethods(JNIEnv* env , const char* className, JNINativeMethod* gMethods, int numMethods) { jclass clazz; clazz = (*env)->FindClass(env, className); if (clazz == NULL) { return JNI_FALSE; } if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { return JNI_FALSE; } return JNI_TRUE; } /* **为所有类注册本地方法 */ static int registerNatives(JNIEnv* env) { const char* kClassName = "com/example/hellojni/HelloJni";//指定要注册的类 return registerNativeMethods(env, kClassName, gMethods, sizeof(gMethods) / sizeof(gMethods[0])); } /* * * System.loadLibrary("lib")时调用 如果成功返回JNI版本, 失败返回-1 */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { return -1; } if (!registerNatives(env)) { return -1; } result = JNI_VERSION_1_4; return result; }
代码分析:
System.loadLibrary()加载JNI动态库后会查找JNI_Onload的函数,JNI_Onload会将JavaVM指针传递进来,透过JavaVM指针取得JNIEnv指针,并存在env 变量中,然后调用clazz = (*env)->FindClass(env, className);
(*env)->RegisterNatives(env, clazz, gMethods, numMethods)
来完成JNI本地函数和java声明的native函数的一一对应。
运行一下
(图四)
动态注册的疑惑比较多
JavaVM JNIEnv JNINativeMethod Jobject都是什么?
FindClass() RegisterNatives() "()Ljava/lang/String;"又是什么?
JNI原理分析
Java语言执行在java虚拟机JVM的环境中,JVM是主机环境中的一个进程,每个JVM虚拟机进程在本地环境中都有一个JavaVM结构体,该结构体在创建Java虚拟机时被返回。JNIEnv是当前java线程的执行环境,一个JVM对应一个JavaVM结构体,而一个JVM中可能创建多个java线程,每个线程对应一个JNIEnv结构,因此不同的线程的JNIEnv不同,也不能相互共享使用。
(图五)
当Java代码调用本地方法时,VM将JNI接口指针作为参数传递给本地方法,本地方法有可以通过JNI的函数表找到与java对应的方法。
(图六)
JNIEnv实际上是提供了一些JNI系统函数,通过这些系统函数可以做到调用java函数,操作jobject对象等很多事情。
Java对象的数据类型在JNI中都用jobject表示,hello-jni例子中stringFromJNI_native( JNIEnv* env, jobject thiz )中的thiz就代表了java层的HelloJni对象,它表示是在哪个HelloJni对象上调用的stringFromJNI,如果Java层是static函数,那么这个参数将是jclass,表示是在调用哪个Java Class的静态函数。
Java调用JNI函数传递的是java数据类型,这些类型到了JNI层该如何表示?
Primitive Types 基础类型
Primitive Types and Native Equivalents
Java Type | Native Type | Description |
boolean | jboolean | unsigned 8 bits |
byte | jbyte | signed 8 bits |
char | jchar | unsigned 16 bits |
short | jshort | signed 16 bits |
int | jint | signed 32 bits |
long | jlong | signed 64 bits |
float | jfloat | 32 bits |
double | jdouble | 64 bits |
void | void | N/A |
#define JNI_FALSE 0 #define JNI_TRUE 1
The jsize integer type is used to describe cardinal indices and sizes:
typedef jint jsize;
Reference Types 引用类型
The JNI includes a number of reference types that correspond to different kinds of Java objects
Java Type | Native Type |
All objects | jobject |
java.lang.Class object | jclass |
java.lang.String object | jstring |
object[] | jobjectArray |
boolean[] | jbooleanArray |
byte[] | jbyteArray |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | floatArray |
double[] | jdoubleArray |
java.lang.Throwbale object | jthrowable |
(图七)
除了基础数据类型的数组、Class、String和Throwable外,其余java对象的数据类型在JNI中都表示为jobject。
JNI中支持很多系统函数操作,这些函数支持上诉的java类型的操作及一些访问对象和注册本地方法,上诉hello-jni的例子中就有FindClass()、RegisterNatives()、NewStringUTF()等,常用的函数大全:/article/10598228.html
还剩一个问题
static JNINativeMethod gMethods[] = { {"stringFromJNI", "()Ljava/lang/String;", (void*)stringFromJNI_native}, };
是什么?
这个是java对应JNI函数的一些签名信息,为了表明java的方法和JNI函数的一个一对一的关系,格式:
{“Java方法名”, “(参数1类型标示参数2类型标示..参数n类型标示)返回值类型标示”, JNI函数名 }
签名类型标示表:
Java Type | Paramter Type |
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
String | L/java/langaugeString; |
int[] | [I |
double[] | jdoubleArray |
Java提供了一个签名工具javap,使用方法:
Javap -s -p xxx.class
在hello-jni这个例子里就是如下用法
(图八)
把解析出来的函数签名对应的拷贝道签名数组的位置即可。
JNI进阶
基本流程弄清楚了,就可以搞一些复杂的。依次介绍如下内容:
1)传递基础数据类型
2)传递返回字符串类型
3)传递返回数组
4)Native层操作java类属性
5)Native层回调java方法
6)传递返回一个java类
7)Native线程回调java方法
8)Native调用第三方库
代码下载:http://download.csdn.net/detail/data_backups/9070847
C语言和C++对应的JNI函数稍有些不同
例如,对于生成一个jstring类型的方法转换分别如下:
C编程环境中使用方法为:(*env) ->NewStringUTF(env , "123") ;
C++编程环境中则是: env ->NewStringUTF( "123") ; (使用起来更简单)
传递基础数据类型
Java//Primitive Types int ret; boolean a = true; byte b = 1; char c = 2; short d = 3; int e = 4; long f = 5; float h = 6.1f; double j = 6.2; ret = PrimitiveTypes(a, b, c, d, e, f, h, j); Log.d(TAG, "app PrimitiveTypes return:" + ret); private native int PrimitiveTypes(boolean a, byte b, char c, short d, int e, long f, float h, double i);//声明
JNI
jint PrimitiveTypes_native(JNIEnv *env, jobject thiz, jboolean a, jbyte b, jchar c, jshort d, jint e, jlong f, jfloat h, jdouble i) { long _f; float _h; double _i; _f = f; _h = h; _i = i; LOGD("JNI: call PrimitiveTypes_native"); LOGD("JNI: jboolean a=%d", a); LOGD("JNI: jbyte b=%d", b); LOGD("JNI: jchar c=%d", c); LOGD("JNI: jshort d=%d", d); LOGD("JNI: jint d=%d", e); LOGD("JNI: jlong d=%ld", _f); LOGD("JNI: jfloat h=%f", _h); LOGD("JNI: jfloat h=%f", _i); return 0; } //签名 {"PrimitiveTypes", "(ZBCSIJFD)I", (void*)PrimitiveTypes_native},
传递返回字符串类型
jstring stringFromJNI_native( JNIEnv* env, jobject thiz ) 就属于返回字符串的例子。JNI 层的jstring对象可以看成java层的String,但java String存储的是Unicode格式的字符串,而JNI层定义的字符串为UTF-8格式的。调用JNIEnv的NewStringUTF可将根据native的一个UTF-8字符串得到一个jstring对象,这个函数用的最多。
JNI还提供了GetStringChars函数,可将java String对象转换成本地字符串。
如果调用了上诉JNI函数,在做完工作之后需要调用ReleaseStringUTFChars函数来释放资源(如果是Unicode的就调用ReleaseStringChars),否则会导致JVM内存泄露。
传递返回数组
Java//Reference Types Array byte array1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; byte array2[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; ByteBuffer mDirectBuffer = ByteBuffer.allocateDirect(10); ReferenceTypesSetByteArray1(array1); Log.d(TAG, "app array1:"); printBuffer(array1); ReferenceTypesSetByteArray2(array2); Log.d(TAG, "app array2:"); printBuffer(array2); ReferenceTypesSetDirectIntArray(mDirectBuffer, 10); Log.d(TAG, "app DirectBuffer:"); printBuffer(mDirectBuffer.array()); byte[] buffer = ReferenceTypesGetByteArray(); Log.d(TAG, "app GetArray:"); printBuffer(buffer); private native void ReferenceTypesSetByteArray1(byte[] array); private native void ReferenceTypesSetByteArray2(byte[] array); private native void ReferenceTypesSetDirectIntArray(Object buffer, int len); private native byte[] ReferenceTypesGetByteArray();
打印方法:
private void printBuffer( byte[] buffer ) { StringBuffer sBuffer = new StringBuffer(); for( int i=0; i<buffer.length; i++ ) { sBuffer.append(buffer[i]); sBuffer.append(" "); } Log.d(TAG, "Native" + sBuffer.toString()); }
JNI
#define TEST_BUFFER_SIZE 10 void ReferenceTypesSetByteArray1_native(JNIEnv *env, jobject thiz, jbyteArray buffer) { int len; char array[TEST_BUFFER_SIZE]; len = (*env)->GetArrayLength(env, buffer); //直接将Java端的数组拷贝到本地的数据中,建议使用这种方式,更加安全 (*env)->GetByteArrayRegion(env, buffer, 0, len, array); //可以通过array来访问这段数组的值了,注意,修改的只是本地的值,Java端不会同时改变数组的值 int i=0; for( i=0; i<len; i++ ) { array[i] = i; LOGD("JNI: array[%d]=%d", i, array[i]); } } void ReferenceTypesSetByteArray2_native(JNIEnv *env, jobject thiz, jbyteArray buffer) { int len; int i=0; char array[TEST_BUFFER_SIZE]; len = (*env)->GetArrayLength(env, buffer); //将本地指针指向含有Java端数组的内存地址,依赖Jvm的具体实现,可能是锁住Java端的那段数组不被回收(增加引用计数), //也可能所Jvm在堆上对该数组的一份拷贝 //速度和效率比GetByteArrayRegion方法要高很多 char * pBuffer = (*env)->GetByteArrayElements(env,buffer,NULL); if( pBuffer == NULL ) { LOGE("GetIntArrayElements Failed!"); return; } for (i=0; i < len; i++) { array[i] = pBuffer[i]; LOGD("JNI: pBuffer[%d]=%d", i, pBuffer[i]); } //可以通过pBuffer指针来访问这段数组的值了,注意,修改的是堆上的值,Java端可能会同步改变,依赖于Jvm的具体实现,不建议通过本方法改变Java端的数组值 for( i=0; i<len; i++ ) { pBuffer[i] = i + 10; } //最后不要忘记释放指针(减小引用计数) (*env)->ReleaseByteArrayElements(env,buffer,pBuffer,0); } void ReferenceTypesSetDirectIntArray_native(JNIEnv *env, jobject thiz, jbyteArray buffer, int len) { //无需拷贝,直接获取与Java端共享的直接内存地址(效率比较高,但object的构造析构开销大,建议长期使用的大型buffer采用这种方式) unsigned char * pBuffer = (unsigned char *)(*env)->GetDirectBufferAddress(env,buffer); if( pBuffer == NULL ) { LOGE("GetDirectBufferAddress Failed!"); return; } //可以通过pBuffer指针来访问这段数组的值了,注意,修改数组的值后,Java端同时变化 int i=0; for( i=0; i<len; i++ ) { pBuffer[i] = i; } } jbyteArray ReferenceTypesGetByteArray_native(JNIEnv *env, jobject thiz) { //传递JNI层的数组数据到Java端,有两种方法,一种是本例所示的通过返回值来传递 //另一种是通过回调Java端的函数来传递(多用于jni线程中回调java层) unsigned char buffer[TEST_BUFFER_SIZE]; int i=0; for( i=0; i<TEST_BUFFER_SIZE; i++ ) { buffer[i] = i; } //分配ByteArray jbyteArray array = (*env)->NewByteArray(env,TEST_BUFFER_SIZE); //将传递数据拷贝到java端 (*env)->SetByteArrayRegion(env, array, 0, TEST_BUFFER_SIZE, buffer); return array; } //签名 {"ReferenceTypesSetByteArray1", "([B)V", ReferenceTypesSetByteArray1_native}, {"ReferenceTypesSetByteArray2", "([B)V", ReferenceTypesSetByteArray2_native}, {"ReferenceTypesSetDirectIntArray", "(Ljava/lang/Object;I)V", ReferenceTypesSetDirectIntArray_native}, {"ReferenceTypesGetByteArray", "()[B", ReferenceTypesGetByteArray_native},
Native层操作java类属性
Javaprivate String name = "Java layer";//定义类属性
Log.d(TAG, "app print original name: " + name); OperateJavaAttribute(); Log.d(TAG, "app print new name: " + name); private native void OperateJavaAttribute();//声明
JNI
void OperateJavaAttribute_native(JNIEnv *env, jobject thiz) { //获得jfieldID 以及 该字段的初始值 jfieldID nameFieldId ; jclass cls = (*env)->GetObjectClass(env, thiz); //获得Java层该对象实例的类引用,即HelloJNI类引用 nameFieldId = (*env)->GetFieldID(env, cls , "name" , "Ljava/lang/String;"); //获得属性句柄 if(nameFieldId == NULL) { LOGE("can not find java field id"); } jstring javaNameStr = (jstring)(*env)->GetObjectField(env, thiz ,nameFieldId); // 获得该属性的值 const char * c_javaName = (*env)->GetStringUTFChars(env, javaNameStr , NULL); //转换为 char *类型 LOGD("JNI: java name is: %s", c_javaName); (*env)->ReleaseStringUTFChars(env, javaNameStr , c_javaName); //释放局部引用 //构造一个jString对象 char * c_ptr_name = "Native layer"; jstring cName = (*env)->NewStringUTF(env, c_ptr_name); //构造一个jstring对象 (*env)->SetObjectField(env, thiz , nameFieldId , cName); // 设置该字段的值 } //签名 {"OperateJavaAttribute", "()V", OperateJavaAttribute_native},
Native层回调java方法
Javapublic void Java_method(String fromNative) //Native层会调用Java_method()方法 { Log.d(TAG, "app java function invoked by native function: " + fromNative); }
NativeCallbackJava();//Native层会调用Java_method()方法 //声明 private native void NativeCallbackJava();
JNI
void NativeCallbackJava_native(JNIEnv *env, jobject thiz) { //回调Java中的方法 jclass cls = (*env)->GetObjectClass(env, thiz);//获得Java类实例 jmethodID callbackID = (*env)->GetMethodID(env, cls, "Java_method" , "(Ljava/lang/String;)V") ;//获得该回调方法句柄 if(callbackID == NULL) { LOGE("getMethodId is failed \n"); } jstring native_desc = (*env)->NewStringUTF(env, "callback java I am form Native"); (*env)->CallVoidMethod(env, thiz , callbackID , native_desc); //回调该方法,并且传递参数值 } //签名 {"NativeCallbackJava", "()V", NativeCallbackJava_native},
传递返回一个java类
定义一个java类 Employee.javapackage com.example.hellojni; public class Employee { private String name; private int age; //构造函数,什么都不做 public Employee(){ } public Employee(int age ,String name){ this.age = age ; this.name = name ; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name){ this.name = name; } }
Java
//native operate java class Employee someone = NativeGetEmployeeInfo();//native层创建并返回java类 Log.d(TAG, "app someone name:" + someone.getName()); Log.d(TAG, "app someone age:" + someone.getAge()); TransferEmployeeInfo(someone);//java向native层传递java类,并在native层打印出来。 //声明 private native Employee NativeGetEmployeeInfo(); private native void TransferEmployeeInfo(Employee someone);
JNI
jobject NativeGetEmployeeInfo_native(JNIEnv *env, jobject thiz) { LOGD("JNI invoke NativeGetEmployeeInfo_native"); jclass employeecls = (*env)->FindClass(env, "com/example/hellojni/Employee"); //获得得该类型的构造函数 函数名为 <init> 返回类型必须为 void 即 V //注意:该 ID特指该类class的构造函数ID , 必须通过调用 GetMethodID() 获得,且调用时的方法名必须为 <init>,而返回类型必须为 void (V) jmethodID constrocMID = (*env)->GetMethodID(env, employeecls, "<init>", "(ILjava/lang/String;)V"); jstring str = (*env)->NewStringUTF(env, "xiaoming"); jobject employeeobj = (*env)->NewObject(env, employeecls,constrocMID, 30, str); //构造一个对象,调用该类的构造函数,并且传递参数 return employeeobj; } void TransferEmployeeInfo_native(JNIEnv *env, jobject thiz, jobject employeeobj) { LOGD("JNI invoke TransferEmployeeInfo_native"); jclass employeecls = (*env)->GetObjectClass(env, employeeobj); //或得Student类引用 if(employeecls == NULL) { LOGE("JNI GetObjectClass failed"); } jfieldID ageFieldID = (*env)->GetFieldID(env, employeecls,"age","I"); //获得得Student类的属性id jfieldID nameFieldID = (*env)->GetFieldID(env, employeecls,"name","Ljava/lang/String;"); // 获得属性ID jint age = (*env)->GetIntField(env, employeeobj , ageFieldID); //获得属性值 jstring name = (jstring)(*env)->GetObjectField(env, employeeobj , nameFieldID);//获得属性值 const char * c_name = (*env)->GetStringUTFChars(env, name ,NULL);//转换成 char * LOGD("JNI name:%s, age:%d", c_name, age); (*env)->ReleaseStringUTFChars(env, name,c_name); //释放引用 //LOGD("JNI name:%s, age:%d", c_name, age); 不能再释放后在调用 } //签名 {"NativeGetEmployeeInfo", "()Lcom/example/hellojni/Employee;", NativeGetEmployeeInfo_native}, {"TransferEmployeeInfo", "(Lcom/example/hellojni/Employee;)V",TransferEmployeeInfo_native},
Native线程回调java方法
javapublic void Java_method(String fromNative) //Native层会调用Java_method()方法 { Log.d(TAG, "app java function invoked by native function: " + fromNative); }
//native pthread callback java NativePthreadCallbackJava();//native层创建的线程去回调java //声明 private native void NativePthreadCallbackJava();
JIN
JavaVM * gJavaVM; jobject gJavaObj; static void* native_thread_exec(void *arg) { JNIEnv *env; LOGD("JNI: pthread tid=%lu, pid=%lu", (unsigned long)gettid(), (unsigned long)getpid()); //从全局的JavaVM中获取到环境变量 (*gJavaVM)->AttachCurrentThread(gJavaVM,&env, NULL); // //获取Java层对应的类 jclass cls = (*env)->GetObjectClass(env,gJavaObj); if( cls == NULL ) { LOGE("JNI Fail to find javaClass"); return 0; } // //获取Java层被回调的函数 jmethodID callbackID = (*env)->GetMethodID(env, cls, "Java_method" , "(Ljava/lang/String;)V") ;//获得该回调方法句柄 if(callbackID == NULL) { LOGE("getMethodId is failed \n"); } jstring str = (*env)->NewStringUTF(env, "native pthread callback java I am form native"); //构造一个jstring对象 (*env)->CallVoidMethod(env, gJavaObj , callbackID , str); //回调该方法,并且传递参数值 (*gJavaVM)->DetachCurrentThread(gJavaVM); } void NativePthreadCallbackJava_native(JNIEnv *env, jobject thiz) { LOGD("JNI: master tid=%lu, pid=%lu", (unsigned long)gettid(), (unsigned long)getpid()); //注意,直接通过定义全局的JNIEnv和jobject变量,在此保存env和thiz的值是不可以在线程中使用的 //线程不允许共用env环境变量,但是JavaVM指针是整个jvm共用的,所以可以通过下面的方法保存JavaVM指针,在线程中使用 (*env)->GetJavaVM(env,&gJavaVM); //同理,jobject变量也不允许在线程中共用,因此需要创建全局的jobject对象在线程中访问该对象 gJavaObj = (*env)->NewGlobalRef(env,thiz); //通过pthread库创建线程 pthread_t threadId; if( pthread_create(&threadId,NULL,native_thread_exec,NULL) != 0 ) { LOGE("native_thread_start pthread_create fail !"); return; } LOGD("Create Pthread success"); pthread_join(threadId, NULL); //在native代码不再需要访问一个全局引用的时候,应该调用DeleteGlobalRef来释放它。 (*env)->DeleteGlobalRef(env, gJavaObj); gJavaObj = NULL; } //签名 {"NativePthreadCallbackJava", "()V", NativePthreadCallbackJava_native},
PS: JNI 是线程相关的,在不同的线程中要通过JavaVM *jvm的方法来获取与当前线程相关的JNIEnv*,那么在有JNIEnv*作为参数的时候可以直接使用,在JNIEnv*不作为参数传入的时候可以通过JNI的函数获取:(*gJavaVM)->AttachCurrentThread(gJavaVM,&env,
NULL); 在线程结束的时候在调用(*gJavaVM)->DetachCurrentThread(gJavaVM);释放
gJavaVM是全局的JavaVM指针。
Native调用第三方库
写了一个加法函数库libsum.so供JNI调用函数库源码:
Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := libsum LOCAL_C_INCLUDES += $(LOCAL_PATH)/include LOCAL_SRC_FILES := sum.c LOCAL_MODULE_TAGS := optional LOCAL_PRELINK_MODULE := false LOCAL_SHARED_LIBRARIES := \ libc \ libcutils \ libutils \ liblog include $(BUILD_SHARED_LIBRARY)
sum.c
int sum_Library(int a, int b) { return a + b; }include/sum.h
#ifndef _SUM__ #define _SUM__ int sum_Library(int a, int b); #endif// _SUM__
编译成动态库放入工程目录中
需要修改Android.mk
LOCAL_PATH := $(call my-dir) #add prebuild library #$(warning ****LOCAL_PATH**** ) #$(warning $(LOCAL_PATH)) include $(CLEAR_VARS) LOCAL_MODULE := libsum LOCAL_SRC_FILES := prebuilt/libsum.so LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/prebuilt/include include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c LOCAL_SHARED_LIBRARIES := libsum LOCAL_LDLIBS := -llog -lz -lm include $(BUILD_SHARED_LIBRARY)
Java
//native call Library int sum = NativeCallLibrary_sum(2, 3); Log.d(TAG, "app 2+3=" + sum); //声明 private native int NativeCallLibrary_sum(int a, int b); //加载动态库 System.loadLibrary("sum");JNI
jint NativeCallLibrary_sum_native(JNIEnv *env, jobject thiz, jint a, jint b) { return sum_Library(a, b); } //签名 {"NativeCallLibrary_sum", "(II)I", NativeCallLibrary_sum_native},
所有代码的执行log
相关文章推荐
- android用canvas绘制两种波纹效果
- Programming Ability Test学习 1008. 数组元素循环右移问题 (20)
- 云计算设计模式(五)——计算资源整合模式
- Mac下使用CornerstoneSVN版本管理中iOS静态库被忽略问题解决
- 解决npm install缓慢
- string类的用法总结
- 【more effective c++读书笔记】【第4章】效率(3)
- ActiveXObject Word.Application 打印小票
- 【more effective c++读书笔记】【第4章】效率(3)
- Mysql加密方式
- expect半自动磁盘扩容脚本
- UINavigationController 弹出新的UIViewController时,setNavigationBarHidden失效的问题
- win 7上32位系统安装office 2010时,提示需要安装MSXML版本6.10.1129.0组件的解决方法
- MFC之静态文本框的使用
- MyBatis官方文档——入门
- PAT (Advanced Level) Practise:1027. Colors in Mars
- 1052. Linked List Sorting (25)
- 关于在stm32中使用printf函数的问题
- HDU 1065.I Think I Need a Houseboat【数学题】【9月1】
- 多重背包