jni_jnienv - CSDN
jni 订阅
JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。 [1]  从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。 展开全文
JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。 [1]  从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。
信息
简    写
JNI
外文名
Java Native Interface
编程语言
Java
中文名
Java本地接口
学    科
Java和本地代码间的双向交互
目    的
使用 Java 本地接口书写程序
JNI简介
SUN公司发布的Java 本地接口(JNI)提供了将Java与C/C++、汇编等本地代码集成的方案,该规范使得在 Java 虚拟机内运行的 Java 代码能够与其它编程语言互相操作,包括创建本地方法、更新Java对象、调用Java方法,引用 Java类,捕捉和抛出异常等,也允许 Java代码调用 C/C++或汇编语言编写的程序和库。作为一个标准程序接口,它没有对底层 Java虚拟机的实现施加任何限制,并具有以下特点:二进制兼容。本地方法库与同一平台上所有Java 虚拟机之间实现二进制兼容,即对于给定平台开发人员只需要维护一种版本的本地方法库。效率高。为了实现实时系统,JNI 在效率与虚拟机无关性之间进行了优化,以保障高效运行。功能强。JNI 提供了大量的函数及接口让本地方法与Java 虚拟机内核相互操作,增强两者的功能。本地代码与 Java 虚拟机之间是通过 JNI 函数实现相互操作的。JNI 函数通过接口指针来获得,本地方法将 JNI 接口指针当作参数来接受。虚拟机保证在从相同的 Java 线程中对本地方法进行多次调用时,传递给本地方法的接口指针是相同的,本地方法被不同的 Java 线程调用时,它接受不同的 JNI接口指针。 [2] 
收起全文
精华内容
参与话题
  • Android开发中,随着对移动程序的安全性、性能等方面的重视,JNI技术也越发重要。 如今,多数企业在招聘中、高级程序员时,基本上都要求熟悉JNI开发,所以,掌握JNI技术,也是我们迈进心仪企业的必备条件。 本套...
  • Android开发中,随着对移动程序的安全性、性能等方面的重视,JNI技术也越发重要。 如今,多数企业在招聘中、高级程序员时,基本上都要求熟悉JNI开发,所以,掌握JNI技术,也是我们迈进心仪企业的必备条件。 本套课程...
  • JNI详解------完整Demo

    万次阅读 多人点赞 2018-08-07 14:34:53
    为什么要用JNI?因为有些功能JAVA无法提供,比如对扫描仪驱动,我现在就是要搞这个,网上给的例子都是SB.我气不过,便要自己去搞.感觉很悲剧.搜来想去,只能想办法通过C/C++来操作,然后用JAVA去调用C.这就需要JNI了.   ...

    为什么要用JNI?因为有些功能JAVA无法提供,比如对扫描仪驱动,我现在就是要搞这个,网上给的例子都是SB.我气不过,便要自己去搞.感觉很悲剧.搜来想去,只能想办法通过C/C++来操作,然后用JAVA去调用C.这就需要JNI了.

     

    什么是JNI?

    JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++).这是百度百科上说的.通俗来说,就是JAVA调用C/C++函数的接口.如果你要想调用C系列的函数,你就必须遵守这样的约定.

    JNI接口都长什么样?

    就一个native的关键字.

    public  class NativeDemo {
    	{
    		/**
    		 * 系统加载其他的语言的函数
    		 */
    		System.load("C:\\Users\\Administrator\\Desktop\\com\\Hello.dll");
    	}
    	/**
    	 * 就这个natice关键字.标记了这个接口,看起来像是abstract
    	 */
    	public native void sayHello();
    	
    	
    	public static void main(String[] args) {
    		new NativeDemo().sayHello();
    	}
    }

    静态代码块先不讲。先看native方法。

    sayHello()方法加了一个关键字native,就代表是一个native接口.执行这个方法时,会根据jni.h来找到真正的C来编写的sayHello()的实际函数.

    jni.h是什么?

    它实际上就存在%JAVA_HOME%\bin\include下面的一个文件,另外还有个%JAVA_HOME%\bin\include\win32下的jni_md.h.

    这东西不说其他的作用,我也不清楚,只知道它里面存储了大量的函数和对象,它有个很好的方法就是通过native接口名,获取C函数.

    打个比方类似如下:

    public static String getCMethod(String javaMethodName);

    它可以根据你的java接口,找到C函数并调用.

    但这就意味着,你不能在C里随意写函数名,因为如果你写的java方法叫native aaa();C函数也叫aaa();但jni.h通过getCMethod(String javaMethodName)去找的结果是xxx();那这样就无法调用了.

    既然不能随意写,怎么办?

    没事,jdk提供了一个通过java方法生成c函数接口名的工具javah.

    javah是什么?

    就像java是运行main方法一样,javah就是提供具有native method的java对象的c函数接口.

    dos命令如下:

    javac NativeDemo.java
    javah NativeDemo

    这个命令可以提供一个c函数的接口.

    上面那个NativeDemo被javah了之后就生成了一个文件Hello.h(可能我改了名字),就是C函数的接口.里面有方法名和返回值什么的.

    Hello.h长什么样?

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include "jni.h"
    /* Header for class NativeDemo */
    
    #ifndef _Included_NativeDemo
    #define _Included_NativeDemo
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     NativeDemo
     * Method:    sayHello
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_NativeDemo_sayHello
      (JNIEnv *, jobject);
    
    #ifdef __cplusplus
    }
    #endif
    #endif

     

    最重要的C函数接口就是这样:JNIEXPORT void JNICALL Java_NativeDemo_sayHello(JNIEnv *, jobject);

    JNIEXPORT :在Jni编程中所有本地语言实现Jni接口的方法前面都有一个"JNIEXPORT",这个可以看做是Jni的一个标志,至今为止没发现它有什么特殊的用处。

    void :这个学过编程的人都知道,当然是方法的返回值了。

    JNICALL :这个可以理解为Jni 和Call两个部分,和起来的意思就是 Jni调用XXX(后面的XXX就是JAVA的方法名)。

    Java_NativeDemo_sayHello:这个就是被上一步中被调用的部分,也就是Java中的native 方法名,这里起名字的方式比较特别,是:包名+类名+方法名。

    JNIEnv * env:这个env可以看做是Jni接口本身的一个对象,jni.h头文件中存在着大量被封装好的函数,这些函数也是Jni编程中经常被使用到的,要想调用这些函数就需要使用JNIEnv这个对象。例如:env->GetObjectClass()。(详情请查看jni.h)

    jobject obj:代表着native方法的调用者,本例即new NativeDemo();但如果native是静态的,那就是NativeDemo.class .

    也就是说,我们的native sayHello()方法实际上是运行C的Java_NativeDemo_sayHello()这个方法,我们是不能随意写C函数名的的,只能这样写。

    接下来我们可以照着接口去写真正的函数方法了.新建Hello.cpp

    /* Replace "dll.h" with the name of your header */
    #include "Hello.h"
    #include <windows.h>
    #include <iostream>
    
    JNIEXPORT void JNICALL Java_NativeDemo_sayHello(JNIEnv *, jobject){
    	using namespace std;
    	cout << "Hello___________World";
    } 

    这个方法什么都没有做,就打印了一句话"Hello___________World"

    然后将Hello.h和Hello.cpp编译运行出dll文件.生成Hello.dll

    这个过程中可以能编译会出现问题.

    说“jni.h”: No such file or directory:

    你需要把jdk里面的那俩jni.h给拷贝过来,再编译,如果还出错.需要把Hello.h里的头部#include<jni.h> 改写成 #include "jni.h".由尖括号改成双引号,具体咋回事,咱不是C程序员不清楚.

    运行下试试吧

    这样你现在就拥有了下列文件

    实际上你现在就只需要NativeDemo和Hello.dll就行了。

    别急,还有一个。NativeDemo里的静态代码块请准确把dll库给load进去

    System.load("C:\\Users\\Administrator\\Desktop\\com\\Hello.dll");

    然后编译java,运行

    C:\Users\Administrator\Desktop\com>javac NativeDemo.java
    
    C:\Users\Administrator\Desktop\com>java NativeDemo
    Hello___________World
    C:\Users\Administrator\Desktop\com>

    非常完美。C++里的打印被调用了。

    展开全文
  • JNI:Java与C++的美好结合

    万人学习 2019-06-24 13:22:29
    这课程,我们的主题就是对C函数,或是对Shared Library,或是对JNI的本地代码的代码写法,我们要讲求它的稳定性。也就是要让它有更好的设计,是我们对这设计做优化的思考,也是架构师的一个很重要的职责。本节,我...
  • Android JNI机制

    千次阅读 2015-09-14 21:40:24
    由前面基础知识可知,Android的应用层由Java语言编写,Framework框架层则是由Java代码与C/C++语言实现,之所以由两种不同的语言组合开发框架层,是因为Java代码是与硬件环境彻底“隔离”的跨平台语言,Java代码无法...

     

    由前面基础知识可知,Android的应用层由Java语言编写,Framework框架层则是由Java代码与C/C++语言实现,之所以由两种不同的语言组合开发框架层,是因为Java代码是与硬件环境彻底“隔离”的跨平台语言,Java代码无法直接操作硬件。比如:Android系统支持大量传感器,Java运行在虚拟机中,无法直接得到传感器数据,而Android系统基于Linux操作系统,在Linux操作系统中C/C++通过Linux提供的系统调用接口可以直接访问传感器硬件驱动,Java代码可以将自己的请求,交给底层的本地C/C++代码实现间接的对传感器的访问。另外,Java代码的执行效率要比C/C++执行效率要低,在一些对性能要求比较高的场合,也要使用C/C++来实现程序逻辑。

    既然Java代码要请求本地C/C++代码,那么二者必须要通过一种媒介或桥梁联系起来,这种媒介就是Java本地接口(Java NativeInterface),它是Java语言支持的一种本地语言访问方式,JNI提供了一系列接口,它允许Java与C/C++语言之间通过特定的方式相互调用、参数传递等交互操作。

    通常在以下几种情况下考虑使用JNI:

    Ø 对处理速度有要求

    Java代码执行速度要比本地代码(C/C++)执行速度慢一些,如果对程序的执行速度有较高的要求,可以考虑使用C/C++编写代码,然后在通过Java代码调用基于C/C++编写的部分。

    Ø 硬件控制

    如前面所述,Java运行在虚拟机中,和真实运行的物理硬件之间是相互隔离的,通常我们使用本地代码C实现对硬件驱动的控制,然后再通过Java代码调用本地硬件控制代码。

    Ø 复用本地代码

    如果程序的处理逻辑已经由本地代码实现并封装成了库,就没有必要再重新使用Java代码实现一次,直接复用该本地代码,即提高了编程效率,又确保了程序的安全性和健壮性。

     

    4.2 JNI原理

    在计算机中,每种编程语言都有一个执行环境(Runtime),执行环境用来解释执行语言中的语句,不同的编程语言的执行环境就好比如西游记中的“阴阳两界”一样,一般人不能同时生存在阴阳两界中,只有“黑白无常”能自由穿梭在阴阳两界之间,“黑白无常”往返于阴阳两界时手持生死簿,“黑白无常”按生死簿上记录着的人名“索魂”。

    4.2.1 JavaVM与JNIEnv

    Java语言的执行环境是Java虚拟机(JVM),JVM其实是主机环境中的一个进程,每个JVM虚拟机进程在本地环境中都有一个JavaVM结构体,该结构体在创建Java虚拟机时被返回,在JNI中创建JVM的函数为JNI_CreateJavaVM。

    JNI_CreateJavaVM(JavaVM **pvm, void **penv, void*args);


     

    JavaVM结构中封装了一些函数指针(或叫函数表结构),JavaVM中封装的这些函数指针主要是对JVM操作接口,如下面代码所示:

    @jni.h

    struct JNIInvokeInterface_ {

    void *reserved0;

    void *reserved1;

    void *reserved2;

    jint (JNICALL *DestroyJavaVM)(JavaVM *vm);     // 销毁Java虚拟机并回收资源,只有JVM主线程可以销毁

     jint (JNICALL *AttachCurrentThread)(JavaVM *vm,void **penv, void *args); // 连接当前线程为Java线程

      jint (JNICALL *DetachCurrentThread)(JavaVM*vm);                // 释放当前Java线程

      jint (JNICALL *GetEnv)(JavaVM *vm, void**penv, jint version);          // 获得当前线程的Java运行环境

      jint (JNICALL*AttachCurrentThreadAsDaemon)(JavaVM *vm, void **penv, void *args);// 连接当前线程作为守护线程

    };

     

    struct JavaVM_ {

        const struct JNIInvokeInterface_*functions;


      jint DestroyJavaVM() {

            returnfunctions->DestroyJavaVM(this);

        }

    …省略部分代码

        jint GetEnv(void **penv, jintversion) {

            returnfunctions->GetEnv(this, penv, version);

      }

      …省略部分代码

    };

     

    #ifdef __cplusplus


       typedef  JavaVM_  JavaVM;


    #else


       typedef  const structJNIInvokeInterface_  *JavaVM;


    #endif

    通过上面代码分析可知,JNIInvokeInterface_结构封装了几个和JVM相关的功能函数,如销毁JVM,获得当前线程的Java执行环境。另外,在C和C++中JavaVM的定义有所不同,在C中JavaVM是JNIInvokeInterface_类型指针,而在C++中又对JNIInvokeInterface_进行了一次封装,比C中少了一个参数,这也是为什么JNI代码更推荐用C++来编写的原因。

    JNIEnv是当前Java线程的执行环境,一个JVM对应一个JavaVM结构,而一个JVM中可能创建多个Java线程,每个线程对应一个JNIEnv结构,它们保存在线程本地存储(TLS)中。因此,不同的线程的JNIEnv是不同,也不能相互共享使用。

    JNIEnv结构也是一个函数表,在本地代码中通过JNIEnv的函数表来操作Java数据或调用Java方法。也就是说,只要在本地代码中拿到了JNIEnv结构,就可以在本地代码中调用Java代码。

     

    @jni.h中对JNIEnv的定义如下:

    struct JNINativeInterface_ {

            …

            jclass (JNICALL*FindClass) (JNIEnv *env, const char *name);

           …定义大量JNI函数指针

    };

     

    struct JNIEnv_ {

            const structJNINativeInterface_ *functions;

            jclass FindClass(constchar *name) {

                   return functions->FindClass(this, name);      //调用JNINativeInterface_中的函数指针

            }

           …省略部分代码

    };

     

    #ifdef __cplusplus


            typedef JNIEnv_ JNIEnv;


    #else


            typedef const structJNINativeInterface_ *JNIEnv;


    #endif

    由上面代码可知,和JavaVM类似,JNIEnv在C代码和C++代码中的使用方式也是不一样的,在C++中对JNINativeInterface_结构又进行了一次封装,调用起来更方便。

             总体来说,JNI其实就是定义了Java语言与本地语言间的一种沟通方式,这种沟通方式依赖于JavaVM和JNIEnv结构中定义的函数表,这些函数表负责将Java中的方法调用转换成对本地语言的函数调用。

    4.3 JNI中的数据传递

    4.3.1 JNI基本类型

    当Java代码与本地C\C++代码相互调用时,肯定会有参数数据的传递。两者属于不同的编程语言,在数据类型上有很多差别。JNI要保证它们两者之间的数据类型和数据空间大小的匹配。尽管C和Java中都拥有int和char的数据类型,但是他们的长度却不尽相同。在C语言中,int类型的长度取决与平台,char类型为1个字节,而在Java语言中,int类型恒为4字节,char类型为2字节。为了使Java语言和本地语言类型、长度匹配,JNI中定义了jint,jchar等类型,在JNI中定义了一些新的数据类型,如下表所示。

    表xx-xx

    Java Language Type

    JNI Type

    boolean

    jboolean

    byte

    jbyte

    char

    jchar

    short

    jshort

    int

    jint

    long

    jlong

    float

    jfloat

    double

    jdouble

    All Reference type

    jobject

    由Java类型和JNI数据类型的对应关系可以看到,这些新定义的JNI类型名称和Java类型名称具有一致性,只是在前面加了个j,如int对应jint,long对应jlong。我们可以通过JDK目录中的jni.h和jni_md.h来更直观的了解:

    @ jni_md.h

    …省略部分代码

    typedef long        jint;

    typedef __int64    jlong;

    typedef signed char      jbyte;

    …省略部分代码

    @jni.h

    // JNI类型与C/C++类型对应关系声明

    typedef unsigned char   jboolean;

    typedef unsigned short  jchar;

    typedef short                 jshort;

    typedef float                  jfloat;

    typedef double              jdouble;

    typedef jint                     jsize;

    由jni头文件可以看出,jint对应的是C/C++中的long类型,即32位整数,而不是C中的int类型(C中的int类型长度依赖于平台)。所以如果要在本地方法中要定义一个jint类型的数据,规范的写法应该是 jinti=123L;

    再比如jchar代表的是Java类型的char类型,实际上在C/C++中却是unsigned short类型,因为Java中的char类型为两个字节,jchar相当于C/C++中的宽字符。所以如果要在本地方法中要定义一个jchar类型的数据,规范的写法应该是jcharc=L'C';

    实际上,所有带j的类型,都是JNI对应的Java的类型,并且jni中的类型接口与本地代码在类型的空间大小是完全匹配的,而在语言层次却不一定相同。在本地方法中与JNI接口调用时,要在内部都要转换,我们在使用的时候也需要小心。

    4.3.2 JNI引用类型

    在本地代码中为了访问Java运行环境中的引用类型,在JNI中也定义了一套对应的引用类型,它们的对应关系如下:

     

    JNI引用类型

    Java引用类型

    jobject

    所有引用类型父类Object

    jclass

    java.lang.Class类型

    jstring

    java.lang.String类型

    jarray

    数组类型

    jobjectArray

    对象数组类型

    jbooleanArray

    布尔数组类型

    jbyteArray

    字节数组类型

    jcharArray

    字符数组类型

    jshortArray

    短整形数组类型

    jintArray

    整形数组类型

    jlongArray

    长整形数组类型

    jfloatArray

    浮点数组类型

    jdoubleArray

    双精度数组类型

    jthrowable

    java.lang.Throwadble类型

     

    由上表内容可知,JNI引用类型都是以j开头类型。与Java中所有类的父类为Object一样,所有的JNI中的引用类型都是jobject的子类,JNI这些j类型和Java中的类一一对应,只不过名字稍有不同而已。

    4.4 Java访问本地方法

    由4.1节可知,在某些情况下一些功能由本地代码来实现,这时Java代码需要调用这些本地代码,在调用本地代码时,首先要保证本地代码被加载到Java执行环境中并与Java代码链接在一起,这样当Java代码在调用本地方法时能保证找到并调用到正确的本地代码,然后在Java中要显示声明本地方法为native方法。其实现步骤如下:

    ·        编写Java代码,在Java代码中加载本地代码库

    ·        在Java中声明本地native方法

    ·        调用本地native方法

    例如:

    public class HelloJNI{

             static{

                       System.loadLibrary(“hellojni”);  // 通过System.loadLibrary()来加载本地代码库

    }

     

             privatestatic native String getJNIHello();    // 由于该方法的实现在本地代码中,所以加上native关键字进行声明

     

             publicstatic void main(String args[]){

                       System.out.println(HelloJNI.getJNIHello()); // 调用本地方法

    }

    }

    上述代码的执行需要本地代码库hellojni,正常运行的话会在屏幕上打印:Helloworld字符串。由代码可知,在Java中调用本地代码不是很复杂,本地代码库的加载系统方法System.loadLibrary在static静态代码块中实现的,这是因为静态代码块只会在Java类加载时被调用,并且只会被调用一次。本地代码库的名字为hellojni,如果在Windows中则其对应的文件名为:hellojni.dll,如果在Linux中,其对应的文件名为libhellojni.so。

    思考:

    1. 可不可以将本地代码库的加载放到构造方法中?放在非静态代码块中呢?

    2. native方法可以声明为abstract类型的吗?

    Native关键字本身和abstract关键字冲突,他们都是方法的声明,只是一个是把方法实现移交给子类,另一个是移交给本地代码库。如果同时出现,就相当于即把实现移交给子类,又把实现移交给本地操作系统,那到底谁来实现具体方法呢?

     

    4.5 JNI访问Java成员

    在JNI调用中,不仅仅Java可以调用本地方法,本地代码也可以调用Java中的方法和成员变量。在Java1.0中规定:Java和C本地代码绑定后,程序员可以直接访问Java对象数据域。这就要求虚拟机暴露它们之间内部数据的绑定关系,基于这个原因,JNI要求程序员通过特殊的JNI函数来获取和设置数据以及调用java方法。

    Java中的类封装了属性和方法,要想访问Java中的属性和方法,首先要获得Java类或Java对象,然后再访问属性、调用方法。

    在Java中类成员指静态属性和静态方法,它们属于类而不属于对象。而对象成员是指非静态属性和非静态方法,它们属于具体一个对象,不同的对象其成员是不同的。正因为如此,在本地代码中,对类成员的访问和对对象成员的访问是不同的。

    在JNI中通过下面的函数来获得Java运行环境中的类。

    jclass FindClass(const char *name);

    name:类全名,包含包名,其实包名间隔符用“/”代替“.”

    例如:

    jclass jActivity = env->FindClass(“java/lang/String”);

    上述JNI代码获得Android中的Activity类保存在jActivity中。

    在JNI中Java对象一般都是作为参数传递给本地方法的。

    例如:

    Java代码:

    package com.test.exam1;

    class MyClass{

             privateint mNumber;

             privatestatic String mName;

             publicMyClass(){

    }

     

    publicvoid printNum(){

       System.out.println(“Number:” + mNumber);

    }

     

    publicstatic void printNm(){

       System.out.println(“Number:” + mNumber);

    }

    }

     

    class PassJavaObj{

             static{

                       System.loadLibrary(“native_method”);

             }

             privatenative static void passObj(String str);

     

             publicstatic void main(String arg[]){

                       passObj(“HelloWorld”);

    }

    }

    本地代码:

    void Java_com_test_exam1_PassJavaObj_passObj

    (JNIEnv * env, jclassthiz, jobject  str)

    {

    }

    在上述例子中,Java代码中将“Hello World”字符串对象传递给了本地代码,在本地代码对应的方法中,共有三个参数,其中前两个参数是由Java运行环境自动传递过来的,env表示当前Java代码的运行环境,thiz表示调用当前本地方法的对象,这两个参数在每个本地方法中都有。第三个参数str就是我们传递过来的字符串。

    在本地方法中拿到了类或对象后,JNI要求程序员通过特殊的JNI函数来获取和设置Java属性以及调用java方法。

    4.5.1取得Java属性ID和方法ID

    为了在C/C++中表示Java的属性和方法,JNI在jni.h头文件中定义了jfieldID和jmethodID类型来分别代表Java对象的属性和方法。我们在访问或是设置Java属性的时候,首先就要先在本地代码取得代表该Java属性的jfieldID,然后才能对本地代码的Java属性进行操作。同样的,我们需要调用Java方法时,也需要取得代表该方法的jmethodID才能进行Java方法调用。

    使用JNIEnv提供的JNI方法,我们就可以获得属性和方法相对应的jfieldID和jmethodID:

    @jni.h

    // 根据属性签名返回 clazz类中的该属性ID

    jfieldID GetFieldID(jclass clazz, const char*name, const char *sig);

    // 如果获得静态属性ID,则调用下面的函数

    jfieldID GetStaticFieldID(jclass clazz, constchar *name, const char *sig);

     

    // 根据方法签名返回clazz类中该方法ID

    jmethodID GetMethodID(jclass clazz, const char*name, const char *sig);


    // 如果是静态方法,则调用下面的函数实现

    jmethodID GetStaticMethodID(jclass clazz, constchar *name, const char *sig);

    可以看到这四个方法的参数列表都是一模一样的,下面来分析下每个参数的含义:

    ·        jclass clazz:要取得成员对应的类

    ·        const char *name:代表我们要取得的方法名或者属性名

    ·        const char *sig:代表我们要取得的方法或属性的签名

    我们将一个例子进行简单修改:

    package com.test.exam2;

    class MyClass{

             privateint mNumber;

             privatestatic String mName = “Michael”;

             publicMyClass(){

                       mNumber= 1000;

    }

     

    publicvoid printNum(){

       System.out.println(“Number:” + mNumber);

    }

     

    publicstatic void printNm(){

       System.out.println(“Number:” + mNumber);

    }

    }

     

    class NativeCallJava{

             static{

                       System.loadLibrary(“native_callback”);

             }

             privatenative static void callNative(MyClass cls);

     

             publicstatic void main(String arg[]){

                       callNative(newMyClass());

    }

    }

    本地代码:

    void Java_com_test_exam2_NativeCallJava_callNative(JNIEnv* env, jclassthiz, jobject  obj)

    {

             jclass  myCls = env->GetObjectClass(obj);

             jfieldIDmNumFieldID = env->GetFieldID(myCls, “mNumber”, “I”);

             jfieldIDmNameFieldID = env->GetStaticFieldID(myCls, “mName”, “java/lang/String”);

     

             jmethodIDprintNumMethodID = env->GetMethodID(myCls, “printNum”, “(V)V”);

             jmethodIDprintNmMethodID = env->GetStaticMethodID(myCls, “printNm” , “(V)V”);

    }

    在上述Java代码中,我们自己定义了一个类MyClass,里面定义了对应的静态和非静态成员,然后将MyClass对象传递给本地代码。在本地代码中通过GetObjectClass方法取得MyClass对象对应的类,然后依次取得MyClass类中的属性ID和方法ID。

    其中GetObjectClass定义如下:

    jclass GetObjectClass(jobject obj) ;

    jobject obj:如果本地代码拿到一个对象,则通过该方法取得该对象的类类型,其功能如同Object.getClass()方法。

    4.5.2 JNI类型签名

    Java语言是面向对象的语言,它支持重载机制,即:允许多个具有相同的方法名不同的方法签名的方法存在。因此,只通过方法名不能明确的让JNI找到Java里对应的方法,还需要指定方法的签名,即:参数列表和返回值类型。

    JNI中类型签名如下表所示:

    类型签名

    Java类型

    类型签名

    Java类型

    Z

    boolean

    [

    []

    B

    byte

    [I

    int[]

    C

    char

    [F

    float[]

    S

    short

    [B

    byte[]

    I

    int

    [C

    char[]

    J

    long

    [S

    short[]

    F

    float

    [D

    double[]

    D

    double

    [J

    long[]

    L

    [Z

    boolean[]

    V

    void

     

     

    ·        基本类型

    以特定的单个大写字母表示

    ·        Java类类型

    Java类类型以L开头,以“/”分隔包名,在类名后加上“;”分隔符,例如String的签名为:Ljava/lang/String;

    在Java中数组是引用类型,数组以“[”开头,后面跟数组元素类型签名,例如:int[]的签名是[I ,对于二维数组,如int[][]签名就是[[I,object数组签名就是[Ljava/lang/Object;

    对于方法签名,在JNI中也有特定的表示方式。

    (参数1类型签名参数2类型签名参数3类型签名.......)返回值类型签名

    注意:

    ·        方法名在方法签名中没有体现出来

    ·        括号内表示参数列表,参数列表紧密相挨,中间没有逗号,没有空格

    ·        返回值出现在括号后面

    ·        如果函数没有返回值,也要加上V类型

    例如:

    Java方法

    JNI方法签名

    boolean isLedOn(void) ;

    (V)Z

    void setLedOn(int ledNo);

    (I)V

    String substr(String str, int idx, int count);

    (Ljava/lang/String;II)Ljava/lang/String

    char fun (int n, String s, int[] value);

    (ILjava/lang/String;[I)C

    boolean showMsg(android.View v, String msg);

    (Landroid/View;Ljava/lang/String;)Z

     

    4.5.3JNI操作Java属性和方法

    1. 获得、设置属性和静态属性

    取得了代表属性和静态属性的jfieldID,就可以使用JNIEnv中提供的方法来获取和设置属性/静态属性。

    取得Java属性的JNI方法定义如下:

    j<类型> Get<类型>Field(jobjectobj, jfieldID fieldID);


    j<类型> Get Static<类型>Field(jobjectobj, jfieldID fieldID);


    设置Java属性的JNI方法定义如下:

    void Set<类型>Field(jobject obj, jfieldID fieldID, j<类型> val);

    void Set Static<类型>Field(jobject obj, jfieldID fieldID, j<类型> val);

    <类型>表示Java中的基本类型。例如:

    // 获得obj对象中,整形属性ID为fieldID的值

    jint GetIntField(jobject obj, jfieldID fieldID);

     

    // 设置obj对象中,属性ID为fieldID,属性值为val

    void  SetObjectField(jobjectobj, jfieldID fielded,jobject val);  

     

    // 设置clazz类中,静态属性ID为fieldID的属性值为value

    void SetStaticCharField(jclass clazz, jfieldIDfieldID, jchar value)

    我们将上一节中的本地代码进行修改如下:

    void Java_com_test_exam2_NativeCallJava_callNative(JNIEnv* env, jclassthiz, jobject  obj)

    {

             jclass  myCls = env->GetObjectClass(obj);

             jfieldIDmNumFieldID = env->GetFieldID(myCls, “mNumber”, “I”);

             jfieldIDmNameFieldID = env->GetStaticFieldID(myCls, “mName”, “java/lang/String”);

            

             //获得、设置Java成员的属性值

             jintmNum = env->GetIntField(obj, mNumFieldID);

             env->SetIntField(obj,mNumFieldID, mNum+100);

     

             //获得、设置静态属性的值

             jstringmNm = (jstring)(env->GetStaticObjectField(myCls, mNameFieldID));

             printf(“%s\n”,mNm);

             jstringnewStr = env->NewStringUTF(“Hello from Native”);

             env->SetStaticObjectField(myCls,mNumFieldID, newStr);

             …

    }

    上述代码通过JNI提供的Get、Set方法取得和设置Java对象和类的属性,NewStringUTF表示创建一个Java的字符串对象,字符串值使用8位字符初始化。

    2. 通过JNI调用Java中的方法

             在4.5.1节我们通过JNI方法获得了jmethodID,在本地代码中我们就可以通过jmethodID来调用Java中的方法了。

    JNI提供了下面的方法用来调用Java方法:

    // 调用Java成员方法

    Call<Type>Method(jobject obj,jmethodIDmethodID,...);

    Call<Type>MethodV(jobject clazz, jmethodIDmethodID,va_listargs);

    Call<Type>tMethodA(jobject clazz,jmethodID methodID,constjvalue *args);

    // 调用Java静态方法

    CallStatic<Type>Method(jclass clazz,jmethodID methodID,...);

    CallStatic<Type>MethodV(jclass clazz,jmethodID methodID,va_listargs);

    CallStatic<Type>tMethodA(jclass clazz,jmethodID methodID,constjvalue *args);

    上面的Type这个方法的返回值类型,如void,int,char,byte等等。

    第一个参数代表调用的这个方法所属于的对象,或者这个静态方法所属的类。

    第二个参数代表jmethodID。

    后面的表示调用方法的参数列表,…表示是变长参数,以“V”结束的方法名表示以向量表形式提供参数列表,以“A”结束的方法名表示以jvalue数组提供参数列表,这两种调用方式使用较少。

    我们将前面的例子的本地代码继续进行修改:

    void Java_com_test_exam2_NativeCallJava_callNative(JNIEnv* env, jclassthiz, jobject  obj)

    {

             jclass  myCls = env->GetObjectClass(obj);

             jfieldIDmNumFieldID = env->GetFieldID(myCls, “mNumber”, “I”);

             jfieldIDmNameFieldID = env->GetStaticFieldID(myCls, “mName”, “java/lang/String”);

            

             //获得、设置Java成员的属性值

             jintmNum = env->GetIntField(obj, mNumFieldID);

             env->SetIntField(obj,mNumFieldID, mNum+100);

     

             //获得、设置静态属性的值

             jstringmNm = (jstring)(env->GetStaticObjectField(myCls, mNameFieldID));

             printf(“%s\n”,mNm);

             jstringnewStr = env->NewStringUTF(“Hello from Native”);

             env->SetStaticObjectField(myCls,mNumFieldID, newStr);

     

             //取得Java方法ID

             jmethodIDprintNumMethodID = env->GetMethodID(myCls, “printNum”, “(V)V”);

             jmethodIDprintNmMethodID = env->GetStaticMethodID(myCls, “printNm” , “(V)V”);

     

             //调用MyClass对象中的printNum方法

             CallVoidMethod(obj,printNumMethodID);

             //调用MyClass类的静态pinrtNm方法

             CallStaticVoidMethod(myCls,printNmMethodID);

    }

    在Java中构造方法是一种特殊的方法,主要用于对象创建时被回调,我们将在下一节分析。

    4.5.4 在本地代码中创建Java对象

    1. 在本地代码中创建Java对象

    在JNIEnv的函数表中提供了下面几个方法来创建一个Java对象:

    jobject NewObject(jclass clazz, jmethodIDmethodID,...);

    jobject NewObjectV(jclass clazz,jmethodIDmethodID,va_list args);

    jobjectNewObjectA(jclass clazz, jmethodIDmethodID,const jvalue *args) ;

    它们和上一节中介绍的调用Java方法使用起来很相似,他们的参数意义如下:

    clazz:要创建的对象的类。

    jmethodID:创建对象对应的构造方法ID。

    参数列表:…表示是变长参数,以“V”结束的方法名表示向量表表示参数列表,以“A”结束的方法名表示以jvalue数组提供参数列表。

    由于Java的构造方法的特点是方法名与类名一样,并且没有返回值,所以对于获得构造方法的ID的方法env->GetMethodID(clazz,method_name,sig)中的第二个参数是固定为类名(也可以用“<init>”代替类名),第三个参数和要调用的构造方法有关,默认的Java构造方法没有返回值,没有参数。

    我们将上一节的例子进行修改,在本地代码中创建一个新的MyClass对象:

    void Java_com_test_exam2_NativeCallJava_callNative(JNIEnv* env, jclassthiz, jobject  obj)

    {

             jclass  myCls = env->GetObjectClass(obj);

             //当然我们也可以像下面这样写:

             //jclass myCls = env->FindClass("com/test/exam2/MyClass");  

             //取得MyClass的构造方法ID

             jmethodIDmyClassMethodID = env->GetMethodID(myCls, “MyClass”, “(V)V”);

             //创建MyClass新对象

             jobjectnewObj = NewObject(myCls, myClassMethodID);

    }

    2.创建Java String对象

    在Java中,字符串String对象是Unicoode(UTF-16)编码,每个字符不论是中文还是英文还是符号,一个字符总是占用两个字节。在C/C++中一个字符是一个字节, C/C++中的宽字符是两个字节的。

    在本地C/C++代码中我们可以通过一个宽字符串,或是一个UTF-8编码的字符串创建一个Java端的String对象。这种情况通常用于返回Java环境一个String返回值等场合。

    根据传入的宽字符串创建一个Java String对象

    jstring NewString(const jchar *unicode, jsizelen)

    根据传入的UTF-8字符串创建一个Java String对象

    jstring NewStringUTF(const char *utf)

    在Java中String类有很多对字符串进行操作的方法,在本地代码中可以通过JNI接口可以将Java的字符串转换到C/C++的宽字符串(wchar_t*),或是传回一个UTF-8的字符串(char*)到C/C++,在本地进行操作。

    可以看下面的一个例子:

    在Java端有一个字符串 String str="abcde";,在本地方法中取得它并且输出:

    void native_string_operation (JNIEnv * env,  jobject obj)

    {

             //取得该字符串的jfieldID

    jfieldIDid_string=env->GetFieldID(env->GetObjectClass(obj),"str", "Ljava/lang/String;");

             jstringstring=(jstring)(env->GetObjectField(obj, id_string));    //取得该字符串,强转为jstring类型。

             printf("%s",string);

    }

    由上面的代码可知,从java端取得的String属性或者是方法返回值的String对象,对应在JNI中都是jstring类型,它并不是C/C++中的字符串。所以,我们需要对取得的 jstring类型的字符串进行一系列的转换,才能使用。

    JNIEnv提供了一系列的方法来操作字符串:

    将一个jstring对象,转换为(UTF-16)编码的宽字符串(jchar*):

    const jchar *GetStringChars(jstring str,jboolean*isCopy) 

    将一个jstring对象,转换为(UTF-8)编码的字符串(char*):

     const char *GetStringUTFChars(jstringstr,jboolean *isCopy)

    这两个函数的参数中,第一个参数传入一个指向Java 中String对象的jstring引用。第二个参数传入的是一个jboolean的指针,其值可以为NULL、JNI_TRUE、JNI_FLASE。

    如果为JNI_TRUE则表示在本地开辟内存,然后把Java中的String拷贝到这个内存中,然后返回指向这个内存地址的指针。如果为JNI_FALSE,则直接返回指向Java中String的内存指针。这时不要改变这个内存中的内容,这将破坏String在Java中始终是常量的规则。

    如果是NULL,则表示不关心是否拷贝字符串。

    使用这两个函数取得的字符,在不适用的时候(不管String的数据是否拷贝到本地内存),要分别对应的使用下面两个函数来释放内存。

    RealeaseStringChars(jstring jstr,const jchar*str)

    RealeaseStringUTFChars(jstringjstr, constchar* str)

    第一个参数指定一个jstring变量,即要释放的本地字符串的资源

    第二个参数就是要释放的本地字符串

    4.5.5 Java数组在本地代码中的处理

    我们可以使用GetFieldID获取一个Java数组变量的ID,然后用GetObjectFiled取得该数组变量到本地方法,返回值为jobject,然后我们可以强制转换为j<Type>Array类型。

    @jni.h

    typedef jarray jbooleanArray;

    typedef jarray jbyteArray;

    typedef jarray jcharArray;

    typedef jarray jshortArray;

    typedef jarray jintArray;

    typedef jarray jlongArray;

    typedef jarray jfloatArray;

    typedef jarray jdoubleArray;

    typedef jarray jobjectArray;

    j<Type>Array类型是JNI定义的一个对象类型,它并不是C/C++的数组,如int[]数组,double[]数组等等。所以我们要把j<Type>Array类型转换为C/C++中的数组来操作。

    JNIEnv定义了一系列的方法来把一个j<Type>Array类型转换为C/C++数组或把C/C++数组转换为j<Type>Array。

    1.      获取数组的长度

    jsize GetArrayLength(jarray array);

     

    2.      对象类型数组的操作

    jobjectArray NewObjectArray(jsize len, jclassclazz, jobject init)                        // 创建对象数组

    jobject GetObjectArrayElement(jobjectArrayarray, jsize index)                      // 获得元素

    void SetObjectArrayElement(jobjectArray array,jsize index, jobject val)          // 设置元素

    参数说明:

    len:新创建对象数组长度

    clazz:对象数组元素类型

    init:对象数组元素的初始值

    array:要操作的数组

    index:要操作数组元素的下标索引

    val:要设置的数组元素的值

    JNI没有提供直接把Java的对象类型数组(Object[ ])直接转到C++中的jobject[ ]数组的函数。而是直接通过Get/SetObjectArrayElement这样的函数来对Java的Object[ ]数组进行操作。

     

    3.       对基本数据类型数组的操作

    基本数据类型数组的操作方法比较多,大致可以分为如下几类:

    获得指定类型的数组:

    j<Type>*Get<Type>ArrayElements(j<Type>Array array, jboolean *isCopy);

    释放数组:

    voidRelease<Type>ArrayElements(j<Type>Array array, j<Type>*elems, jint mode);

    这类函数可以把Java基本类型的数组转换到C/C++中的数组。有两种处理方式,一是拷贝一份传回本地代码,另一种是把指向Java数组的指针直接传回到本地代码,处理完本地化的数组后,通过Realease<Type>ArrayElements来释放数组。处理方式由Get方法的第二个参数isCopied来决定(取值为JNI_TRUE或JNI_FLASE)。

    其第三个参数mode可以取下面的值:

    l  0:对Java的数组进行更新并释放C/C++的数组

    l  JNI_COMMIT:对Java的数组进行更新但是不释放C/C++的数组

    l  JNI_ABORT:对Java的数组不进行更新,释放C/C++的数组

    例如:

    package com.test.exam4_5

    class ArrayTest {

            static{

                       System.loadLibrary("native_array");

             }

     

            privateint [] arrays=new int[]{1,2,3,4,5};

     

            publicnative void show();

     

    publicstatic void main(String[] args) {

                       new ArrayTest ().show();

             }

    }

    本地代码:

    void Java_com_test_exam4_5_ArrayTest_show(JNIEnv * env,  jobject obj)

    {

            jfieldID id_arrsys = env->GetFieldID(env->GetObjectClass(obj),"arrays", "[I");

            jintArrayarr = (jintArray)(env->GetObjectField(obj, id_arrsys));

            jint*int_arr = env->GetIntArrayElements(arr, NULL);

            jsizelen = env->GetArrayLength(arr);

            for(int i = 0; I < len; i++)

             {

                       cout << int_arr[i]<< endl;

             }

            env->ReleaseIntArrayElements(arr, int_arr, JNI_ABORT);

    }

     

     

    4.6 局部引用与全局引用

    Java代码与本地代码里在进行参数传递与返回值复制的时候,要注意数据类型的匹配。对于int, char等基本类型直接进行拷贝即可,对于Java中的对象类型,通过传递引用实现。JVM保证所有的Java对象正确的传递给了本地代码,并且维持这些引用,因此这些对象不会被Java的gc(垃圾收集器)回收。因此,本地代码必须有一种方式来通知JVM本地代码不再使用这些Java对象,让gc来回收这些对象。

            JNI将传递给本地代码的对象分为两种:局部引用和全局引用。

    l  局部引用:只在上层Java调用本地代码的函数内有效,当本地方法返回时,局部引用自动回收。

    l  全局引用:只有显示通知VM时,全局引用才会被回收,否则一直有效,Java的gc不会释放该引用的对象。

    JNI中对于局部引用和全局引用相关的函数如下:

    创建指向某个对象的局部引用,创建失败返回NULL

    jobject NewLocalRef(jobject ref);

    删除局部引用

    void DeleteLocalRef(jobject obj);

    创建指向某个对象的全局引用,创建失败返回NULL

    jobject NewGlobalRef(jobject lobj);

    删除全局引用

    void DeleteGlobalRef(jobject gref);

    4.6.1局部引用

    默认的话,传递给本地代码的引用是局部引用。所有的JNI函数的返回值都是局部引用。

    jstring MyNewString(JNIEnv *env, jchar *chars,jint len)

    {

    staticjclassstringClass = NULL;              //static 不能保存一个局部引用

    jmethodIDcid;

    jcharArrayelemArr;

    jstringresult;

    if(stringClass== NULL) {

    stringClass =env->FindClass("java/lang/String");    // 局部引用

    if(stringClass == NULL) {

    return NULL; /* exception thrown */

                       }

                     }

              /* 本地代码中创建的字符串为局部引用,当函数返回后字符串有可能被gc回收 */

              cid =env->GetMethodID(stringClass,"<init>","([C)V");

             result=env->NewStringUTF(stringClass, cid, “Hello World”);

             returnresult;

    }

    虽然局部引用会在本地代码执行之后自动释放,但是有下列情况时,要手动释放:

    l  本地代码访问一个很大的Java对象时,在使用完该对象后,本地代码要去执行比较复杂耗时的运算时,由于本地代码还没有返回,Java收集器无法释放该本地引用的对象,这时,应该手动释放掉该引用对象。

    l   本地代码创建了大量局部引用,这可能会导致JNI局部引用表溢出,此时有必要及时地删除那些不再被使用的局部引用。比如:在本地代码里创建一个很大的对象数组。

    jni.h头文件中定义了JNI本地方法与Java方法映射关系结构体JNINativeMethod。

    l  创建的工具函数,它会被未知的代码调用,在工具函数里使用完的引用要及时释放。

    l  不返回的本地函数。例如,一个可能进入无限事件分发的循环中的方法。此时在循环中释放局部引用,是至关重要的,这样才能不会无限期地累积,进而导致内存泄露。局部引用只在创建它们的线程里有效,本地代码不能将局部引用在多线程间传递。一个线程想要调用另一个线程创建的局部引用是不被允许的。将一个局部引用保存到全局变量中,然后在其它线程中使用它,这是一种错误的编程。

    4.6.2 全局引用

    在一个本地方法被多次调用时,可以使用一个全局引用跨越它们。一个全局引用可以跨越多个线程,并且在被程序员手动释放之前,一直有效。和局部引用一样,全局引用保证了所引用的对象不会被垃圾回收。

    JNI允许程序员通过局部引用来创建全局引用, 全局引用只能由NewGlobalRef函数创建。下面是一个使用全局引用例子:

    jstringMyNewString(JNIEnv *env, jchar *chars,jint len)

    {

        staticjclassstringClass = NULL;

        ...省略部分代码

       if(stringClass == NULL) {

           jclasslocalRefCls =env->FindClass("java/lang/String");

           if(localRefCls == NULL) {

              return NULL;

            }

            /*创建全局引用并指向局部引用 */

          stringClass = env->NewGlobalRef(localRefCls);

            /*删除局部引用 */

          env->DeleteLocalRef(localRefCls);

            /*判断全局引用是否创建成功 */

           if(stringClass == NULL) {

              return NULL; /* out of memory exception thrown */

            }

        }

    }

    在native代码不再需要访问一个全局引用的时候,应该调用DeleteGlobalRef来释放它。如果调用这个函数失败,Java VM将不会回收对应的对象。

    4.6.3 在Java环境中保存JNI对象

    本地代码在某次被调用时生成的对象,在其他函数调用时是不可见的。虽然可以设置全局变量但那不是好的解决方式,Android中通常是在Java域中定义一个int型的变量,在本地代码生成对象的地方,与这个Java域的变量关联,在别的使用到的地方,再从这个变量中取值。

    以JNICameraContext为例来说明:

    JNICameraContext是android_hardware_camera.cpp中定义的类型,并会在本地代码中生成对象并与Java中定义的android.hardware.Camera类的mNativeContext整形成员关联。

    注:为了简化理解,代码已经做了简单修改

    static voidandroid_hardware_Camera_native_setup(JNIEnv*env, jobject thiz,jobjectweak_this, jintcameraId)

    {

        // 创建JNICameraContext对象

      JNICameraContext *context = new JNICameraContext(env, weak_this,clazz,camera);

    …省略部分代码

     

    // 查找到Camera类

       jclassclazz =env->FindClass("android/hardware/Camera ");

    // 保存Carmera类中mNativeContext成员ID

      jfieldID field = env->GetFieldID(clazz,"mNativeContext","I");

     

        // 保存context对象的地址到了Java中的mNativeContext属性里

      env->SetIntField(thiz,fields, (int)context);

    }

    当要使用在本地代码中创建的JNICameraContext对象时,通过JNIEnv::GetIntField()获取Java对象的属性,并转化为JNICameraContext类型:

    // 查找到Camera类

       jclassclazz =env->FindClass("android/hardware/Camera ");

    // 保存Carmera类中mNativeContext成员ID

      jfieldID field = env->GetFieldID(clazz,"mNativeContext","I");

       // 从Java环境中得到保存在mNativeContext中的对象引用地址

     JNICameraContext*context=(JNICameraContext*)(env->GetIntField(thiz,field));

        if(context!= NULL) {

            //…省略部分代码

        }

     

    4.7本地方法的注册

             前面我们介绍了Java方法和本地方法互相调用过程中用到的JNI接口函数。在Java代码调用本地方法时,JVM又是如何能正确的绑定到本地方法呢?

    当JVM在调用带有native关键字的方法时,JVM在Java运行时环境中查找“一张方法映射表”,根据这张表寻找对应的本地方法,如果本地代码中没有找到对应的函数,则会抛出java.lang.UnsatisfiedLinkError错误,所以,当我们在使用JNI编程时,必须保证本地方法出现在“方法映射表”中。

    4.7.1 JNI_OnLoad方法

             本地代码最终编译成动态库,在Java代码中通过System.loadLibrary方法来加载本地代码库,当本地代码动态库被JVM加载时,JVM会自动调用本地代码中的JNI_OnLoad函数。

    JNI_OnLoad函数的定义如下:

    jint JNI_OnLoad(JavaVM *vm, void *reserved);

    参数说明:

    vm:代表了JVM实例,其实是和JVM相关的一些操作函数指针,详情请查看4.2.1章节。

    reserved:保留

    一般来说JNI_OnLoad函数里主要做以下工作:

    l 调用GetEnv函数,获得JNIEnv,即Java执行环境

    l 通过RegisterNatives函数注册本地方法

    l 返回JNI版本号

    JNI从Java1.0到现在其版本也在发生变化 ,变化主要体现在JNIEnv中支持的函数个数,当调用GetEnv函数时可以指定获得某个版本的JNIEnv函数表。

    jint GetEnv(void **penv, jint version);

    参数说明:

    penv: JNIEnv指针的地址,GetEnv成功调用后,它指向JNIEnv指针。

    version:请求的JNI版本号,如:JNI_VERSION_1_4,表示请求JNI1.4版本的JNIEnv执行环境。

    返回值:当请求的JNI版本号不支持时,返回负值,成功返回JNI_OK。

             RegisterNatives是JNIEnv所提供的功能函数,用于注册本地方法和Java方法的映射关系到JVM中,保证Java代码调用本地代码时能正确调用到本地代码。

             JVM要求JNI_OnLoad函数必须返回一个合法的JNI版本号,表示该库将被JVM加载,因此本地代码的JNI_OnLoad的实现一般如下:

    /*

      * Thisiscalled by the VM when the shared library is first loaded.

      */

     jintJNI_OnLoad(JavaVM* vm, void* reserved) {

        JNIEnv* env= NULL;

         jintresult= -1;

        if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {        // 调用GetEnv请求获得指定版本的JNIEnv

    gotofail;

         }

     

    if(env!= NULL)

    gotofail;

        if(registerMethods(env) != 0) {                               //调用子函数注册本地方法

            gotofail;

         }

         /*success-- return valid version number */

        result =JNI_VERSION_1_4;                                          //指定返回值为合法的JNI版本号

     

     fail:

        return result;

     }

    4.7.2 RegisterNatives方法

             RegisterNatives通常在本地代码被加载时被调用,用来将JNI映射关系“告诉”Java执行环境。映射关系其实是在jni.h中定义一个结构体:

    @jni.h

    typedef struct {

    
    char *name;                        // Java方法名

    
    char *signature;                         //方法签名表示字符串

    
    void *fnPtr;
                     // Java方法对应的本地函数指针

    } JNINativeMethod;

    该结构体记录了Java运行环境中Java方法名name,Java方法签名signature以及其对应的本地函数名fnPtr。

             由于Java代码中可能定义多个本地方法,所以JNINativeMethod结构通常放到一个数组中,通过RegisterNatives注册到JVM中:

    @jni.h

    jint RegisterNatives(jclass clazz, constJNINativeMethod *methods,
jint nMethods);

    jint UnregisterNatives(jclass clazz);

    clazz:成员方法属于某个类,clazz指定注册的映射关系所在的类

    methods:JNINativeMethod指针,它通常是一个映射关系数组

    nMethods:映射关系数组元素个数,即:映射关系数量

    当这些映射关系不再需要时,或需要更新映射关系时,则调用UnregisterNatives函数,删除这些映射关系。

    我们现在完整的来看下之前例子:

    @ NativeTest.java

    package com.test.exam2;

    class NativeTest{

             static{

                       System.loadLibrary(“native_call”);

             }

             privatenative static void callNativePrint(String str);

             privatenative static intcallNativeAdd(int n1, int n2);

     

     

             publicstatic void main(String arg[]){

                       callNativePrint(“HelloWorld”);

                       System.out.println(“n1+ n2 = ” + callNativeAdd(10, 20));

     

    }

    }

    @com_test_exam2_NativeTest.cpp:

    #include <stdlib.h>

    #include <string.h>

    #include <unistd.h>

    #include <jni.h>

    #include <jni_md.h>

     

    void  native_print(JNIEnv * env, jclassthiz, jobject obj)

    {

             printf(“Stringfrom java:%s\n”, env->GetStringUTFChars((jstring)obj, JNI_FALSE));

    }

     

    jint native_add(JNIEnv * env, jclassthiz, jintn1, jint n2)

    {

             returnn1 + n2;

    }

     

    /*

    * 定义映射关系结构体数组

    */

    static const JNINativeMethod gMethods[] = {

        {"callNativePrint",  "(Ljava/lang/String;)V",(void*)native_print},

        {"callNativeAdd",  "(II)I",(void*)native_add},

    };

     

    /*

    * 将映射关系结构体数组注册到JVM中

    */

     static int registerMethods(JNIEnv*env) {

         static constchar* const className= " com/test/exam2/NativeTest";

         jclass clazz;

         /* look upthe class */

         clazz =env->FindClass(className);

         if (clazz ==NULL) {

             return-1;

         }

     

         /* registerall the methods */

         if(env->RegisterNatives(clazz,gMethods,

                sizeof(gMethods) /sizeof(gMethods[0])) != JNI_OK)

         {

             return -1;

         }

         /* fill outthe rest of the IDcache */

         return 0;

     }

     

    /*

      * Thisiscalled by the VM when the shared library is first loaded.

      */

     jintJNI_OnLoad(JavaVM* vm, void* reserved) {

        JNIEnv* env= NULL;

         jintresult= -1;

        if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {        // 调用GetEnv请求获得指定版本的JNIEnv

    gotofail;

         }

     

    if(env!= NULL)

    gotofail;

        if(registerMethods(env) != 0) {                               //调用子函数注册本地方法

            gotofail;

         }

         /*success-- return valid version number */

        result =JNI_VERSION_1_4;                                          //指定返回值为合法的JNI版本号

     

     fail:

        return result;

     }

     

    4.8 JNI调用实验

    【实验内容】

    在Linux操作系统中硬件通常有:open,read,write,close等相关操作接口,每个设备硬件还有一些自己的属性,我们用Java编写一个Screen“屏幕”设备类,设备的初始化,设备打开,关闭,读/写都交给本地代码去实现。当写屏幕设备时,将写入内容存放在本地代码缓冲区中,当读屏幕设备时,则将数据经过简单处理读取出来。例如:向Screen中写入a-z的小写字母,读出来变成A-Z的大写。

    在Ubuntu系统中编写Java程序和本地C++代码,编写Makefile文件用来编译Java代码和本地代码库,最终正常运行Java与C++本地代码。

    【实验目的】

    通过实验,学员掌握在Linux系统中编写基于JNI的java程序和与Java对应的C++本地代码,熟悉Linux中编译动态库的过程和Makefile的编写,最终掌握JNI编程相关知识。

    【实验平台】

    安装有JDK的Ubuntu操作系统(可以在Windows系统中虚拟Ubuntu系统)。

    【实验步骤】

    1.      设计Screen类,并实现其代码

    @Screen.java

    package com.test.practice4_8;

    class Screen{

    // Load libscreen.so lib

       static{

           System.loadLibrary("screen");

        }

     

       private String mDevName;

       private int mDevNo;

       private boolean isInit = false;

       private int mWidth;

       private int mHeight;

     

        publicScreen(){

           mDevName = null;

           mDevNo = 0;

        }

     

    // check is device inital

        publicboolean isInit(){

           return isInit;

        }

     

    // get the screen width

        publicint getWidth(){

           return mWidth;

        }

     

    // get the screen height

        publicint getHeight(){

           return mHeight;

    }

     

    // print screen informations

        publicvoid printInfo(){

           System.out.println("Screen Name: " + mDevName);

           System.out.println("Device No:   " + mDevNo);

           System.out.println("Screen width: " + mWidth);

           System.out.println("Screen height:" + mHeight);

    }

     

    // define all native methods

        publicnative boolean open();

        publicnative int read(byte[] data, int len);

        publicnative int write(byte[] data, int len);

        publicnative void close();

    }

     

    2.      设计并实现Screen测试类

    package com.test.practice4_8;

    class ScreenTest{

        publicstatic void main(String arg[]){

           Screen dev = new Screen();                  //创建Screen对象

     

           if(!dev.open()){                              //打开Screen设备

               System.out.println("Screen open error");

               return;

            }

     

           dev.printInfo();                              //打印设备信息

     

           byte[] data = new byte[26];                    //定义要写入的数据,不能用双字节的char类型

           for(int i = 0; i < 26; i++){

               data[i] = (byte)(97 + i);

            }

     

           System.out.println("Write a-z to Screen device:");

           dev.write(data, data.length);                  // 写入设备中

     

           byte[] buf = new byte[64];

           int size = dev.read(buf, buf.length);                 //从设备中读取出来

           if(size < 0){

               System.out.println("read data from screen device error");

               return ;

            }

     

           System.out.println("Read data from Screen device:");

           for(int i = 0; i < 26; i++){

               System.out.print((char)buf[i] + ",");            // 打印出读取出的数据

            }

           System.out.println();  

     

           dev.close();                                   //关闭设备

        }

    }

    3.      设计并实现本地代码

    @com_test_practice4_8_ScreenTest.cpp

    #include <unistd.h>

    #include <stdlib.h>

    #include <malloc.h>

    #include <jni.h>

    #include <jni_md.h>

     

    // 定义一个全局结构体,用来保存所有的Screen类的相关ID信息

    struct screen{

        jclassclazz;

       jfieldID id_dev_name;

       jfieldID id_dev_no;

       jfieldID id_is_init;

        jfieldIDid_width;

       jfieldID id_height;

    } *gfieldID;

     

    // 定义读写数据的缓冲区

    char _data[64];

     

    //初始化Screen类的相关ID信息,起到缓存的作用

    static int native_id_init(JNIEnv *env){

        gfieldID = (struct screen*)malloc(sizeof(struct screen));

        if(gfieldID == NULL)

            return -1;

     

        gfieldID->clazz =env->FindClass("com/test/practice4_8/Screen");

        gfieldID->id_dev_name = env->GetFieldID(gfieldID->clazz,"mDevName", "Ljava/lang/String;");

        gfieldID->id_dev_no = env->GetFieldID(gfieldID->clazz,"mDevNo", "I");

         gfieldID->id_is_init= env->GetFieldID(gfieldID->clazz, "isInit", "Z");

        gfieldID->id_width = env->GetFieldID(gfieldID->clazz,"mWidth", "I");

        gfieldID->id_height = env->GetFieldID(gfieldID->clazz,"mHeight", "I");

        return 0;

     }

     

    // Java代码中open方法的本地实现

    static jboolean native_open(JNIEnv * env,jobject thiz) {

        //init the jfieldID in Java env

       if(native_id_init(env) != 0){                               

           return JNI_FALSE;

        }

     

        // 创建设备名字符串

       jstring dev_nm = env->NewStringUTF("Farsight HD LCDScreen");

       if(dev_nm == NULL)

           return JNI_FALSE;

     

       // 写回Screen对象的mDevName属性里

       env->SetObjectField(thiz, gfieldID->id_dev_name, dev_nm);

     

        // 设备设备号

       env->SetIntField(thiz, gfieldID->id_dev_no, 0x1234);

        // 设置初始化标识

       env->SetBooleanField(thiz, gfieldID->id_is_init, JNI_TRUE);

        // 设置Screen宽度

       env->SetIntField(thiz, gfieldID->id_width, 1023);

        // 设置Screen高度

       env->SetIntField(thiz, gfieldID->id_height, 768);

        returnJNI_TRUE;

    }

     

    // Screen类 read方法的本地实现

    static jint native_read(JNIEnv * env, jobjectthiz, jbyteArray arr, jint len)

    {

        if(len<= 0){

           return len;

        }

       // 获得Java层定义的byte数组

        jbyte*byte_arr = env->GetByteArrayElements(arr, NULL);

        int i= 0;

        for(;i < len; i++){

            byte_arr[i]= _data[i] - 32;             // 将处理过的数据写回Java byte数组里

        }

       env->ReleaseByteArrayElements(arr, byte_arr, 0);                // update array data andrelease array

        returni;

    }

     

    // Screen类write方法的本地实现

    static jint native_write(JNIEnv * env, jobjectthiz, jbyteArray arr, jint len)

    {

        if(len> sizeof(_data) && len <= 0){

           return -1;

    }

    // 获得Java层定义的byte数组

        jbyte*byte_arr = env->GetByteArrayElements(arr, NULL);

        int i= 0;

        for(;i < len; i++){

           _data[i] = byte_arr[i];           //将Java byte数组保存在本地缓存区中

           printf("%c,", _data[i]);

        }

       printf("\n");

       env->ReleaseByteArrayElements(arr, byte_arr, JNI_ABORT);        // do not update array data release array

        returni;

    }

     

    // Screen类close方法的本地实现

    static void native_close(JNIEnv * env, jobjectthiz)

    {

        // 修改isInit的值为false

       env->SetBooleanField(thiz, gfieldID->id_is_init, JNI_FALSE);

       free(gfieldID); //释放空间

       gfieldID = NULL;

    }

     

    /*

     * 定义映射关系结构体数组

     */

    static const JNINativeMethod gMethods[] = {

       {"open",   "()Z", (void*)native_open},

       {"read",           "([BI)I", (void*)native_read},

       {"write",          "([BI)I", (void*)native_write},

       {"close",  "()V", (void*)native_close},

    };

     

    /*

     * 将映射关系结构体数组注册到JVM中

     */

    static int registerMethods(JNIEnv* env) {

        staticconst char* const className = "com/test/practice4_8/Screen";

        jclassclazz;

        /*look up the class */

        clazz= env->FindClass(className);

        if(clazz == NULL) {

           printf("FindClass error\n");

           return-1;

        }

     

        /*registerall the methods */

       if(env->RegisterNatives(clazz, gMethods, sizeof(gMethods) /sizeof(gMethods[0])) != JNI_OK)

        {

           return -1;

        }

        /*fill outthe rest of the ID cache */

        return0;                                                                                  

    }

     

    /*

     *  This iscalled by the VM when the sharedlibrary is first loaded.

     */

    jint JNI_OnLoad(JavaVM* vm, void* reserved) {

       JNIEnv* env= NULL;

        jintresult= -1;

       if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {  // 调用GetEnv请求获得指定版本的JNIEnv

           printf("GetEnv error\n");

           goto fail;

        }

     

        if(env== NULL)

           goto fail;

       if(registerMethods(env) != 0) {                 // 调用子函数注册本地方法

           printf("registerMethods error\n");

           goto fail;

        }

        /*success-- return valid version number */

        result= JNI_VERSION_1_4;                   // 指定返回值为合法的JNI版本号

     

    fail:

        returnresult;

    }

    我们在本地代码中声明了一个全局结构体指针gfieldID,该结构体里面存放的是Screen类成员ID,因为这些ID要在后面的方法中频繁的使用,如果不缓存起来,意味着每次使用都要Findclass,GetFieldID,这对性能有很大影响。

    4.      为了方便编译,编写Makefile

    libscreen.so:com_test_practice4_8_ScreenTest.cpp ScreenTest.class

        g++-I/home/linux/jdk1.5.0_21/include/ -I/home/linux/jdk1.5.0_21/include/linux/$< -fPIC -shared -o $@

     

    ScreenTest.class: ScreenTest.java

        javac-d ./ $<

     

    clean:

        $(RM)ScreenTest.class libscreen.so

    由于本地代码要编译成so动态库,所以g++的参数要指定-fPIC –shared等选项,另外,在编译本地代码时要用到jni.h和jni_md.h头文件,所以还要加上-I选项,用来指定这两个头文件的位置,它们在我们安装的JDK的目录下。

    细心的同学可能已经注意到,本地C++文件名为Java的包名+类名.cpp,包名不是以“.”作为间隔符,而是以目录间隔符“/”分隔,这也是因为Java中的包名本身就是使用目录名以区分命名空间。这样做还有另外一个好处,即:我们看到本地代码文件时基本上就可以通过文件找到其对应的Java代码,反之亦然。

    5.      执行make命令,并且运行查看实验结果

    $ make

    $ java -Djava.library.path='.'com/test/practice4_8/ScreenTest

    Java命令的“-Djava.library.path”选项表示指定在运行Java代码时,加载本地库时的寻找路径。为了避免每次都输入上述运行命令,我们可以写到一个脚本中

    @run.sh

    #!/bin/bash

    java -Djava.library.path='.'com/test/practice4_8/ScreenTest

    运行结果如下:

    linux@ubuntu:~/jni/practice$ ./run.sh

    Screen Name: Farsight HD LCD Screen

    Device No:   4660

    Screen width: 1023

    Screen height:768

    Write a-z to Screen device:

    a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,

    Read data from Screen device:

    A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,

    6.      常见问题

    l Exception in thread"main" java.lang.NoClassDefFoundError: xxx

    一般是由于FindClass方法查找不到Java类造成的,检查FindClass的参数是否正确。

    l Exception in thread"main" java.lang.NoSuchMethodError: xxx

    Java与本地方法的链接映射时出现错误,先确认下Java中有没有对应xxx方法声明,如果有,确认RegisterNatives注册映射关系的签名是否匹配。

    l Exception in thread"main" java.lang.NoSuchFieldError:xxx

    这表示在本地代码中访问xxx属性时,在java代码中没有该属性,先确认该属性是否定义,如果有定义,看下属性是静态属性还是非静态属性,如果是静态属性,本地方法只能通过Get/SetStatic<Type>Field来访问,如果是非静态属性,本地方法只能通过Get/Set<Type>Field来访问。

    l Exception in thread"main" java.lang.UnsatisfiedLinkError: no xxx in java.library.path

    这表示本地代码库找不到,确认java在执行时,“-Djava.library.path”参数是否正确。

    展开全文
  • JNI机制源码解析

    千次阅读 2016-10-22 07:45:38
    1,JNI 是什么? 2,JNI 有和作用? 3,JNI 为什么存在? JNI(Java Native Interface): java 本地接口。Natvie 一般指 C/C++。 可以这么说,JNI是 java 和 C/C++ 之间的桥梁,通过JNI技术, java 和 C/C++ 可以互相调用。  ...

    1, 基本概念

    1,JNI 是什么?

    2,JNI 有和作用?

    3,JNI 为什么存在?

    JNI(Java Native Interface): java 本地接口。Natvie 一般指 C/C++。

    可以这么说,JNI是 java 和 C/C++ 之间的桥梁,通过JNI技术, java 和 C/C++ 可以互相调用。

       首先,Java是平台无关的,但是承载java的虚拟机是用C/C++写的,但是虚拟机是运行在具体的平台上,是平台相关的。然而,使用JNI技术就可以屏蔽不同操作系统平台之间的差异。其次,C/C++在java之前就广泛使用了,JNI技术避免了java重复写功能写代码的窘况。最后, C/C++相对于java在一些场合更加有效率,所以JNI技术也是不可或缺的。

    虚拟机特点:

    1, 每一个Android应用程序进程都有一个Dalvik虚拟机实例。

    2, 虚拟机的进程和线程都是与目标机器本地操作系统的进程和线程一一对应的,由本地操作系统来管理。

       Java代码根据所述进程运行在虚拟机里,C/C++在本地库中。JNI方法是直接在本地操作系统上执行的,而不是由Dalvik虚拟机解释器执行。由此也可看出,JNI方法是Android应用程序与本地操作系统直接进行通信的一个手段。那么如何将java代码和C/C++ 代码相关联起来呢?

    2, 方法注册

    方法注册目的:将Java的方法和C/C++方法通过JNI技术一一对应起来,这样才可以互相调用。有2种注册方法,静态注册和动态注册。在这里仅讨论android系统中常用的注册方法,动态注册。

    2.1 注册核心类方法


    zygote进程由init进程启动, zygote在内部会先启动Dalvik虚拟机(JNI环境),然后开始方法的注册,其它和本文无关的就不论述了。

    1.   /*static*/ intAndroidRuntime::startReg(JNIEnv* env)

    2.   {

    3.       androidSetCreateThreadFunc((android_create_thread_fn)javaCreateThreadEtc);

    4.      

    5.       env->PushLocalFrame(200);

    6.    

    7.       if(register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {

    8.           env->PopLocalFrame(NULL);

    9.           return -1;

    10.      }

    11.      env->PopLocalFrame(NULL);

    12.   

    13.      //createJavaThread("fubar",quickTest, (void*) "hello");

    14.   

    15.      return 0;

    16.  }

     

    17.  static const RegJNIRec gRegJNI[] = {

    18.      REG_JNI(register_android_util_SeempLog),

    19.      REG_JNI(register_com_android_internal_os_RuntimeInit),

    20.      REG_JNI(register_android_os_SystemClock),

    21.      REG_JNI(register_android_os_MessageQueue),

    22.      ···

    gRegJNI是一个全局变量的JNI方法注册函数表,通过register_jni_procs方法逐个调用,逐个注册。

    23.  static int register_jni_procs(const RegJNIRecarray[], size_t count, JNIEnv* env)

    24.  {

    25.      for(size_t i = 0; i < count; i++) {

    26.          if (array[i].mProc(env) < 0) {

    27.  #ifndef NDEBUG

    28.              ALOGD("----------!!! %s failedto load\n", array[i].mName);

    29.  #endif

    30.              return -1;

    31.          }

    32.      }

    33.      return 0;

    34.  }

    我们抽取其中一个MessageQueue注册函数register_android_os_MessageQueue来具体分析,其他的注册函数原理都是一样的。

    最后调用JNIHelp.cpp 的jniRegisterNativeMethods 方法完成注册。

    35.  int register_android_os_MessageQueue(JNIEnv*env) {

    36.      intres = RegisterMethodsOrDie(env,"android/os/MessageQueue",gMessageQueueMethods,

    37.                                     NELEM(gMessageQueueMethods));

    38.      jclass clazz = FindClassOrDie(env,"android/os/MessageQueue");

    39.      gMessageQueueClassInfo.mPtr =GetFieldIDOrDie(env, clazz, "mPtr", "J");

    40.      gMessageQueueClassInfo.dispatchEvents =GetMethodIDOrDie(env, clazz,

    41.              "dispatchEvents","(II)I");

    42.   

    43.      return res;

    44.  }

     

    主要看gMessageQueueMethods 变量

    45.  static JNINativeMethod gMessageQueueMethods[]= {

    46.      /*name, signature, funcPtr */

    47.      {"nativeInit", "()J",(void*)android_os_MessageQueue_nativeInit },

    48.      {"nativeDestroy", "(J)V",(void*)android_os_MessageQueue_nativeDestroy },

    49.      {"nativePollOnce", "(JI)V",(void*)android_os_MessageQueue_nativePollOnce },

    50.      {"nativeWake", "(J)V",(void*)android_os_MessageQueue_nativeWake },

    51.      {"nativeIsPolling", "(J)Z",(void*)android_os_MessageQueue_nativeIsPolling },

    52.      {"nativeSetFileDescriptorEvents", "(JII)V",

    53.              (void*)android_os_MessageQueue_nativeSetFileDescriptorEvents},

    54.  };

    由gMessageQueueMethods可知,RegisterMethodsOrDie方法将 MessageQueue.java 中的方法nativeInit 等 和android_os_MessageQueue_nativeInit等一一关联起来了,这样java层就可以调用C/C++层对应的方法了。

    55.  static inline int RegisterMethodsOrDie(JNIEnv*env, const char* className,

    56.                                         constJNINativeMethod* gMethods, int numMethods) {

    57.      intres = AndroidRuntime::registerNativeMethods(env,className, gMethods, numMethods);

    58.      LOG_ALWAYS_FATAL_IF(res < 0,"Unable to register native methods.");

    59.      return res;

    60.  }

     

    61.  /*static*/ intAndroidRuntime::registerNativeMethods(JNIEnv* env,

    62.      const char* className, constJNINativeMethod* gMethods, int numMethods)

    63.  {

    64.      return jniRegisterNativeMethods(env,className, gMethods, numMethods);

    65.  }

     

    66.  extern "C" intjniRegisterNativeMethods(C_JNIEnv* env, const char* className,

    67.      const JNINativeMethod* gMethods, intnumMethods)

    68.  {

    69.      JNIEnv* e =reinterpret_cast<JNIEnv*>(env);

    70.      ALOGV("Registering %s's %d nativemethods...", className, numMethods);

    71.   

    72.      scoped_local_ref<jclass> c(env,findClass(env, className));

    73.      if(c.get() == NULL) {

    74.          char* msg;

    75.          asprintf(&msg, "Nativeregistration unable to find class '%s'; aborting...", className);

    76.          e->FatalError(msg);

    77.      }

    78.   

    79.      if((*env)->RegisterNatives(e,c.get(), gMethods, numMethods) < 0) {

    80.          char* msg;

    81.          asprintf(&msg,"RegisterNatives failed for '%s'; aborting...", className);

    82.          e->FatalError(msg);

    83.      }

    84.   

    85.      return 0;

    86.  }

    注册核心类都是C/C++ 代码, 虚拟机刚启动时还未运行java代码。

    2.2 一般方法的注册

    Framework代码太多太杂,为了便于分析,选择packages\apps 路径下的Bluetooth来分析,其实这部分代码也应该放在 framework 里面的。主要分为2步:

    1,加载动态链接库。

    2,注册

    2.2.1 加载库


    首先在AdapterApp中,  AdapterApp是继承于 Application,所以在该进程创建之后进行初始化。

    87.  static {

    88.          if (DBG) Log.d(TAG,"Loading JNILibrary");

    89.          System.loadLibrary("bluetooth_jni");

    90.      }

    packages\apps\Bluetooth\jni\  路径下的文件会打包成 libbluetooth_jni.so文件。

    2.2.2 注册java>>C/C++


    当加载libbluetooth_jni.so文件后,对应的

    com_android_bluetooth_btservice_AdapterService.cpp 的方法JNI_OnLoad 就会调用,详细代码如下:

    91.  jint JNI_OnLoad(JavaVM*jvm, void *reserved)

    92.  {

    93.      JNIEnv *e;

    94.      intstatus;

    95.      ALOGV("Bluetooth Adapter Service :loading JNI\n");

    96.      //Check JNI version

    97.      if(jvm->GetEnv((void **)&e, JNI_VERSION_1_6)) {

    98.          ALOGE("JNI version mismatcherror");

    99.          return JNI_ERR;

    100.    }

    101. 

    102.    if((status = android::register_com_android_bluetooth_btservice_AdapterService(e))< 0) {

    103.       ALOGE("jni adapter service registration failure, status: %d",status);

    104.       return JNI_ERR;

    105.    }

    106. 

    107.    if((status = android::register_com_android_bluetooth_hfp(e))< 0) {

    108.       ALOGE("jni hfp registration failure, status: %d", status);

    109.       return JNI_ERR;

    110.    }

    111.    ···

    112.   return JNI_VERSION_1_6;

    113.}

    这些注册方法最后都会调用JNIHelp.cpp的jniRegisterNativeMethods方法完成注册。和注册核心库时完全一样。

    比如register_com_android_bluetooth_btservice_AdapterService

    114.intregister_com_android_bluetooth_btservice_AdapterService(JNIEnv* env)

    115.{

    116.   return jniRegisterNativeMethods(env,"com/android/bluetooth/btservice/AdapterService",

    117.                                    sMethods, NELEM(sMethods));

    118.}

    sMethods 变量定义如下:

    119.static JNINativeMethod sMethods[] = {

    120.    /*name, signature, funcPtr */

    121.   {"classInitNative","()V", (void *) classInitNative},

    122.   {"initNative", "()Z", (void *) initNative},

    123.   {"cleanupNative", "()V", (void*) cleanupNative},

    124.   {"ssrcleanupNative", "(Z)V", (void*)ssrcleanupNative},

    125.   {"enableNative", "()Z",  (void*) enableNative},

    126.    ···

    127.   };

    在本例中,通过调用jniRegisterNativeMethods 方法, AdapterService.java中的一些方法和com_android_bluetooth_btservice_AdapterService.cpp的方法一一对应,这样,java层就可以调用C/C++的代码了。那么,C/C++ 层代码如何调用java的方法呢?

     

    2.2.3 C/C++ >> java

    同样在AdapterService.java类中,

    128.static {

    129.       System.load("/system/lib/libbluetooth_jni.so");

    130.       classInitNative();

    131.    }

    根据上小节,最后会调用com_android_bluetooth_btservice_AdapterService.cpp的classInitNative方法,如下:

    1.   static void classInitNative(JNIEnv* env,jclass clazz) {

    2.       interr;

    3.       hw_module_t* module;

    4.    

    5.       jclass jniCallbackClass=

    6.           env->FindClass("com/android/bluetooth/btservice/JniCallbacks");

    7.       sJniCallbacksField =env->GetFieldID(clazz, "mJniCallbacks",

    8.           "Lcom/android/bluetooth/btservice/JniCallbacks;");

    9.    

    10.      method_stateChangeCallback =env->GetMethodID(jniCallbackClass, "stateChangeCallback","(I)V");

    11.   

    12.      ···

    13.      method_devicePropertyChangedCallback =env->GetMethodID(jniCallbackClass,

    14.                       "devicePropertyChangedCallback","([B[I[[B)V");

    15.      method_deviceFoundCallback =env->GetMethodID(jniCallbackClass,    

    16.                       "deviceFoundCallback", "([B)V");

    17.      ···

    18.  }

    C/C++ 调用java 方法的关键点:

    jniCallbackClass 指向JniCallbacks.java 类

    1,调用java的哪个类?  

    com/android/bluetooth/btservice/JniCallbacks 

      根据这行那行代码就将com_android_bluetooth_btservice_AdapterService 和JniCallbacks.java对应了起来.

    2,调用哪个对应的方法?

    method_deviceFoundCallback =env->GetMethodID(jniCallbackClass,"deviceFoundCallback","([B)V");

    根据这行那行代码将C/C++ 的method_deviceFoundCallback 和

    deviceFoundCallback对应。

    通过classInitNative方法, com_android_bluetooth_btservice_AdapterService.cpp中的方法就可以调用JniCallbacks.java中对应的方法。

    其实,也可以不用这样调用,仅仅通过jniCallbackClass就可以调用JniCallbacks.java里的方法,    env->GetMethodID(jniCallbackClass,"deviceFoundCallback", "([B)V");

    统一将函数对应起来只是为了代码的简洁。

    3, 数据类型的转换

    Java(平台无关)和C/C++(平台相关)的数据类型不一样,就像中文和英文一样,在互相交流时得有一个翻译,即转换规则。

    1,基本类型的数据转换对应表

    Java

    C/C++

    字长

    boolean

    jboolean

    8位

    byte

    jbyte

    8位

    char

    jchar

    16位

    short

    jshort

    16位

    int

    jint

    32位

    long

    jlong

    64位

    float

    jfloat

    32位

    double

    jdouble

    64位

     

    2,应用类型数据转换表

    Java

    C/C++

    Java

    C/C++

    All object

    jobject

    char[ ]

    jcharArray

    java.lang.Class

    jclass

    short[ ]

    jshortArray

    java.lang.String

    jstring

    int[ ]

    jintArray

    Object[ ]

    jobjectArray

    long[ ]

    jlongArray

    boolean[ ]

    jbooleanArray

    float[ ]

    jfloatArray

    byte[ ]

    jbyteArray

    double[ ]

    jdoubleArray

    java.lang.Throwable

    jthrowable

     

     

     

    4, 小结

    Android上层的java代码只是一层皮,大量的功能都离不开C/C++ 的支持,在上层开发中也会碰到一些使用JNI机制调用C/C++ 代码的例子,了解一下JNI机制不仅看见这些代码时心不慌,而且可以更深的理解相关android机制,知识点。

     

     

     

     

    展开全文
  • JNI 实战全面解析

    万次阅读 多人点赞 2014-11-01 09:05:43
    项目决定移植一款C++开源项目到Android平台,开始对JNI深入研究。 JNI是什么? JNI(Java Native Interface)意为JAVA本地调用,它允许Java代码和其他语言写的代码进行交互,简单的说,一种在Java虚拟机控制下执行代码...
    简介

    项目决定移植一款C++开源项目到Android平台,开始对JNI深入研究。

    JNI是什么?

    JNI(Java Native Interface)意为JAVA本地调用,它允许Java代码和其他语言写的代码进行交互,简单的说,一种在Java虚拟机控制下执行代码的标准机制。

    NDK是什么?

    Android NDK(Native Development Kit )是一套工具集合,允许你用像C/C++语言那样实现应用程序的一部分。

    为什么要用NDK?

    1、安全性,java是半解释型语言,很容易被反汇编后拿到源代码文件,我们可以在重要的交互功能使用C语言代替。
    2、效率,C语言比起java来说效率要高出很多。

    JNI和NDK的区别?

    从工具上说,NDK其实多了一个把.so和.apk打包的工具,而JNI开发并没有打包,只是把.so文件放到文件系统的特定位置。
    从编译库说NDK开发C/C++只能能使用NDK自带的有限的头文件,而使用JNI则可以使用文件系统中带的头文件。
    从编写方式说,它们一样。

    详解
    1、JNI 元素

    1、JNI组织结构


    JNI函数表的组成就像C++的虚函数表,虚拟机可以运行多张函数表。
    JNI接口指针仅在当前线程中起作用,指针不能从一个线程进入另一个线程,但可以在不同的线程中调用本地方法。


    2、原始数据

    Jobject  对象 引用类型


    Java类型 本地类型(JNI) 描述
    boolean(布尔型)jboolean 无符号8个比特
    byte(字节型)jbyte 有符号8个比特
    char(字符型)jchar 无符号16个比特
    short(短整型)jshort 有符号16个比特
    int(整型)jint 有符号32个比特
    long(长整型)jlong 有符号64个比特
    float(浮点型)jfloat 32个比特
    double(双精度浮点型)jdouble 64个比特
    void(空型)void N/A

    函数操作

    函数 Java 数组类型 本地类型 说明
    GetBooleanArrayElementsjbooleanArrayjbooleanReleaseBooleanArrayElements 释放
    GetByteArrayElementsjbyteArrayjbyteReleaseByteArrayElements 释放
    GetCharArrayElementsjcharArrayjcharReleaseShortArrayElements 释放
    GetShortArrayElementsjshortArrayjshortReleaseBooleanArrayElements 释放
    GetIntArrayElementsjintArrayjintReleaseIntArrayElements 释放
    GetLongArrayElementsjlongArrayjlongReleaseLongArrayElements 释放
    GetFloatArrayElementsjfloatArrayjfloatReleaseFloatArrayElements 释放
    GetDoubleArrayElementsjdoubleArrayjdoubleReleaseDoubleArrayElements 释放
    GetObjectArrayElement自定义对象object 
    SetObjectArrayElement自定义对象object 
    GetArrayLength  获取数组大小
    New<Type>Array  创建一个指定长度的原始数据类型的数组
    GetPrimitiveArrayCritical  得到指向原始数据类型内容的指针,该方法可能使垃圾回收不能执行,该方法可能返回数组的拷贝,因此必须释放此资源。
    ReleasePrimitiveArrayCritical  释放指向原始数据类型内容的指针,该方法可能使垃圾回收不能执行,该方法可能返回数组的拷贝,因此必须释放此资源。
    NewStringUTF  jstring类型的方法转换
    GetStringUTFChars  jstring类型的方法转换
    DefineClass   从原始类数据的缓冲区中加载类
    FindClass   该函数用于加载本地定义的类。它将搜索由CLASSPATH 环境变量为具有指定名称的类所指定的目录和 zip文件
    GetObjectClass   通过对象获取这个类。该函数比较简单,唯一注意的是对象不能为NULL,否则获取的class肯定返回也为NULL
    GetSuperclass   获取父类或者说超类 。 如果 clazz 代表类class而非类 object,则该函数返回由 clazz 所指定的类的超类。 如果 clazz指定类 object 或代表某个接口,则该函数返回NULL
    IsAssignableFrom   确定 clazz1 的对象是否可安全地强制转换为clazz2
    Throw  抛出 java.lang.Throwable 对象
    ThrowNew   利用指定类的消息(由 message 指定)构造异常对象并抛出该异常
    ExceptionOccurred   确定是否某个异常正被抛出。在平台相关代码调用 ExceptionClear() 或 Java 代码处理该异常前,异常将始终保持抛出状态
    ExceptionDescribe   将异常及堆栈的回溯输出到系统错误报告信道(例如 stderr)。该例程可便利调试操作
    ExceptionClear   清除当前抛出的任何异常。如果当前无异常,则此例程不产生任何效果
    FatalError   抛出致命错误并且不希望虚拟机进行修复。该函数无返回值
    NewGlobalRef   创建 obj 参数所引用对象的新全局引用。obj 参数既可以是全局引用,也可以是局部引用。全局引用通过调用DeleteGlobalRef() 来显式撤消。
    DeleteGlobalRef   删除 globalRef 所指向的全局引用
    DeleteLocalRef   删除 localRef所指向的局部引用
    AllocObject   分配新 Java 对象而不调用该对象的任何构造函数。返回该对象的引用。clazz 参数务必不要引用数组类。
    getObjectClass  返回对象的类
    IsSameObject  测试两个引用是否引用同一 Java 对象
    NewString   利用 Unicode 字符数组构造新的 java.lang.String 对象
    GetStringLength   返回 Java 字符串的长度(Unicode 字符数)
    GetStringChars   返回指向字符串的 Unicode 字符数组的指针。该指针在调用 ReleaseStringchars() 前一直有效
    ReleaseStringChars   通知虚拟机平台相关代码无需再访问 chars。参数chars 是一个指针,可通过 GetStringChars() 从 string 获得
    NewStringUTF   利用 UTF-8 字符数组构造新 java.lang.String 对象
    GetStringUTFLength   以字节为单位返回字符串的 UTF-8 长度
    GetStringUTFChars   返回指向字符串的 UTF-8 字符数组的指针。该数组在被ReleaseStringUTFChars() 释放前将一直有效
    ReleaseStringUTFChars   通知虚拟机平台相关代码无需再访问 utf。utf 参数是一个指针,可利用 GetStringUTFChars() 获得
    NewObjectArray   构造新的数组,它将保存类 elementClass 中的对象。所有元素初始值均设为 initialElement
    Set<PrimitiveType>ArrayRegion  将基本类型数组的某一区域从缓冲区中复制回来的一组函数
    GetFieldID   返回类的实例(非静态)域的属性 ID。该域由其名称及签名指定。访问器函数的
    Get<type>Field 及 Set<type>Field系列使用域 ID 检索对象域。GetFieldID() 不能用于获取数组的长度域。应使用GetArrayLength()。
    Get<type>Field  该访问器例程系列返回对象的实例(非静态)域的值。要访问的域由通过调用GetFieldID() 而得到的域 ID 指定。 
    Set<type>Field  该访问器例程系列设置对象的实例(非静态)属性的值。要访问的属性由通过调用
    SetFieldID() 而得到的属性 ID指定。
    GetStaticFieldID 

    GetStatic<type>Field

    SetStatic<type>Field
      同上,只不过是静态属性。
    GetMethodID  返回类或接口实例(非静态)方法的方法 ID。方法可在某个 clazz 的超类中定义,也可从 clazz 继承。该方法由其名称和签名决定。 GetMethodID() 可使未初始化的类初始化。要获得构造函数的方法 ID,应将<init> 作为方法名,同时将void (V) 作为返回类型。
    CallVoidMethod   
    CallObjectMethod   
    CallBooleanMethod    
    CallByteMethod   
    CallCharMethod   
    CallShortMethod   
    CallIntMethod   
    CallLongMethod   
    CallFloatMethod   
    CallDoubleMethod   
    GetStaticMethodID   调用静态方法
    Call<type>Method   
    RegisterNatives   向 clazz 参数指定的类注册本地方法。methods 参数将指定 JNINativeMethod 结构的数组,其中包含本地方法的名称、签名和函数指针。nMethods 参数将指定数组中的本地方法数。
    UnregisterNatives   取消注册类的本地方法。类将返回到链接或注册了本地方法函数前的状态。该函数不应在常规平台相关代码中使用。相反,它可以为某些程序提供一种重新加载和重新链接本地库的途径。    
        

    域描述符

    Java 语言
    Zboolean
    Bbyte
    Cchar
    Sshort
    Iint
    Jlong
    Ffloat
    Ddouble
      

    引用类型则为 L + 该类型类描述符 + 。

    数组,其为 :  [ + 其类型的域描述符 + 。

    多维数组则是 n个[ +该类型的域描述符 , N代表的是几维数组。

    String类型的域描述符为 Ljava/lang/String;  
    
    [ + 其类型的域描述符 + ;
    int[ ]     其描述符为[I
    float[ ]   其描述符为[F
    String[ ]  其描述符为[Ljava/lang/String;
    Object[ ]类型的域描述符为[Ljava/lang/Object;
    int  [ ][ ] 其描述符为[[I
    float[ ][ ] 其描述符为[[F

     将参数类型的域描述符按照申明顺序放入一对括号中后跟返回值类型的域描述符,规则如下: (参数的域描述符的叠加)返回类型描述符。对于,没有返回值的,用V(表示void型)表示。
    举例如下:

    Java层方法                                               JNI函数签名
                    String test ( )                                              Ljava/lang/String;
                    int f (int i, Object object)                            (ILjava/lang/Object;)I
                    void set (byte[ ] bytes)                                ([B)V

    JNIEnv与JavaVM 

    JNIEnv 概念 : 是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境 ; 

    JNIEnv 与 JavaVM : 注意区分这两个概念; 
    -- JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局只有一个;
    -- JNIEnv : JavaVM 在线程中的代表, 每个线程都有一个, JNI 中可能有很多个 JNIEnv;

    JNIEnv 作用 : 
    -- 调用 Java 函数 : JNIEnv 代表 Java 运行环境, 可以使用 JNIEnv 调用 Java 中的代码;
    -- 操作 Java 对象 : Java 对象传入 JNI 层就是 Jobject 对象, 需要使用 JNIEnv 来操作这个 Java 对象;


    JNIEnv 体系结构 

    线程相关 : JNIEnv 是线程相关的, 即 在 每个线程中 都有一个 JNIEnv 指针, 每个JNIEnv 都是线程专有的, 其它线程不能使用本线程中的 JNIEnv, 线程 A 不能调用 线程 B 的 JNIEnv;

    JNIEnv 不能跨线程 : 
    -- 当前线程有效 : JNIEnv 只在当前线程有效, JNIEnv 不能在 线程之间进行传递, 在同一个线程中, 多次调用 JNI层方法, 传入的 JNIEnv 是相同的;
    -- 本地方法匹配多JNIEnv : 在 Java 层定义的本地方法, 可以在不同的线程调用, 因此 可以接受不同的 JNIEnv;

    JNIEnv 结构 : 由上面的代码可以得出, JNIEnv 是一个指针,  指向一个线程相关的结构, 线程相关结构指向 JNI 函数指针 数组, 这个数组中存放了大量的 JNI 函数指针, 这些指针指向了具体的 JNI 函数; 


    注意:JNIEnv只在当前线程中有效。本地方法不能将JNIEnv从一个线程传递到另一个线程中。相同的 Java 线程中对本地方法多次调用时,传递给该本地方法的JNIEnv是相同的。但是,一个本地方法可被不同的 Java 线程所调用,因此可以接受不同的 JNIEnv。



    UTF-8编码

    JNI使用改进的UTF-8字符串来表示不同的字符类型。Java使用UTF-16编码。UTF-8编码主要使用于C语言,因为它的编码用\u000表示为0xc0,而不是通常的0×00。非空ASCII字符改进后的字符串编码中可以用一个字节表示。

    错误

    JNI不会检查NullPointerException、IllegalArgumentException这样的错误,原因是:导致性能下降。

    在绝大多数C的库函数中,很难避免错误发生。
    JNI允许用户使用Java异常处理。大部分JNI方法会返回错误代码但本身并不会报出异常。因此,很有必要在代码本身进行处理,将异常抛给Java。在JNI内部,首先会检查调用函数返回的错误代码,之后会调用ExpectOccurred()返回一个错误对象。
    jthrowable ExceptionOccurred(JNIEnv *env);
    例如:一些操作数组的JNI函数不会报错,因此可以调用ArrayIndexOutofBoundsException或ArrayStoreExpection方法报告异常。
    3、JNI函数实战

    1、*.so的入口函数

    JNI_OnLoad()与JNI_OnUnload()
    当Android的VM(Virtual Machine)执行到System.loadLibrary()函数时,首先会去执行C组件里的JNI_OnLoad()函数。它的用途有二:
    (1)告诉VM此C组件使用那一个JNI版本。如果你的*.so档没有提供JNI_OnLoad()函数,VM会默认该*.so档是使用最老的JNI 1.1版本。由于新版的JNI做了许多扩充,如果需要使用JNI的新版功能,例如JNI 1.4的java.nio.ByteBuffer,就必须藉由JNI_OnLoad()函数来告知VM。
    (2)由于VM执行到System.loadLibrary()函数时,就会立即先呼叫JNI_OnLoad(),所以C组件的开发者可以藉由JNI_OnLoad()来进行C组件内的初期值之设定(Initialization) 。


    2、返回值

    jstring str = env->newStringUTF("HelloJNI");  //直接使用该JNI构造一个jstring对象返回  
        return str ;  
    jobjectArray ret = 0;
    jsize len = 5;
    jstring str;
    string value("hello");
     
    ret = (jobjectArray)(env->NewObjectArray(len, env->FindClass("java/lang/String"), 0));
    for(int i = 0; i < len; i++)
    {
        str = env->NewStringUTF(value..c_str());
        env->SetObjectArrayElement(ret, i, str);
    }
    return ret; 返回数组
     jclass    m_cls   = env->FindClass("com/ldq/ScanResult");  
      
        jmethodID m_mid   = env->GetMethodID(m_cls,"<init>","()V");  
          
        jfieldID  m_fid_1 = env->GetFieldID(m_cls,"ssid","Ljava/lang/String;");  
        jfieldID  m_fid_2 = env->GetFieldID(m_cls,"mac","Ljava/lang/String;");  
        jfieldID  m_fid_3 = env->GetFieldID(m_cls,"level","I");  
      
        jobject   m_obj   = env->NewObject(m_cls,m_mid);  
      
                            env->SetObjectField(m_obj,m_fid_1,env->NewStringUTF("AP1"));  
                            env->SetObjectField(m_obj,m_fid_2,env->NewStringUTF("00-11-22-33-44-55"));  
                            env->SetIntField(m_obj,m_fid_3,-50);  
        return m_obj;  返回自定义对象

    jclass list_cls = env->FindClass("Ljava/util/ArrayList;");//获得ArrayList类引用  
      
        if(listcls == NULL)  
        {  
            cout << "listcls is null \n" ;  
        }  
        jmethodID list_costruct = env->GetMethodID(list_cls , "<init>","()V"); //获得得构造函数Id  
      
        jobject list_obj = env->NewObject(list_cls , list_costruct); //创建一个Arraylist集合对象  
        //或得Arraylist类中的 add()方法ID,其方法原型为: boolean add(Object object) ;  
        jmethodID list_add  = env->GetMethodID(list_cls,"add","(Ljava/lang/Object;)Z");   
        
        jclass stu_cls = env->FindClass("Lcom/feixun/jni/Student;");//获得Student类引用  
        //获得该类型的构造函数  函数名为 <init> 返回类型必须为 void 即 V  
        jmethodID stu_costruct = env->GetMethodID(stu_cls , "<init>", "(ILjava/lang/String;)V");  
      
        for(int i = 0 ; i < 3 ; i++)  
        {  
            jstring str = env->NewStringUTF("Native");  
            //通过调用该对象的构造函数来new 一个 Student实例  
            jobject stu_obj = env->NewObject(stucls , stu_costruct , 10,str);  //构造一个对象  
              
            env->CallBooleanMethod(list_obj , list_add , stu_obj); //执行Arraylist类实例的add方法,添加一个stu对象  
        }  
      
        return list_obj ;   返回对象集合

    3、操作Java层的类

    //获得jfieldID 以及 该字段的初始值  
       jfieldID  nameFieldId ;  
      
       jclass cls = env->GetObjectClass(obj);  //获得Java层该对象实例的类引用,即HelloJNI类引用  
      
       nameFieldId = env->GetFieldID(cls , "name" , "Ljava/lang/String;"); //获得属性句柄  
      
       if(nameFieldId == NULL)  
       {  
           cout << " 没有得到name 的句柄Id \n;" ;  
       }  
       jstring javaNameStr = (jstring)env->GetObjectField(obj ,nameFieldId);  // 获得该属性的值  
       const char * c_javaName = env->GetStringUTFChars(javaNameStr , NULL);  //转换为 char *类型  
       string str_name = c_javaName ;    
       cout << "the name from java is " << str_name << endl ; //输出显示  
       env->ReleaseStringUTFChars(javaNameStr , c_javaName);  //释放局部引用  
      
       //构造一个jString对象  
       char * c_ptr_name = "I come from Native" ;  
         
       jstring cName = env->NewStringUTF(c_ptr_name); //构造一个jstring对象  
      
       env->SetObjectField(obj , nameFieldId , cName); // 设置该字段的值 
    4、回调Java层方法

      jstring str = NULL;  
          
        jclass clz = env->FindClass("cc/androidos/jni/JniTest");  
        //获取clz的构造函数并生成一个对象  
        jmethodID ctor = env->GetMethodID(clz, "<init>", "()V");  
        jobject obj = env->NewObject(clz, ctor);  
      
        // 如果是数组类型,则在类型前加[,如整形数组int[] intArray,则对应类型为[I,整形数组String[] strArray对应为[Ljava/lang/String;  
        jmethodID mid = env->GetMethodID(clz, "sayHelloFromJava", "(Ljava/lang/String;II[I)I");  
        if (mid)  
        {  
            LOGI("mid is get");  
            jstring str1 = env->NewStringUTF("I am Native");  
            jint index1 = 10;  
            jint index2 = 12;  
            //env->CallVoidMethod(obj, mid, str1, index1, index2);  
      
            // 数组类型转换 testIntArray能不能不申请内存空间  
            jintArray testIntArray = env->NewIntArray(10);  
            jint *test = new jint[10];  
            for(int i = 0; i < 10; ++i)  
            {  
                *(test+i) = i + 100;  
            }  
            env->SetIntArrayRegion(testIntArray, 0, 10, test);  
      
      
            jint javaIndex = env->CallIntMethod(obj, mid, str1, index1, index2, testIntArray);  
            LOGI("javaIndex = %d", javaIndex);  
            delete[] test;  
            test = NULL;  
        }  

    static void event_callback(int eventId,const char* description) {  //主进程回调可以,线程中回调失败。
    	if (gEventHandle == NULL)
    		return;
    	
    	JNIEnv *env;
    	bool isAttached = false;
    
    	if (myVm->GetEnv((void**) &env, JNI_VERSION_1_2) < 0) { //获取当前的JNIEnv
    		if (myVm->AttachCurrentThread(&env, NULL) < 0)
    			return;
    		isAttached = true;
    	}
    
    	jclass cls = env->GetObjectClass(gEventHandle); //获取类对象
    	if (!cls) {
    		LOGE("EventHandler: failed to get class reference");
    		return;
    	}
    
    	jmethodID methodID = env->GetStaticMethodID(cls, "callbackStatic",
    		"(ILjava/lang/String;)V");  //静态方法或成员方法
    	if (methodID) {
    		jstring content = env->NewStringUTF(description);
    		env->CallVoidMethod(gEventHandle, methodID,eventId,
    			content);
    		env->ReleaseStringUTFChars(content,description);
    	} else {
    		LOGE("EventHandler: failed to get the callback method");
    	}
    
    	if (isAttached)
    		myVm->DetachCurrentThread();
    }

    线程中回调
    把c/c++中所有线程的创建,由pthread_create函数替换为由Java层的创建线程的函数AndroidRuntime::createJavaThread。
    static pthread_t create_thread_callback(const char* name, void (*start)(void *), void* arg)  
    {  
        return (pthread_t)AndroidRuntime::createJavaThread(name, start, arg);  
    } 
    
    
    static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {  //异常检测和排除
        if (env->ExceptionCheck()) {  
            LOGE("An exception was thrown by callback '%s'.", methodName);  
            LOGE_EX(env);  
            env->ExceptionClear();  
        }  
    }  
      
    static void receive_callback(unsigned char *buf, int len)  //回调
    {  
        int i;  
        JNIEnv* env = AndroidRuntime::getJNIEnv();  
        jcharArray array = env->NewCharArray(len);  
        jchar *pArray ;  
          
        if(array == NULL){  
            LOGE("receive_callback: NewCharArray error.");  
            return;   
        }  
      
        pArray = (jchar*)calloc(len, sizeof(jchar));  
        if(pArray == NULL){  
            LOGE("receive_callback: calloc error.");  
            return;   
        }  
      
        //copy buffer to jchar array  
        for(i = 0; i < len; i++)  
        {  
            *(pArray + i) = *(buf + i);  
        }  
        //copy buffer to jcharArray  
        env->SetCharArrayRegion(array,0,len,pArray);  
        //invoke java callback method  
        env->CallVoidMethod(mCallbacksObj, method_receive,array,len);  
        //release resource  
        env->DeleteLocalRef(array);  
        free(pArray);  
        pArray = NULL;  
          
        checkAndClearExceptionFromCallback(env, __FUNCTION__);  
    }
    
    
    public void Receive(char buffer[],int length){  //java层函数
            String msg = new String(buffer);  
            msg = "received from jni callback" + msg;  
            Log.d("Test", msg);  
        }

      
        jclass cls = env->GetObjectClass(obj);//获得Java类实例  
        jmethodID callbackID = env->GetMethodID(cls , "callback" , "(Ljava/lang/String;)V") ;//或得该回调方法句柄  
      
        if(callbackID == NULL)  
        {  
             cout << "getMethodId is failed \n" << endl ;  
        }  
        
        jstring native_desc = env->NewStringUTF(" I am Native");  
      
        env->CallVoidMethod(obj , callbackID , native_desc); //回调该方法,并且


    5、传对象到JNI调用

      jclass stu_cls = env->GetObjectClass(obj_stu); //或得Student类引用  
      
        if(stu_cls == NULL)  
        {  
            cout << "GetObjectClass failed \n" ;  
        }  
        //下面这些函数操作,我们都见过的。O(∩_∩)O~  
        jfieldID ageFieldID = env->GetFieldID(stucls,"age","I"); //获得得Student类的属性id   
        jfieldID nameFieldID = env->GetFieldID(stucls,"name","Ljava/lang/String;"); // 获得属性ID  
      
        jint age = env->GetIntField(objstu , ageFieldID);  //获得属性值  
        jstring name = (jstring)env->GetObjectField(objstu , nameFieldID);//获得属性值  
      
        const char * c_name = env->GetStringUTFChars(name ,NULL);//转换成 char *  
       
        string str_name = c_name ;   
        env->ReleaseStringUTFChars(name,c_name); //释放引用  
          
        cout << " at Native age is :" << age << " # name is " << str_name << endl ;   
    6、与C++互转

    jbytearray转c++byte数组

    jbyte * arrayBody = env->GetByteArrayElements(data,0);   
    jsize theArrayLengthJ = env->GetArrayLength(data);   
    BYTE * starter = (BYTE *)arrayBody;   


    jbyteArray 转 c++中的BYTE[] 
    jbyte * olddata = (jbyte*)env->GetByteArrayElements(strIn, 0);  
    jsize  oldsize = env->GetArrayLength(strIn);  
    BYTE* bytearr = (BYTE*)olddata;  
    int len = (int)oldsize;  


    C++中的BYTE[]转jbyteArray 
    jbyte *by = (jbyte*)pData;  
    jbyteArray jarray = env->NewByteArray(nOutSize);  
    env->SetByteArrayRegin(jarray, 0, nOutSize, by);  


    jbyteArray 转 char * 
    char* data = (char*)env->GetByteArrayElements(strIn, 0);  


    char* 转jstring
    jstring WindowsTojstring(JNIEnv* env, char* str_tmp)  
    {  
     jstring rtn=0;  
     int slen = (int)strlen(str_tmp);  
     unsigned short* buffer=0;  
     if(slen == 0)  
     {  
      rtn = env->NewStringUTF(str_tmp);  
     }  
     else  
     {  
      int length = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str_tmp, slen, NULL, 0);  
      buffer = (unsigned short*)malloc(length*2+1);  
      if(MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str_tmp, slen, (LPWSTR)buffer, length) > 0)  
      {  
       rtn = env->NewString((jchar*)buffer, length);  
      }  
     }  
     if(buffer)  
     {  
      free(buffer);  
     }  
     return rtn;  
    }  


    char* jstring互转
    JNIEXPORT jstring JNICALL Java_com_explorer_jni_SambaTreeNative_getDetailsBy  
      (JNIEnv *env, jobject jobj, jstring pc_server, jstring server_user, jstring server_passwd)  
    {  
        const char *pc = env->GetStringUTFChars(pc_server, NULL);  
        const char *user = env->GetStringUTFChars(server_user, NULL);  
        const char *passwd = env->GetStringUTFChars(server_passwd, NULL);  
        const char *details = smbtree::getPara(pc, user, passwd);  
        jstring jDetails = env->NewStringUTF(details);  
        return jDetails;  
    }  
    4、Android.mk、Application.mk
    1、Android.mk

    Android.mk文件是GNU Makefile的一小部分,它用来对Android程序进行编译,Android.mk中的变量都是全局的,解析过程会被定义。

    一个Android.mk文件可以编译多个模块,模块包括:APK程序、JAVA库、C\C++应用程序、C\C++静态库、C\C++共享库。

    简单实例如下:

    LOCAL_PATH := $(call my-dir)  #表示是当前文件的路径
    include $(CLEAR_VARS)		#指定让GNU MAKEFILE该脚本为你清除许多 LOCAL_XXX 变量
    LOCAL_MODULE:= helloworld	#标识你在 Android.mk 文件中描述的每个模块
    MY_SOURCES := foo.c			#自定义变量
    ifneq ($(MY_CONFIG_BAR),)
     MY_SOURCES += bar.c
    endif
    LOCAL_SRC_FILES += $(MY_SOURCES)	#包含将要编译打包进模块中的 C 或 C++源代码文件
    include $(BUILD_SHARED_LIBRARY)	#根据LOCAL_XXX系列变量中的值,来编译生成共享库(动态链接库)


    GNU Make系统变量

    变量 描述
    CLEAR_VARS指向一个编译脚本,几乎所有未定义的 LOCAL_XXX 变量都在"Module-description"节中列出。必须在开始一个新模块之前包含这个脚本:include$(CLEAR_VARS),用于重置除LOCAL_PATH变量外的,所有LOCAL_XXX系列变量。
    BUILD_SHARED_LIBRARY指向编译脚本,根据所有的在 LOCAL_XXX 变量把列出的源代码文件编译成一个共享库。
    BUILD_STATIC_LIBRARY一个 BUILD_SHARED_LIBRARY 变量用于编译一个静态库。静态库不会复制到的APK包中,但是能够用于编译共享库。
    TARGET_ARCH目标 CPU平台的名字,  和 android 开放源码中指定的那样。如果是arm,表示要生成 ARM 兼容的指令,与 CPU架构的修订版无关。
    TARGET_PLATFORMAndroid.mk 解析的时候,目标 Android 平台的名字.详情可参考/development/ndk/docs/stable- apis.txt.
    TARGET_ARCH_ABI支持目标平台
    TARGET_ABI目标平台和 ABI 的组合,它事实上被定义成$(TARGET_PLATFORM)-$(TARGET_ARCH_ABI)  ,在想要在真实的设备中针对一个特别的目标系统进行测试时,会有用。在默认的情况下,它会是'android-3-arm'。
      

    模块描述变量

    变量 描述
    LOCAL_PATH这个变量用于给出当前文件的路径。必须在 Android.mk 的开头定义,可以这样使用:LOCAL_PATH := $(call my-dir)  这个变量不会被$(CLEAR_VARS)清除,因此每
    个 Android.mk 只需要定义一次(即使在一个文件中定义了几个模块的情况下)。
    LOCAL_MODULE这是模块的名字,它必须是唯一的,而且不能包含空格。必须在包含任一的$(BUILD_XXXX)脚本之前定义它。模块的名字决定了生成文件的名字。例如,如果一个一个共享库模块的名字是,那么生成文件的名字就是 lib.so。但是,在的 NDK 生成文件中(或者 Android.mk 或者 Application.mk),应该只涉及(引用)有正常名字的其他模块。
    LOCAL_SRC_FILES这是要编译的源代码文件列表。只要列出要传递给编译器的文件,因为编译系统自动计算依赖。注意源代码文件名称都是相对于 LOCAL_PATH的,你可以使用路径部分。
    LOCAL_CPP_EXTENSION这是一个可选变量, 用来指定C++代码文件的扩展名,默认是'.cpp',但是可以改变它。
    LOCAL_C_INCLUDES可选变量,表示头文件的搜索路径。
    LOCAL_CFLAGS可选的编译器选项,在编译 C 代码文件的时候使用。
    LOCAL_CXXFLAGS与 LOCAL_CFLAGS同理,针对 C++源文件。
    LOCAL_CPPFLAGS与 LOCAL_CFLAGS同理,但是对 C 和 C++ source files都适用。
    LOCAL_STATIC_LIBRARIES表示该模块需要使用哪些静态库,以便在编译时进行链接。
    LOCAL_SHARED_LIBRARIES表示模块在运行时要依赖的共享库(动态库),在链接时就需要,以便在生成文件时嵌入其相应的信息。注意:它不会附加列出的模块到编译图,也就是仍然需要在Application.mk 中把它们添加到程序要求的模块中。
    LOCAL_LDLIBS编译模块时要使用的附加的链接器选项。这对于使用‘-l’前缀传递指定库的名字是有用的。
    LOCAL_ALLOW_UNDEFINED_SYMBOLS默认情况下, 在试图编译一个共享库时,任何未定义的引用将导致一个“未定义的符号”错误。
    LOCAL_ARM_MODE默认情况下, arm目标二进制会以 thumb 的形式生成(16 位),你可以通过设置这个变量为 arm如果你希望你的 module 是以 32 位指令的形式。
    LOCAL_MODULE_PATH 和 LOCAL_UNSTRIPPED_PATH在 Android.mk 文件中, 还可以用LOCAL_MODULE_PATH 和LOCAL_UNSTRIPPED_PATH指定最后的目标安装路径.
    不同的文件系统路径用以下的宏进行选择:
      TARGET_ROOT_OUT:表示根文件系统。
       TARGET_OUT:表示 system文件系统。
       TARGET_OUT_DATA:表示 data文件系统。
    用法如:LOCAL_MODULE_PATH :=$(TARGET_ROOT_OUT) 
    至于LOCAL_MODULE_PATH 和LOCAL_UNSTRIPPED_PATH的区别,暂时还不清楚。

    GNU Make 功能宏

    变量 描述
    my-dir返回当前 Android.mk 所在的目录的路径,相对于 NDK 编译系统的顶层。
    all-subdir-makefiles返回一个位于当前'my-dir'路径的子目录中的所有Android.mk的列表。
    this-makefile返回当前Makefile 的路径(即这个函数调用的地方)
    parent-makefile返回调用树中父 Makefile 路径。即包含当前Makefile的Makefile 路径。
    grand-parent-makefile返回调用树中父Makefile的父Makefile的路径
      

    范例:


    2、

    编译一个简单的APK

      LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
      # Build all java files in the java subdirectory
      LOCAL_SRC_FILES := $(call all-subdir-java-files)
      # Name of the APK to build
      LOCAL_PACKAGE_NAME := LocalPackage
      # Tell it to build an APK
      include $(BUILD_PACKAGE)

    编译一个依赖静态.jar文件的APK 

    LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
      # List of static libraries to include in the package
      LOCAL_STATIC_JAVA_LIBRARIES := static-library
      # Build all java files in the java subdirectory
      LOCAL_SRC_FILES := $(call all-subdir-java-files)
      # Name of the APK to build
      LOCAL_PACKAGE_NAME := LocalPackage
      # Tell it to build an APK
      include $(BUILD_PACKAGE)
     注:LOCAL_STATIC_JAVA_LIBRARIES 后面应是你的APK程序所需要的JAVA库的JAR文件名。


    编译一个需要platform key签名的APK

    LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
      # Build all java files in the java subdirectory
      LOCAL_SRC_FILES := $(call all-subdir-java-files)
      # Name of the APK to build
      LOCAL_PACKAGE_NAME := LocalPackage
      LOCAL_CERTIFICATE := platform
      # Tell it to build an APK
      include $(BUILD_PACKAGE)
     注:LOCAL_CERTIFICATE 后面应该是签名文件的文件名

    编译一个需要特殊vendor key签名的APK 

     LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
      # Build all java files in the java subdirectory
      LOCAL_SRC_FILES := $(call all-subdir-java-files)
      # Name of the APK to build
      LOCAL_PACKAGE_NAME := LocalPackage
      LOCAL_CERTIFICATE := vendor/example/certs/app
      # Tell it to build an APK
      include $(BUILD_PACKAGE)

    装载一个普通的第三方APK

     LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
      # Module name should match apk name to be installed.
      LOCAL_MODULE := LocalModuleName
      LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
      LOCAL_MODULE_CLASS := APPS
      LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
      LOCAL_CERTIFICATE := platform
      include $(BUILD_PREBUILT) 

    装载需要.so(动态库)的第三方apk

    LOCAL_PATH := $(my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE := baiduinput_android_v1.1_1000e
    LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
    LOCAL_MODULE_CLASS := APPS
    LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
    LOCAL_CERTIFICATE := platform
    include $(BUILD_PREBUILT)
     
    #################################################################
    ####### copy the library to /system/lib #########################
    #################################################################
    include $(CLEAR_VARS)
    LOCAL_MODULE := libinputcore.so
    LOCAL_MODULE_CLASS := SHARED_LIBRARIES
    LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)
    LOCAL_SRC_FILES := lib/$(LOCAL_MODULE)
    OVERRIDE_BUILD_MODULE_PATH := $(TARGET_OUT_INTERMEDIATE_LIBRARIES)
    include $(BUILD_PREBUILT)

    编译一个静态java库 

      LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
      # Build all java files in the java subdirectory
      LOCAL_SRC_FILES := $(call all-subdir-java-files)
      # Any libraries that this library depends on
      LOCAL_JAVA_LIBRARIES := android.test.runner
      # The name of the jar file to create
      LOCAL_MODULE := sample
      # Build a static jar file.
      include $(BUILD_STATIC_JAVA_LIBRARY)
    注:LOCAL_JAVA_LIBRARIES表示生成的java库的jar文件名。

    编译C/C++应用程序模板

    LOCAL_PATH := $(call my-dir)
    #include $(CLEAR_VARS)
    LOCAL_SRC_FILES := main.c
    LOCAL_MODULE := test_exe
    #LOCAL_C_INCLUDES :=
    #LOCAL_STATIC_LIBRARIES :=
    #LOCAL_SHARED_LIBRARIES :=
    include $(BUILD_EXECUTABLE)
    注:‘:=’是赋值的意思,'+='是追加的意思,‘$’表示引用某变量的值
    LOCAL_SRC_FILES中加入源文件路径,LOCAL_C_INCLUDES中加入需要的头文件搜索路径
    LOCAL_STATIC_LIBRARIES 加入所需要链接的静态库(*.a)的名称,
    LOCAL_SHARED_LIBRARIES 中加入所需要链接的动态库(*.so)的名称,
    LOCAL_MODULE表示模块最终的名称,BUILD_EXECUTABLE 表示以一个可执行程序的方式进行编译。
    (4)编译C\C++静态库
    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_SRC_FILES := \
     helloworld.c
    LOCAL_MODULE:= libtest_static
     #LOCAL_C_INCLUDES :=
    #LOCAL_STATIC_LIBRARIES :=
    #LOCAL_SHARED_LIBRARIES :=
    include $(BUILD_STATIC_LIBRARY)
    和上面相似,BUILD_STATIC_LIBRARY 表示编译一个静态库。

    编译C\C++动态库的模板

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_SRC_FILES := helloworld.c
    LOCAL_MODULE := libtest_shared
    TARGET_PRELINK_MODULES := false
    #LOCAL_C_INCLUDES :=
    #LOCAL_STATIC_LIBRARIES :=
    #LOCAL_SHARED_LIBRARIES :=
    include $(BUILD_SHARED_LIBRARY)
    和上面相似,BUILD_SHARED_LIBRARY 表示编译一个共享库。
    以上三者的生成结果分别在如下目录中,generic 依具体 target 会变:
    out/target/product/generic/obj/APPS
    out/target/product/generic/obj/JAVA_LIBRARIES
    out/target/product/generic/obj/EXECUTABLE
    out/target/product/generic/obj/STATIC_LIBRARY
    out/target/product/generic/obj/SHARED_LIBRARY
    每个模块的目标文件夹分别为:
    1)APK程序:XXX_intermediates
    2)JAVA库程序:XXX_intermediates
    这里的XXX
     3)C\C++可执行程序:XXX_intermediates
     4)C\C++静态库: XXX_static_intermediates
     5)C\C++动态库: XXX_shared_intermediates


    实例:

    LOCAL_PATH := $(call my-dir)  #项目地址
    include $(CLEAR_VARS)		#清除变量
    
    LOCAL_MODULE    := libvlcjni	#库
    
    #源文件
    LOCAL_SRC_FILES := libvlcjni.c libvlcjni-util.c libvlcjni-track.c libvlcjni-medialist.c aout.c vout.c libvlcjni-equalizer.c native_crash_handler.c
    LOCAL_SRC_FILES += thumbnailer.c pthread-condattr.c pthread-rwlocks.c pthread-once.c eventfd.c sem.c
    LOCAL_SRC_FILES += pipe2.c
    LOCAL_SRC_FILES += wchar/wcpcpy.c
    LOCAL_SRC_FILES += wchar/wcpncpy.c
    LOCAL_SRC_FILES += wchar/wcscasecmp.c
    LOCAL_SRC_FILES += wchar/wcscat.c
    LOCAL_SRC_FILES += wchar/wcschr.c
    LOCAL_SRC_FILES += wchar/wcscmp.c
    LOCAL_SRC_FILES += wchar/wcscoll.c
    LOCAL_SRC_FILES += wchar/wcscpy.c
    LOCAL_SRC_FILES += wchar/wcscspn.c
    LOCAL_SRC_FILES += wchar/wcsdup.c
    LOCAL_SRC_FILES += wchar/wcslcat.c
    LOCAL_SRC_FILES += wchar/wcslcpy.c
    LOCAL_SRC_FILES += wchar/wcslen.c
    LOCAL_SRC_FILES += wchar/wcsncasecmp.c
    LOCAL_SRC_FILES += wchar/wcsncat.c
    LOCAL_SRC_FILES += wchar/wcsncmp.c
    LOCAL_SRC_FILES += wchar/wcsncpy.c
    LOCAL_SRC_FILES += wchar/wcsnlen.c
    LOCAL_SRC_FILES += wchar/wcspbrk.c
    LOCAL_SRC_FILES += wchar/wcsrchr.c
    LOCAL_SRC_FILES += wchar/wcsspn.c
    LOCAL_SRC_FILES += wchar/wcsstr.c
    LOCAL_SRC_FILES += wchar/wcstok.c
    LOCAL_SRC_FILES += wchar/wcswidth.c
    LOCAL_SRC_FILES += wchar/wcsxfrm.c
    LOCAL_SRC_FILES += wchar/wmemchr.c
    LOCAL_SRC_FILES += wchar/wmemcmp.c
    LOCAL_SRC_FILES += wchar/wmemcpy.c
    LOCAL_SRC_FILES += wchar/wmemmove.c
    LOCAL_SRC_FILES += wchar/wmemset.c
    
    
    LOCAL_C_INCLUDES := $(VLC_SRC_DIR)/include	#包含头
    
    ARCH=$(ANDROID_ABI) #变量 平台
    
    CPP_STATIC=$(ANDROID_NDK)/sources/cxx-stl/gnu-libstdc++$(CXXSTL)/libs/$(ARCH)/libgnustl_static.a #应用静态库
    
    LOCAL_CFLAGS := -std=gnu99  #编译器标识
    ifeq ($(ARCH), armeabi)
    	LOCAL_CFLAGS += -DHAVE_ARMEABI
    	# Needed by ARMv6 Thumb1 (the System Control coprocessor/CP15 is mandatory on ARMv6)
    	# On newer ARM architectures we can use Thumb2
    	LOCAL_ARM_MODE := arm
    endif
    ifeq ($(ARCH), armeabi-v7a)
    	LOCAL_CFLAGS += -DHAVE_ARMEABI_V7A
    endif
    LOCAL_LDLIBS := -L$(VLC_CONTRIB)/lib \	#使用本地库
    	$(VLC_MODULES) \
    	$(VLC_BUILD_DIR)/lib/.libs/libvlc.a \
    	$(VLC_BUILD_DIR)/src/.libs/libvlccore.a \
    	$(VLC_BUILD_DIR)/compat/.libs/libcompat.a \
    	-ldl -lz -lm -llog \
    	-ldvbpsi -lebml -lmatroska -ltag \
    	-logg -lFLAC -ltheora -lvorbis \
    	-lmpeg2 -la52 \
    	-lavformat -lavcodec -lswscale -lavutil -lpostproc -lgsm -lopenjpeg \
    	-lliveMedia -lUsageEnvironment -lBasicUsageEnvironment -lgroupsock \
    	-lspeex -lspeexdsp \
    	-lxml2 -lpng -lgnutls -lgcrypt -lgpg-error \
    	-lnettle -lhogweed -lgmp \
    	-lfreetype -liconv -lass -lfribidi -lopus \
    	-lEGL -lGLESv2 -ljpeg \
    	-ldvdnav -ldvdread -ldvdcss \
    	$(CPP_STATIC)
    
    include $(BUILD_SHARED_LIBRARY) #编译成动态库
    
    
    include $(CLEAR_VARS)	#清除变量
    
    LOCAL_MODULE     := libiomx-gingerbread  
    LOCAL_SRC_FILES  := ../$(VLC_SRC_DIR)/modules/codec/omxil/iomx.cpp
    LOCAL_C_INCLUDES := $(VLC_SRC_DIR)/modules/codec/omxil $(ANDROID_SYS_HEADERS_GINGERBREAD)/frameworks/base/include $(ANDROID_SYS_HEADERS_GINGERBREAD)/system/core/include
    LOCAL_CFLAGS     := -Wno-psabi
    LOCAL_LDLIBS     := -L$(ANDROID_LIBS) -lgcc -lstagefright -lmedia -lutils -lbinder
    
    include $(BUILD_SHARED_LIBRARY)
    
    include $(CLEAR_VARS)
    
    LOCAL_MODULE     := libiomx-hc
    LOCAL_SRC_FILES  := ../$(VLC_SRC_DIR)/modules/codec/omxil/iomx.cpp
    LOCAL_C_INCLUDES := $(VLC_SRC_DIR)/modules/codec/omxil $(ANDROID_SYS_HEADERS_HC)/frameworks/base/include $(ANDROID_SYS_HEADERS_HC)/frameworks/base/native/include $(ANDROID_SYS_HEADERS_HC)/system/core/include $(ANDROID_SYS_HEADERS_HC)/hardware/libhardware/include
    LOCAL_CFLAGS     := -Wno-psabi
    LOCAL_LDLIBS     := -L$(ANDROID_LIBS) -lgcc -lstagefright -lmedia -lutils -lbinder
    
    include $(BUILD_SHARED_LIBRARY)
    
    include $(CLEAR_VARS)
    
    LOCAL_MODULE     := libiomx-ics
    LOCAL_SRC_FILES  := ../$(VLC_SRC_DIR)/modules/codec/omxil/iomx.cpp
    LOCAL_C_INCLUDES := $(VLC_SRC_DIR)/modules/codec/omxil $(ANDROID_SYS_HEADERS_ICS)/frameworks/base/include $(ANDROID_SYS_HEADERS_ICS)/frameworks/base/native/include $(ANDROID_SYS_HEADERS_ICS)/system/core/include $(ANDROID_SYS_HEADERS_ICS)/hardware/libhardware/include
    LOCAL_CFLAGS     := -Wno-psabi
    LOCAL_LDLIBS     := -L$(ANDROID_LIBS) -lgcc -lstagefright -lmedia -lutils -lbinder
    
    include $(BUILD_SHARED_LIBRARY)

    2、Application.mk

    Application.mk目的是描述在你的应用程序中所需要的模块(即静态库或动态库)。

    变量 描述
    APP_PROJECT_PATH这个变量是强制性的,并且会给出应用程序工程的根目录的一个绝对路径。
    APP_MODULES这个变量是可选的,如果没有定义,NDK将由在Android.mk中声明的默认的模块编译,并且包含所有的子文件(makefile文件)如果APP_MODULES定义了,它不许是一个空格分隔的模块列表,这个模块名字被定义在Android.mk文件中的LOCAL_MODULE中。
    APP_OPTIM这个变量是可选的,用来义“release”或"debug"。在编译您的应用程序模块的时候,可以用来改变优先级。
    APP_CFLAGS当编译模块中有任何C文件或者C++文件的时候,C编译器的信号就会被发出。
    APP_CXXFLAGSAPP_CPPFLAGS的别名,已经考虑在将在未来的版本中废除了
    APP_CPPFLAGS当编译的只有C++源文件的时候,可以通过这个C++编译器来设置
    APP_BUILD_SCRIPT默认情况下,NDK编译系统会在$(APP_PROJECT_PATH)/jni目录下寻找名为Android.mk文件:
    $(APP_PROJECT_PATH)/jni/Android.mk
    APP_ABI默认情况下,NDK的编译系统回味"armeabi"ABI生成机器代码。
    APP_STL默认情况下,NDK的编译系统为最小的C++运行时库(/system/lib/libstdc++.so)提供C++头文件。然而,NDK的C++的实现,可以让你使用或着链接在自己的应用程序中。
    例如:
    APP_STL := stlport_static    --> static STLport library
    APP_STL := stlport_shared    --> shared STLport library
    APP_STL := system            --> default C++ runtime library
      

    实例:

    APP_OPTIM := release   //调试版还是发行版
    APP_PLATFORM := android-8  //平台
    APP_STL := gnustl_static  //C++运行时库
    APP_CPPFLAGS += -frtti		//编译标识
    APP_CPPFLAGS += -fexceptions  //编译标识 异常
    APP_CPPFLAGS += -DANDROID	//编译标识
    APP_MODULES := test		//静态模块

    JNI内存泄漏

    JAVA 编程中的内存泄漏,从泄漏的内存位置角度可以分为两种:JVM 中 Java Heap 的内存泄漏;JVM 内存中 native memory 的内存泄漏。

    Java Heap 的内存泄漏:
    Java 对象存储在 JVM 进程空间中的 Java Heap 中,Java Heap 可以在 JVM 运行过程中动态变化。如果 Java 对象越来越多,占据 Java Heap 的空间也越来越大,JVM 会在运行时扩充 Java Heap 的容量。如果 Java Heap 容量扩充到上限,并且在 GC 后仍然没有足够空间分配新的 Java 对象,便会抛出 out of memory 异常,导致 JVM 进程崩溃。
    Java Heap 中 out of memory 异常的出现有两种原因①程序过于庞大,致使过多 Java 对象的同时存在;②程序编写的错误导致 Java Heap 内存泄漏。

    JVM 中 native memory 的内存泄漏
    从操作系统角度看,JVM 在运行时和其它进程没有本质区别。在系统级别上,它们具有同样的调度机制,同样的内存分配方式,同样的内存格局。
    JVM 进程空间中,Java Heap 以外的内存空间称为 JVM 的 native memory。进程的很多资源都是存储在 JVM 的 native memory 中,例如载入的代码映像,线程的堆栈,线程的管理控制块,JVM 的静态数据、全局数据等等。也包括 JNI 程序中 native code 分配到的资源。
    在 JVM 运行中,多数进程资源从 native memory 中动态分配。当越来越多的资源在 native memory 中分配,占据越来越多 native memory 空间并且达到 native memory 上限时,JVM 会抛出异常,使 JVM 进程异常退出。而此时 Java Heap 往往还没有达到上限。
    多种原因可能导致 JVM 的 native memory 内存泄漏。
    例如:
    JVM 在运行中过多的线程被创建,并且在同时运行。
    JVM 为线程分配的资源就可能耗尽 native memory 的容量。
    JNI 编程错误也可能导致 native memory 的内存泄漏。
    Native Code 本身的内存泄漏
    JNI 编程首先是一门具体的编程语言,或者 C 语言,或者 C++,或者汇编,或者其它 native 的编程语言。每门编程语言环境都实现了自身的内存管理机制。因此,JNI 程序开发者要遵循 native 语言本身的内存管理机制,避免造成内存泄漏。以 C 语言为例,当用 malloc() 在进程堆中动态分配内存时,JNI 程序在使用完后,应当调用 free() 将内存释放。总之,所有在 native 语言编程中应当注意的内存泄漏规则,在 JNI 编程中依然适应。
    Native 语言本身引入的内存泄漏会造成 native memory 的内存,严重情况下会造成 native memory 的 out of memory。
    Global Reference 引入的内存泄漏
    JNI 编程还要同时遵循 JNI 的规范标准,JVM 附加了 JNI 编程特有的内存管理机制。
    JNI 中的 Local Reference 只在 native method 执行时存在,当 native method 执行完后自动失效。这种自动失效,使得对 Local Reference 的使用相对简单,native method 执行完后,它们所引用的 Java 对象的 reference count 会相应减 1。不会造成 Java Heap 中 Java 对象的内存泄漏。
    而 Global Reference 对 Java 对象的引用一直有效,因此它们引用的 Java 对象会一直存在 Java Heap 中。程序员在使用 Global Reference 时,需要仔细维护对 Global Reference 的使用。如果一定要使用 Global Reference,务必确保在不用的时候删除。就像在 C 语言中,调用 malloc() 动态分配一块内存之后,调用 free() 释放一样。否则,Global Reference 引用的 Java 对象将永远停留在 Java Heap 中,造成 Java Heap 的内存泄漏。
    LocalReference 的深入理解
    Local Reference 在 native method 执行完成后,会自动被释放,似乎不会造成任何的内存泄漏。但这是错误的。


    泄漏实例1:创建大量的 JNI Local Reference
    Java 代码部分
     class TestLocalReference { 
     private native void nativeMethod(int i); 
     public static void main(String args[]) { 
             TestLocalReference c = new TestLocalReference(); 
             //call the jni native method 
             c.nativeMethod(1000000); 
     }  
     static { 
     //load the jni library 
     System.loadLibrary("StaticMethodCall"); 
     } 
     } 
    
    
     JNI 代码,nativeMethod(int i) 的 C 语言实现
     #include<stdio.h> 
     #include<jni.h> 
     #include"TestLocalReference.h"
     JNIEXPORT void JNICALL Java_TestLocalReference_nativeMethod 
     (JNIEnv * env, jobject obj, jint count) 
     { 
     jint i = 0; 
     jstring str; 
    
    
     for(; i<count; i++) 
             str = (*env)->NewStringUTF(env, "0"); 
     } 
    运行结果
     JVMCI161: FATAL ERROR in native method: Out of memory when expanding 
     local ref table beyond capacity 
     at TestLocalReference.nativeMethod(Native Method) 
     at TestLocalReference.main(TestLocalReference.java:9)

    泄漏实例2:建立一个 String 对象,返回给调用函数。
    JNI 代码,nativeMethod(int i) 的 C 语言实现
     #include<stdio.h> 
     #include<jni.h> 
     #include"TestLocalReference.h"
     jstring CreateStringUTF(JNIEnv * env) 
     { 
     return (*env)->NewStringUTF(env, "0"); 
     } 
     JNIEXPORT void JNICALL Java_TestLocalReference_nativeMethod 
     (JNIEnv * env, jobject obj, jint count) 
     { 
     jint i = 0; 
     for(; i<count; i++) 
     { 
             str = CreateStringUTF(env); 
     } 
     } 
    运行结果
     JVMCI161: FATAL ERROR in native method: Out of memory when expanding local ref 
     table beyond  capacity 
     at TestLocalReference.nativeMethod(Native Method) 
     at TestLocalReference.main(TestLocalReference.java:9)


    编译问题:SLES/OpenSLES.h: No such file or directory
    解决方法:ndk-build TARGET_PLATFORM=android-9


    编译断点问题:有没有好用的断点工具

    解决方法:visualGDB 神器


    展开全文
  • JNI入门-第一个实例

    千次阅读 2018-08-26 12:43:24
    NDK简介 官方文档介绍如下: The Android NDK is a toolset that lets you implement parts of your app in native code, using languages such as C and C++. For certain types of apps, this can help you ...
  • JNI学习笔记

    千次阅读 2017-02-24 16:55:07
    1为什么使用JNIJNI 的强大特性使我们在使用 JAVA 平台的同时,还可以重用原来的本地代码。作为虚拟机 实现的一部分,JNI 允许 JAVA 和本地代码间的双向交互。 请记住,一旦使用 JNI,JAVA 程序就丧失了 JAVA 平台...
  • JNI实现机制

    千次阅读 2018-11-09 12:12:47
    说到JNI都不陌生,它的全称:Java Native Interface,即Java本地接口。 JNI不仅仅是Android特有的,它是属于Java平台的,它允许在Java虚拟机内运行的java代码与其他编程语言(如c, c++和汇编语言)编写的程序和库...
  • Android:JNI 与 NDK到底是什么?(含实例教学)

    万次阅读 多人点赞 2019-04-21 08:17:02
    今天,我将先介绍JNI 与 NDK & 之间的区别,手把手进行 NDK的使用教学,希望你们会喜欢 目录1. JNI介绍1.1 简介 定义:Java Native Interface,即 Java本地接口 作用: 使得Java 与 本地其他类型语言(如C、C++)...
  • jni是什么

    千次阅读 2019-01-05 16:33:52
    JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&amp;C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是...
  • 该问题产生与继承的子类没有定义方法,而该方法字父类已定义,但是由于 是private,所以子类无法找到该方法,改为protect 或者 public
  • 代码下载:http://dl.dbank.com/c0c0xs3b24 一、JNI实现回调 通过JNI在Native层调用JAVA层的方法,来实现Native层向JAVA层传递消息。 JNICallback.java public class JNICallb
  • Android JNI(实现自己的JNI_OnLoad函数)

    万次阅读 热门讨论 2010-09-03 18:25:00
    (2)自己重写JNI_OnLoad()函数。(本文介绍)(Android中采用这种) Java端代码:package com.jni; public class JavaHello { public static native String hello(); static { // load ...
  • Javah生成JNI头文件出现找不到类的错误 换个格式即可:javah -classpath . -jni com.harlan.jni.HelloCPP 记住通用格式—— 在工程的bin目录下,输入命令: javah -classpath . -jni 类路径.JNI...
  • Android Studio创建assets、jniLibs目录

    万次阅读 2018-03-17 16:15:58
    android studio中创建assets、jniLibs目录,引入arr文件
  • 使用JNI调用so的时候出现java.lang.UnsatisfiedLinkError: JNI_ERR returned from JNI_OnLoad XXXX.so错误,这是因为无法调用到so。 1.确认在moudle中的gradle写入了 sourceSets { main { jniLibs.srcDirs = ['libs'...
  • Android中关于libs和JniLibs的各种坑

    千次阅读 2018-10-11 14:38:08
    如果是在Android Studio中,则会默认匹配main下的jniLibs目录,如果没有目录需要自己手动创建。并且库的名称也不能随便更改。 但是这里会有一个问题,就是如果使用的是AndroidStudio,但是想用libs下的库,还需要...
  • 添加了ZXing的依赖,出现64K问题,解决完64k问题之后,报 java.lang.UnsatisfiedLinkError: JNI_ERR returned from JNI_OnLoad in "/data/app-lib/com.example.santintrestaurant-1/libQt5Core.so"
1 2 3 4 5 ... 20
收藏数 145,261
精华内容 58,104
关键字:

jni