Android Jni使用案例

Android上层和C/C++层通信可以通过JNI实现,具体做法有两种:


一:采用默认的本地函数注册流程
  1). 编写带有native方法的Java类;
    Gpio.java 代码:
<span style="font-size:14px;">    package com.prowave.jnitest;


    public class Gpio {</span>
<span style="font-size:14px;">            public String printJNI(String s) {
		return native_printJNI(s);
	    }
	    ...
	    private native String nativePrintJNI(String inputstr);
	    ...
    }</span>
    
  2). 使用javah命令生成.h头文件;
    自动生成头文件com_prowave_jnitest_Gpio.h(头文件的命名格式是javah根据java类的包名类名自动生成):
<span style="font-size:14px;">    #include <jni.h>
    /* Header for class com_prowave_jnitest_Gpio */

    #ifndef _Included_com_prowave_jnitest_Gpio
    #define _Included_com_prowave_jnitest_Gpio
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     com_prowave_jnitest_Gpio
     * Method:    nativePrintJNI
     * Signature: (Ljava/lang/String;)Ljava/lang/String;
     */
    JNIEXPORT jstring JNICALL Java_com_prowave_jnitest_Gpio_nativePrintJNI
      (JNIEnv *, jclass, jstring);

    #ifdef __cplusplus
    }
    #endif
    #endif</span>
    
    方法名称Java_com_prowave_jnitest_Gpio_nativePrintJNI解释:Java_包名(层级关系用_分隔)_类名_方法名
    
  3). 编写Gpio.c代码实现头文件中的方法
    
<span style="font-size:14px;">    #include <jni.h>
    #include <stdio.h>
    #include "com_prowave_jnitest_Gpio.h"

    jstring JNICALL Java_com_prowave_jnitest_Gpio_nativePrintJNI(JNIEnv *env, jobject obj, jstring inputStr)
    {
        return (*env)->NewStringUTF(env, "Hello JNI! I am Native interface");
    }</span>

  4). 编写Android.mk文件,通过NDK编译生成so文件(NDK集成开发环境需要自行安装)
    
<span style="font-size:14px;">        LOCAL_PATH := $(call my-dir)
	include $(CLEAR_VARS)

	LOCAL_MODULE    := Gpio
	LOCAL_SRC_FILES := Gpio.c
	include $(BUILD_SHARED_LIBRARY)</span>

通过ndk-build编译最终生成libGpio.so文件,可供上层应用使用,比如调用Gpio.java的printJNI方法,最终获得C代码中Java_com_prowave_jnitest_Gpio_nativePrintJNI方法中返回的字符串。

注意事项:通过这种方式去实现JNI,Java代码中声明native方法时不能带下划线,比如nativePrintJNI方法不能写成native_printJNI,因为C/C++中的方法命名规则是“Java_包名(层级关系用_分隔)_类名_方法名”这种形式,以此来匹配Java类中的方法,下划线导致生成的头文件中方法名多出一个下划线,这样就匹配不上Java中定义的本地方法了。


生成头文件的方法有两个:

1). 用eclipse新建一个java项目或者android项目,在com/prowave/jnitest包下新建Gpio.java文件,添加上native方法,eclipse会自动编译java文件,生成Gpio.class文件在bin/class/com/prowave/jnitest/目录下。终端(window环境下可使用cygwin)进入bin同级目录,执行命令:javah -classpath bin/classes/ -d jni com.prowave.jnitest.Gpio,生成com_prowave_jnitest_Gpio.h头文件在jni目录。 

-classpath:指定路径,这里只要指定com/prowave/jnitest这个包所在的路径就可以了,并不是指Gpio.class的路径,所以classpath的参数为bin/classes/; 

-d jni:在当前路径生成一个jni文件夹;

com.prowave.jnitest.Gpio:java文件的包名类名。


2). 直接写一个xxx.java文件,通过javac xxx.java编译,生成xxx.class文件;新建文件目录,比如xxx.java文件里的包名是这样定义的:package com.prowave.jnitest;建立com/prowave/jnitest(这几个文件夹是层级关系),将xxx.class拷贝到最里面的文件夹jnitest;在当前目录打开终端,执行命令:javah -classpath . -d jni com.prowave.jnitest.Gpio,生成com_prowave_jnitest_Gpio.h。



二:自己重写JNI_OnLoad()函数
  1). 编写带有native方法的Java类;
    Gpio.java 代码:
<span style="font-size:14px;">    package com.prowave.jnitest;

    public class Gpio {
	    ...
	    private native String native_printJNI(String inputstr);
	    private native String native_getChar();
	    ...
    }</span>
  2). 定义C/C++文件,重写JNI_OnLoad()函数 (Gpio.c文件)
       
<span style="font-size:14px;">        #include <jni.h>
	#include <stdio.h>
	#include <stdlib.h>
	#include <string.h>

        // 定义需要映射的Java类名称
	#define JNIREG_CLASS "com/prowave/jnitest/Gpio"

	/* java类中定义的本地方法的具体实现 */
	jstring nativePrintJNI(JNIEnv *env, jobject obj, jstring inputStr)
	{
		const char *str = (const char *)(*env)->GetStringUTFChars( env,inputStr, JNI_FALSE );
		(*env)->ReleaseStringUTFChars(env, inputStr, (const char *)str );
		return (*env)->NewStringUTF(env, "Hello World! I am Native interface");
	}

    /* java类中定义的本地方法的具体实现 */
	jstring printChar(JNIEnv *env, jobject obj)
	{
		return (*env)->NewStringUTF(env, "JNI Print Char Succ!");
	}

    /* 本地方法和java类中定义的native方法映射关系
     * JNINativeMethod
     * const char* name;      java方法名称   
     * const char* signature; java方法签名
     * void* fnPtr;           c/c++的函数指针 
     */
	static JNINativeMethod methods[] = {
    	{ "native_printJNI", "(Ljava/lang/String;)Ljava/lang/String;", (void*)nativePrintJNI },
    	{ "native_getChar", "()Ljava/lang/String;", (void*)printChar },
	};

	/* 
	 * Register several native methods for one class. 
	 */  
	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) != JNI_OK) {
    	    return JNI_FALSE;
    	}
    
    	return JNI_TRUE;
	}

	/* 
	 * Register native methods for all classes we know about. 
	 * 
	 * returns JNI_TRUE on success. 
	 */  
	static int registerNatives(JNIEnv* env)
	{
    	if (!registerNativeMethods(env, JNIREG_CLASS,
                               methods, sizeof(methods) / sizeof(methods[0]))) {
    	    return JNI_FALSE;
    	}
    	return JNI_TRUE;
	}

	/* This function will be call when the library first be load.
	* You can do some init in the libray. return which version jni it support.
	*/
	jint JNI_OnLoad(JavaVM* vm, void* reserved)
	{
    	JNIEnv* env = NULL;
		if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) {
			return -1;
		}
	
		if (!registerNatives(env)) {
        	return -1;
    	}
		return JNI_VERSION_1_6;
	}</span>

  3). 编写Android.mk文件,通过NDK编译生成so文件(NDK集成开发工具需要安装)。
<span style="font-size:14px;">        LOCAL_PATH := $(call my-dir)
	include $(CLEAR_VARS)

	LOCAL_MODULE    := Gpio
	LOCAL_SRC_FILES := Gpio.c
	include $(BUILD_SHARED_LIBRARY)</span>

  通过这种方式注册的好处:
  1>. 函数名称可以自定义,无须遵循特殊的命名格式
  2>. 不需要通过javah生成头文件
  3>. 将本地函数向VM进行登记,VM能更有效率的去找到registerNativeMethods。

  4>. 可在执行期间进行抽换。由于gMethods[]是一个<名称,函数指针>对照表,在程序执行时,可多次呼叫registerNativeMethods()函数来更换本地函数之指针,而达到弹性抽换本地函数之目的。



附上案例源代码,下载地址:http://download.csdn.net/detail/visionliao/9532013


猜你喜欢

转载自blog.csdn.net/visionliao/article/details/51507085
今日推荐