NDK是Native Developement Kit的缩写,顾名思义,NDK是Google提供的一套原生Java代码与本地C/C++代码“交互”的开发工具集。而Android是运行在Dalvik虚拟机之上,支持通过JNI的方式调用本地C/C++动态链接库。C/C++有着较高的性能和移植性,通过这种调用机制就可以实现多平台开发、多语言混编的Android应用了。当然,这些都是基于JNI实现的。在游戏开发中,这种需求更是必不可少。

  1、认识JNI

  JNI是Java Native Interface的缩写,也称为Java本地接口。是JVM规范中的一部分,因此,我们可以将任何实现了JVM规范的JNI程序在Java虚拟机中运行。这里的本地接口,主要指的是C/C++所现实的接口。因此,也使得我们可以通过这种方式重用C/C++开发的代码或模块。

  具体关于JNI的详细介绍,可以参见JNI的官方文档。

  Java Native Interface Specification:

  http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html

  2、JNI的类型和数据结构

  实现原生Java代码与本地C/C++代码,一个重要的环节是将原生Java的类型和数据结构映射成本地C/C++支持的相应的类型和数据结构。

  (1)Java基本数据类型与原生C/C++类型对应关系如下:

  Java类型 本地类型 说明

  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

  (2)Java引用数据类型与原生C/C++类型对应关系如下:

  Java类型 本地类型

  Object jobject

  Class jclass

  String jstring

  Object[] jobjectArray

  boolean[] jbooleanArray

  byte[] jbyteArray

  char[] jcharArray

  short[] jshortArray

  int[] jintArray

  long[] jlongArray

  float[] jfloatArray

  double[] jdoubleArray

  通过上面的对应关系可以发现,本地类型的命名基本上是在Java原生类型明明的前面加上了个j,组成j-type格式的新类型命名,还是很直观的。

  (3)JNI引用类型的类关系图,如下:

  (上图源自:Java Native Interface Specification文档)

  3、JNI函数的签名

  在函数的声明中,由函数的参数,返回值类型共同构成了函数的签名。因此,将Java函数映射到本地C/C++中的对应也要遵循相应的规则。

  (1)函数数据类型的签名关系如下:

  Java类型 类型签名

  boolean Z

  byte B

  char C

  short S

  int I

  long J

  float F

  double D

  void V

  full-qualified-class(全限定的类) L

  [] [

  boolean[] [Z

  byte[] [B

  char[] [C

  short[] [S

  int[] [I

  long[] [J

  float[] [F

  double[] [D

  注意:

  1. full-qualified-class(全限定的类):指的是引用类型,用L加全类名表示。

  2. 数组类型的签名,只取中括号左半边。

  (2)JNI函数签名格式比较

  Java函数原型:

  return-value fun(params1, params2, params3)

  return-value:表示返回值

  params:表示参数

  对应函数签名格式为:

  (params1params2params3)return-value

  注意:

  1. JNI函数签名中间都没逗号,没有空格

  2. 返回值在()后面

  3. 如果参数是引用类型,那么参数应该写为:L加全类名加分号。例如:Ljava/lang/String;

  根据这种规则,知道Java函数原型就能判断出对应的JNI函数的签名格式:

Java代码
  1. // 原型为:  
  2. boolean  isLoading();  
  3. // 签名格式为:  
  4. ()Z  
Java代码
  1. // 原型为:  
  2. void  setLevel(int level);  
  3. // 签名格式为:  
  4. (I)V  
Java代码
  1. // 原型为:  
  2. char  getCharFunc(int index, String str, int[] value);  
  3. // 签名格式为:  
  4. (ILjava/lang/String;[I)C  

  4、JNI开发流程

  1.简要开发步骤

  JNI的具体开发流程总结起来分为这么几步:

  (1)在原生java类中声明native方法。native表明该方法为一个本地方法,由C/C++实现。

  (2)使用javac命令将带有声明native方法的类,编译成class字节码。javac是jdk自带的一个命令,一般在javapath/bin(javapath为java安装目录)路径下。

  (3)使用javah命令将编译好的class生成本地C/C++代码的.h头文件。同样,javah也是jdk自带的一个命令。

  (4)实现.h头文件中的方法。

  (5)将本地代码编译成动态库。注意,不同平台的动态库是不一样的。

  (6)在java工程中引用编译好的动态库。

  2.开发实例

  按照上面的开发步骤作为指导,来一步步实现个简单的JNI的例子。

  (1)新建名为HelloJNI的java工程,并新建一个声明了native方法的类。(这里就以Eclipse作为开发IDE举例了)

Java代码
  1. package com.hellojni.test;  
  2.   
  3. public class HelloJni {  
  4.   
  5.     public native void printJni();  
  6.       
  7.     public static void main(String[] args) {  
  8.   
  9.     }  
  10. }  

  (2)使用javac编译该HelloJni的类编译为.class文件。当然,这步也可以由Eclipse来完成即可。

  (3)使用javah命令生成头文件。在命令行终端下输入如下命令:

  javah -classpath E:\workplace\java\HelloJNI\src com.hellojni.test.HelloJni

  classpath:是指定加载类的路径

  com.hellojni.test.HelloJni:为完整类名。注意,不需要带java

  具体javah的使用参数介绍,可以输入javah -help。

  如果,执行成功,会在当前目录下生成com_hellojni_test_HelloJni.h的头文件。

C++代码
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class com_hellojni_test_HelloJni */  
  4.   
  5. #ifndef _Included_com_hellojni_test_HelloJni  
  6. #define _Included_com_hellojni_test_HelloJni  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11. * Class:     com_hellojni_test_HelloJni 
  12. * Method:    printJni 
  13. * Signature: ()V 
  14. */  
  15. JNIEXPORT void JNICALL Java_com_hellojni_test_HelloJni_printJni(JNIEnv *, jobject);  
  16.   
  17. #ifdef __cplusplus  
  18. }  
  19. #endif  
  20. #endif  

  可以看到javah自动为我们生成了一个Java_com_hellojni_test_HelloJni_printJni的方法。格式是:Java_Packagename_Classname_Methodname。

  首先,这里引入了jni.h的头文件。这个是jdk自带的一个头文件,一般在javapath/include(javapath为java安装目录)。

  (4)打开VS新建一个Win32控制台应用程序,应用程序类型选择DLL(Win平台动态库为.dll)。并将生成的Java_com_hellojni_test_HelloJni_printJni.h头文件拷贝到该工程目录下。

  然后,再将该头文件添加到工程中。如图:

  编译生成一下。会提示找不到jni.h。因此,把jni.h拷贝到工程目录下,并加入到项目中。jni.h一般在javapath/include(javapath为java安装目录)路径下。

  重新编译生成下,会提示找不到jni_md.h。这个文件在,javapath/include/win32路径下。拷贝该文件再加入工程。并修改Java_com_hellojni_test_HelloJni_printJni.h头文件。

  将#include 修改为#include "jni.h",在当前目录下找jni.h头文件。

  新建一个hellojni.cpp的源文件。如下:

C++代码
  1. #include "stdafx.h"  
  2. #include <iostream>  
  3. #include "com_hellojni_test_HelloJni.h"  
  4.   
  5. using namespace std;  
  6.   
  7. JNIEXPORT void JNICALL Java_com_hellojni_test_HelloJni_printJni(JNIEnv *env, jobject obj)  
  8. {  
  9.     cout<<"Hello JNI"<<endl;  
  10. }  

  (5)再将工程重新生成下,成功的话,会在工程的Debug目录下生成一个HelloJni.dll的动态库。将HelloJni.dll所在的路径添加到环境变量,这样每次重新生成,在任意目录都能访问。

  (6)在java工程中引用刚生成的HelloJni.dll。并加入如下代码:

Java代码
  1. package com.hellojni.test;  
  2.   
  3. public class HelloJni {  
  4.   
  5.     public native void printJni();  
  6.       
  7.     public static void main(String[] args) {  
  8.         System.loadLibrary("HelloJni");  
  9.           
  10.         HelloJni hello = new HelloJni();  
  11.         hello.printJni();  
  12.     }  
  13. }  

  调用System.loadLibrary来加载动态库。注意,动态库的名字不需要加.dll。

  运行java工程,这时候会提示Exception in thread “main” java.lang.UnsatisfiedLinkError: no HelloJni in java.library.path。这时候,需要重启下Eclipse。因为,刚配置的环境变量。重启下,Eclipse才能识别。

  重启完毕,运行java工程。控制台会输入:

  Hello JNI

  表明整个JNI调用成功。

  第一篇就介绍这么多,大体明白了JNI的整个开发流程及基本规则。下一篇将介绍下在Android NDK环境下的交叉编译及调用过程。

本文发布:Android开发网
本文地址:http://www.jizhuomi.com/android/game/699.html
2017年7月31日
发布:鸡啄米 分类:Android游戏开发 浏览: 评论:0