零基础 JNI 入门 + 进阶

JNI入门

简介

文档:Java Native Interface Specification

http://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

http://blog.csdn.net/data_backups/article/details/48004943

根据PC机的平台下载其中的

ADT Bundle

里面包含了:sdk + 特定版本platform + eclipse + adt + 兼容包, 解压缩即可使用。

 

开发jni需要安装NDK工具

为什么eclips中编译JNI要安装NDK?

    NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。

NDK介绍:

http://www.cnblogs.com/devinzhang/archive/2012/02/29/2373729.html

 

工具下载:

Android NDK

http://blog.csdn.net/data_backups/article/details/48004847

 

NDK工具自带的hello-jni的例子

    Hello-jni:http://download.csdn.net/detail/data_backups/9050683

下载后把hello-jni工程导入eclips中去,并把NDK集成到eclips中。

集成NDK开发环境

http://blog.csdn.net/data_backups/article/details/47809201

 

OK一切准备就绪,跑一下试试。


             (图一)


代码分析

零基础 JNI 入门 + 进阶_第1张图片

              (图二)

 

结构上看比正常开发apk多了一个jni目录,Java可以调用JNI的接口。

零基础 JNI 入门 + 进阶_第2张图片

        (图三)

代码:

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] 

其中, [options] 包括:

  -o                                   输出文件 (只能使用 -d 或 -o 之一)

  -d 

                                 输出目录

  -v  -verbose                           启用详细输出

  -h  --help  -?                          输出此消息

  -version                                 输出版本信息

  -jni                                          生成 JNI 样式的标头文件 (默认值)

  -force                                     始终写入输出文件

  -classpath               从中加载类的路径

  -bootclasspath      从中加载引导类的路径

                            是使用其全限定名称指定的

(例如, 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 
/* 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 
#include 

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不同,也不能相互共享使用。


零基础 JNI 入门 + 进阶_第3张图片

                  (图五)

    当Java代码调用本地方法时,VM将JNI接口指针作为参数传递给本地方法,本地方法有可以通过JNI的函数表找到与java对应的方法。


零基础 JNI 入门 + 进阶_第4张图片

                                       (图六)

    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


The following definition is provided for convenience.

#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


零基础 JNI 入门 + 进阶_第5张图片

                                   (图七)

    除了基础数据类型的数组、Class、String和Throwable外,其余java对象的数据类型在JNI中都表示为jobject。

 

    JNI中支持很多系统函数操作,这些函数支持上诉的java类型的操作及一些访问对象和注册本地方法,上诉hello-jni的例子中就有FindClass()、RegisterNatives()、NewStringUTF()等,常用的函数大全:http://blog.csdn.net/data_backups/article/details/48030425

 

    还剩一个问题

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 入门 + 进阶_第6张图片

                                                (图八)

把解析出来的函数签名对应的拷贝道签名数组的位置即可。


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格式的。调用JNIEnvNewStringUTF可将根据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

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; iGetArrayLength(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; iReleaseByteArrayElements(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; iNewByteArray(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类属性

Java
private 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方法

Java
    public 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.java
package 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");

	//获得得该类型的构造函数  函数名为  返回类型必须为 void 即 V
	//注意:该 ID特指该类class的构造函数ID , 必须通过调用 GetMethodID() 获得,且调用时的方法名必须为 ,而返回类型必须为 void (V)
	jmethodID constrocMID = (*env)->GetMethodID(env, employeecls, "", "(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方法

java
	    public 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__

编译成动态库放入工程目录中
零基础 JNI 入门 + 进阶_第7张图片

需要修改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
零基础 JNI 入门 + 进阶_第8张图片

你可能感兴趣的:(零基础 JNI 入门 + 进阶)