今天在看android硬件抽象层时需要用到Java的JNI调用,所以在网上找了些资料学习了一下,特别在这里记录一下。Java的JNI调用主要分为两种方式静态调用方式和动态调用方式。

静态调用方式:

首先确保你的pc上安装了jdk和g++,如果没有安装请安装后在试: 打开自己常用的目录,输入以下命令:

mkdir test

vim test/HelloWorld.java

package test;

public class HelloWorld
{
	public static void main(String[] args)
	{
		System.loadLibrary("HelloWorld");
		print_hello();
	}

	public static native final void print_hello();
}

其中最后声明的方法就是我们将要调用的方法,最后会在c++里实现,如果是声明的JNI方法一定要在方法前加上native关键字,否则系统无法识别到。

之后就可以调用java命令编译了。

javac test/HelloWorld.java

之后通过javah命令生成我们的头文件。实现该头文件定义的方法。

vim test/HelloWorld.cpp

#include "Hello.h"

#include <cstdio>

void  Java_test_HelloWorld_print_1hello(JNIEnv *,jclass)
{
	printf("helloworld asianux\n");
}

 调用g++编译该头文件,生成一个动态库。

g++ test/HelloWorld.cpp  -I /usr/lib/jvm/java-6-sun/include/ -I /usr/lib/jvm/java-6-sun/include/linux/ -fPIC  --shared -o test/libHelloWorld.so

如果以上命令没有问题,那就可以运行了

java test.HelloWorld 

但是一般都会出点问题,出错信息如下:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no HelloWorld in java.library.path
	at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1738)
	at java.lang.Runtime.loadLibrary0(Runtime.java:823)
	at java.lang.System.loadLibrary(System.java:1028)
	at test.HelloWorld.main(HelloWorld.java:7)

这个时因为我们没有正确设置java的库路径导致的,执行以下命令设置该变量的值

export LD_LIBRARY_PATH=`pwd`/test

再次运行程序就可以看到正常输出了。

以上就是JNI中的静态调用。

动态调用:

动态调用相比与静态调用的主要区别有以下几点:

1.定义调用的JNINativeMethod

2.定义调用挂钩的函数

3.实现JNI_OnLoad函数

JNI_OnLoad是java jni技术的一个实现,每次java层加载System.loadLibrary之后,自动会查找改库一个叫JNI_OnLoad的函数,动态注册的时候,cpp可以通过实现JNI_OnLoad而完成jni的动态注册。

此处的代码和上面没什么差别,主要的差别在cpp文件的实现中,cpp的实现如下:

#include "Hello.h"

#include <cstdio>

static void android_print(JNIEnv *env,jclass clazz)
{
	printf("hello asianux\n");
}

static JNINativeMethod gMethods [] = 
{
	{"print_hello","()V",(void *)android_print},
};

jint JNI_OnLoad(JavaVM *vm, void *reseved)
{
	JNIEnv *env = NULL;
	jint result = -1;

	if(vm->GetEnv((void**)&env,JNI_VERSION_1_4) !=JNI_OK)
	{
		return -1;
	}

	char className[20] = {"dynamic/HelloWorld"};

	jclass clazz = (env)->FindClass( (const char*)className);

	if((env)->RegisterNatives(clazz,gMethods,1)<0)
	{
		return -1;
	}

	result = JNI_VERSION_1_4;

	return result;
}

所不同的是这次调用时将不是直接查找print_hello方法的实现,而是查找该方法的映射函数。其中JNINativeMethod 的定义主要包含如下:

第一个字段是java层声明的方法也就是我们声明的print_hello方法

第二个字段相对来说就比较复杂一点了,其对应关系如下:

"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();

"(II)V" 表示 void Func(int, int);

具体的每一个字符的对应关系如下

字符   Java类型     C类型

V      void         void

Z      jboolean     boolean

I       jint         int

J       jlong        long

D      jdouble       double

F      jfloat            float

B      jbyte            byte

C      jchar           char

S      jshort          short

数组则以"["开始,用两个字符表示

[I     jintArray       int[]

[F     jfloatArray     float[]

[B     jbyteArray     byte[]

[C    jcharArray      char[]

[S    jshortArray      short[]

[D    jdoubleArray    double[]

[J     jlongArray      long[]

[Z    jbooleanArray    boolean[]

上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾,中间是用"/" 隔开的包及类名。

而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring Ljava/lang/String; String jstring Ljava/net/Socket; Socket jobject

第三个参数就是我们最终调用函数的实现。按照静态调用的方式编译该程序,就可以看到运行结果。

对应到android的实现为:

java文件的实现:

package com.android.server;

import android.content.Context;
import android.os.IMemdevService;
import android.util.Slog;
import android.util.Log;

public class MemdevService extends IMemdevService.Stub
{
	private static final String TAG = "MemdevService";

	private int mPtr = 0;


	MemdevService()
	{
		mPtr = init_native();
	}

	public void setVal(String val)
	{
		if(mPtr == 0)
		{
			return ;
		}
		setVal_native(mPtr,val);
	}

	public String getVal()
	{
		if(mPtr == 0)
		{
			return null;
		}

		String str = getVal_native(mPtr);

		return str;
	}

	private static native int init_native();
	private static native void setVal_native(int ptr,String val);
	private static native String getVal_native(int ptr);
};

其中我们定义了三个方法,第一个方法返回一个init类型参数,第二个方法无返回值但是需要传递两个int类型的参数,第三个方法返回一个String类型的参数,带一个int类型的参数

其c++文件实现为:

#define LOG_TAG "MemdevServiceJNI"

#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"

#include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/hardware.h>
#include <hardware/memdev.h>

namespace android
{
	
	static inline int memdev_device_open(const hw_module_t *module,struct memdev_device_t **device)
	{
		int fd = module->methods->open(module,MEMDEV_HARDWARE_MODULE_ID,(struct hw_device_t**)device);

		return fd;
	}


	static void memdev_setVal(JNIEnv *env,jobject clazz,jint ptr,jstring value)
	{
		memdev_device_t *device = (memdev_device_t*)ptr;

		LOGE("*****memdev_setVal ptr=%d\t value=%d\n",ptr,value);

		if(!device)
		{
			LOGE("device memdev is not open");
			return ;
		}

		const char* chars = env->GetStringUTFChars(value, NULL);


		char *str = strdup(chars);

		device->set_val(device,str);

	}

	static jstring memdev_getVal(JNIEnv *env,jobject clazz,jint ptr)
	{
		memdev_device_t *device  = (memdev_device_t*)ptr;

		if(!device)
		{
			LOGE("Device memdev is not open");
		}
		
		char *str;
		int value = device->get_val(device,&str);
		
		int strLen = strlen(str);
		jstring jstr = env->NewStringUTF(str);
		return jstr;

	}

	static jint memdev_init(JNIEnv *env,jclass clazz)
	{
		memdev_module_t *module;
		memdev_device_t *device;

		LOGI("Init HAL stub memdev......");

		if(hw_get_module(MEMDEV_HARDWARE_MODULE_ID,(const struct hw_module_t **)&module) == 0);
		{
			LOGI("device with find ....");
		
			if(memdev_device_open(&(module->common),&device) == 0)	
			{
				LOGI("device memdev is open %d\n",device->fd);
				return (jint)device;
			}

			LOGI("Failed to open device");
			return 0;

		}
	}

	static const JNINativeMethod method_table[] = 
	{
		{"init_native","()I",(void*)memdev_init},
		{"setVal_native","(ILjava/lang/String;)V",(void*)memdev_setVal},
		{"getVal_native","(I)Ljava/lang/String;",(void*)memdev_getVal},

	};

	int register_android_server_MemdevService(JNIEnv *env)
	{
		return jniRegisterNativeMethods(env,"com/android/server/MemdevService",
				method_table,NELEM(method_table));
	}
};

在android源码树中编译无问题,正常通过。

参考自:http://blog.chinaunix.net/uid-10275706-id-3241480.html