Android使用JNI

来源:互联网 发布:威戈背包知乎 编辑:程序博客网 时间:2024/05/17 23:42

参考:

JNI官方:http://developer.android.com/training/articles/perf-jni.html

JNI入门:http://blog.sina.com.cn/s/blog_5f35912f0100vvnn.html

http://blog.sina.com.cn/s/blog_5de73d0b0101chk1.html

http://wenku.baidu.com/view/d670c823a5e9856a56126051.html


Java中的native函数与jni中的C函数是怎样联系起来的:

(1)Test.java,分别代表了四种java函数:非static无参函数、static无参函数、非static有参函数、static有参函数。


package com.jeremyfeinstein.slidingmenu.example;public class Test {public native String test();public native static void test2();public native void test3(String str);public native static void test4(String str);}

(2)使用javah.exe生成com_jeremyfeinstein_slidingmenu_example_Test.h,点击:(关于javah.exe和NDK的配置请参见:ANDROID 一键搞定JNI创建C头文件),如下所示:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_jeremyfeinstein_slidingmenu_example_Test */#ifndef _Included_com_jeremyfeinstein_slidingmenu_example_Test#define _Included_com_jeremyfeinstein_slidingmenu_example_Test#ifdef __cplusplusextern "C" {#endif/* * Class:     com_jeremyfeinstein_slidingmenu_example_Test * Method:    test * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_jeremyfeinstein_slidingmenu_example_Test_test(JNIEnv *, jobject);/* * Class:     com_jeremyfeinstein_slidingmenu_example_Test * Method:    test2 * Signature: ()V */JNIEXPORT void JNICALL Java_com_jeremyfeinstein_slidingmenu_example_Test_test2(JNIEnv *, jclass);/* * Class:     com_jeremyfeinstein_slidingmenu_example_Test * Method:    test3 * Signature: (Ljava/lang/String;)V */JNIEXPORT void JNICALL Java_com_jeremyfeinstein_slidingmenu_example_Test_test3(JNIEnv *, jobject, jstring);/* * Class:     com_jeremyfeinstein_slidingmenu_example_Test * Method:    test4 * Signature: (Ljava/lang/String;)V */JNIEXPORT void JNICALL Java_com_jeremyfeinstein_slidingmenu_example_Test_test4(JNIEnv *, jclass, jstring);#ifdef __cplusplus}#endif#endif
在当前工程目录的jni目录下自动生成文件com_jeremyfeinstein_slidingmenu_example_Test.h,这个.h文件的名称组成是:java类Test的全称(包名+类名),jni就是通过这个名称组合来找到java类对应C/C++文件的,进而再根据C/C++函数名称来找到对应的调用函数,生成的具有jni格式的C/C++函数名称组成是:Java_包名_java类名_java类中的对应方法名(Java_Java类全称_Java类中对应的方法名),方法参数组成规则是:每个函数的第一个参数都是JNIEvn *类型的;如果函数在Java类中是static的函数,则第二个参数必须是jclass类型,代表当前Java类,如果函数在Java类中是非static类型的函数,则第二个参数必须是jobject类型的,代表的是当前Java类的实例对象;剩下的参数就和java类中的函数的参数一一对应了。

(3)在当前工程目录下的jni目录中找到生成的com_jeremyfeinstein_slidingmenu_example_Test.h,然后再新建一个com_jeremyfeinstein_slidingmenu_example_Test.c文件,并实现com_jeremyfeinstein_slidingmenu_example_Test.h中的所有函数,这里和C/C++的写法一样,只是必须按照jni的格式来写C/C++代码,或者是直接调用已经存在的C/C++代码,这些已经存在的C/C++代码存放的位置也是在当前工程目录下的jni目录中,这样在com_jeremyfeinstein_slidingmenu_example_Test.c中就可以直接遵照JNI标准来调用这部分C/C++代码了。除此之外,还有一点:在具有JNI格式的.c/.cpp文件中可以完全像C/C++文件一样使用C/C++语法,例如可以在.c/.cpp文件中写其他C/C++函数(供其他函数调用),只是这些函数本身必须遵照JNI格式;可以在jni下建立任意的C/C++文件,让程序具有jni格式的C/C++文件调用:


这个地方的test.c和test.h是完全用C/C++语法写的代码,可以在com_jeremyfeinstein_slidingmenu_example_Test.c中进行直接调用;



#include <stdio.h>#include <jni.h>#include "com_jeremyfeinstein_slidingmenu_example_Test.h"JNIEXPORT jstring JNICALL Java_com_jeremyfeinstein_slidingmenu_example_Test_test(JNIEnv *env, jobject obj) {return (**env).NewStringUTF(env, "Say hello frome C to Android !");}JNIEXPORT void JNICALL Java_com_jeremyfeinstein_slidingmenu_example_Test_test2(JNIEnv *env, jclass clz) {}JNIEXPORT void JNICALL Java_com_jeremyfeinstein_slidingmenu_example_Test_test3(JNIEnv *env, jobject obj, jstring str) {}JNIEXPORT void JNICALL Java_com_jeremyfeinstein_slidingmenu_example_Test_test4(JNIEnv *env, jclass clz, jstring str) {}

(4)上面.h和.c都完成之后,就必须在当前目录下写一个Android.mk文件,这个文件类似C/C++里的Makefile文件,但是Makefile中的依赖文件、编译规则等等都已经被Android系统完成了,Android.mk里的规则很简单,我们只需要按照模板修改添加需要编译的.c或.cpp文件和编译最终生成.so名称即可,关于Android.mk详细语法规则参见:http://blog.csdn.net/habbyge/article/details/11267081,这里需要特别注意:Android.mk的存放位置,必须和当前编译的C/C++(JNI格式)文件放在同意目录下。


LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE:= testLOCAL_SRC_FILES := com_jeremyfeinstein_slidingmenu_example_Test.cinclude $(BUILD_SHARED_LIBRARY)

(5)之后,就可以使用NDK编译生成libtest.so文件了,点击:,这个文件被生成在当前工程目录下的libs/armeabi目录下(libs/armeabi/libtest.so),自此就可以在Java中使用这个C/C++代码了,具体使用方法,参见:http://blog.csdn.net/habbyge/article/details/19857113。



补充:

以上主要是介绍在Java中调用C/C++代码,下面说明C/C++调用Java代码的方法:

C++中调用Java会比较麻烦一些,因为需要在C++中获取Java的运行环境,并寻找我们要用的类和方法。首先我们需要了解几个概念

JavaVM这个代表java的虚拟机。所有的工作都是从获取虚拟机的接口开始的,如何获取这个接口呢?我们之前导入C的组件时调用了:System.loadLibrary(“test”);

调用该方法时,java会先调用该组件的JNI_OnLoad()函数.其用途有二: 

一是:告诉java VMC组件使用那一个JNI版本。如果你没有提供JNI_OnLoad()函数,VM会默认使用最老的JNI 1.1版本。由于新版的JNI做了许多扩充,如果需要使用JNI的新版功能,例如JNI 1.4java.nio.ByteBuffer,就必须藉由JNI_OnLoad()函数来告知VM

二是:由于VM执行到System.loadLibrary()函数时,就会立即先呼叫JNI_OnLoad(),所以C组件的开发者可以藉由JNI_OnLoad()来进行C组件内的初期值之设定,也就是获取JavaVM接口。

如:

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JniHelper::setJavaVM(vm); // 获取JavaVM接口
    return JNI_VERSION_1_4; // 告知java使用什么版本的VM
}

JNIEnv: 代表Java环境。通过这个JNIEnv*指针,就可以对Java端的代码进行操作。如,创建Java类得对象,调用Java对象的方法,获取Java对象的属性等。通过之前获得的JavVM我们可以获取JNIEnv

static bool getEnv(JNIEnv **env) {
    bool bRet = false;
    do {
        if (JAVAVM->GetEnv((void**)env, JNI_VERSION_1_4) != JNI_OK) {
            LOGD("Failed to get the environment using GetEnv()");
            break;
        }
        if (JAVAVM->AttachCurrentThread(env, 0) < 0) {
            LOGD("Failed to get the environment using AttachCurrentThread()");
            break;
        }
        bRet = true;
    } while (0);

    return bRet;
}

有了上面的准备,下面我们就可以开始调用java的东西了:

一:获取对象的类id

我们只要知道类的名字就可以通过JNIEnv来获取classid

jclass classID = pEnv->FindClass(className);

二:获取要调用的方法id,包括静态和普通方法。

    jmethodID methodID = pEnv->GetStaticMethodID(classID, methodName, paramCode);

    jmethodID methodID = pEnv->GetMethodID(classID,methodName, paramCode);

    这其中的最后一个参数代表方法的参数信息,因为java支持多态,只有完整的参数信息才可以找到唯一的方法。这个参数有着特定的格式,见附录。

三:调用方法,同样包括静态和普通。

    pEnv ->CallStatic***Method(classID,methodID);

    pEnv ->Call***Method(classID,methodID);

 不同的返回参数,调用不同的方法,如CallBooleanMethodCallIntMethodCallObjectMethodCallStaticIntMethod等等。
函数签名是一个字符串:"(M)N",括号中的内容是函数的参数类型,括号后面表示函数的返回值。
字符  Java类型     C类型
  void       void
  jboolean    boolean
  jint        int
  jlong      long
  jdouble     double
  jfloat       float
  jbyte        byte
  jchar        char
  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

    一个例外是String类,其对应的类为jstring
    Ljava/lang/String; String jstring
    Ljava/net/Socket; Socket jobject

    Ljava/lang/String; String jstring
    Ljava/net/Socket; Socket jobject
 

附:函数属性签名规则:

GetMethodID最后一个参数是签名字符串,用来标示java函数和成员的唯一性。因为java中存在重载函数,所以一个函数名不足以唯一指定一个函数,这时候就需要签名字符串来指定函数的参数列表和返回值类型了。具体的每一个字符的对应关系如下数组则以”["开始,用两个字符表示

0 0
原创粉丝点击