Android NDK编程

JNI

简介

JNI(Java Native Interface),译作Java本地接口,是Java与Native原生层语言互通的桥梁。Native语言一般指C和Cpp。通过JNI特性,Java编写的函数可以调用Native(C、Cpp)语言编写的函数,同样,Native语言编写的函数也可以调用Java编写的函数。JNI是Java语言的特性,有非常广泛的应用场景,而非Android独有。Android NDK(Native Development Kit)是Android为了方便Android程序员利用JNI特性使Java语言和Native语言互通而发明的开发工具集。

何时使用JNI?

  1. 应用需要直接操作硬件,访问系统的特性和设备。JVM本身不跨平台,为了使JAVA实现跨平台操作,JVM将硬件操作全部封装起来。所以Java并不能进行驱动开发等操作,但Java可以通过JNI调用C语言进行驱动开发、操作硬件。使用JNI开发的应用不是平台无关的,除非将本地代码在不同的操作平台下编译出相应的动态库。
  2. 应用对性能(运行效率、安全性)要求比较高。
  3. 复用C、Cpp沉淀的优秀代码(如文件压缩、人脸识别)。

如何使用JNI?

  1. 在Java类中声明一个本地方法。
  2. 运行javah获得包含该方法的C声明的头文件(也可以不生成头文件,直接编写本地代码)
  3. 用C实现该本地方法
  4. 将代码置于共享类库中
  5. 在Java程序中加载该类库。
/*第1步*/
class HelloNative
{
    public static native void greeting();
}

本地方法既可以是静态也可以是非静态的,此处本地方法被声明为静态方法。

/*第2-3步*/
/*不生成头文件的Native代码编写*/
#include"jni.h"
#include<stdio.h>s
Jstring Java_HelloNative_greeting(JNIEnv* enc,jclass cl)
{
    printf("Hello Native World\n");
}
  • Native中方法的命名方式:
  • 使用完全限定方法名,若类属于某个包,则要包含包名。如HelloNative.greeting和com.exam.HelloNative.greeting。
  • 将点号替换为下划线,并加上“Java_”前缀
  • 如果类名中含有非ASCII字母或数字,用_0xxxx替换,xxxx是该字符的4个十六进制数字

可以使用javah工具自动生成函数名:

/*第2步*/
javac HelloNative.class 
javah HelloNative

/*HelloNative.h*/
#include<jni.h>

……省略……
JNIEXPORT void JNICALL Java_helloNative_greeting(JNIEnv*,jclass);
……省略……

生成的HelloNative.h中包含了java方法的声明,只需在Native代码中包含该头文件并实现方法。

/*第3步*/
#include"HelloNative.h"
#include<stdio.h>
JNIEXPORT void JNICALL Java_HelloNative_greeting(JNIEnv* env,jclass cl)
{
    printf("Hello Native World\n");
}

方法的第一个参数,JNIEnv* 为Java Native Interface Environmen,译作Java本地接口环境,是指向可用JNI函数表的接口指针。函数表的每一个成员指向一个JNI函数,Native方法通过JNI函数访问JVM中的数据结构。JNIEnv指针在jni.h中实现。

C调用JNI函数时,需要先对JNIEnv指针解引用

return (*env)->NewStringUTF(env,”Hello Native World\n”)

Cpp调用JNI函数时,不需要先对JNIEnv指针解引用

return env->NewStringUTF(“Hello Native World\n”)

方法的第二个参数,当Native方法为静态方法时,为Jclass,是HelloNative类的Java对象引用;当Native方法为非静态方法时,为Jobject,是HelloNative类实例的Java对象引用。

/*第4步*/
/*将C代码编译到一个动态装载库中,具体方法依赖于编译器*/
/*linux 下的 Gnu C编译器*/
gcc -fPIC -I jdk/include -I jdk/include/linux -shared -o libHelloNative.so Hellonative.c
/*Windows下的微软编译器*/
cl -I jdk\include -I jdk\include\win32 -LD HelloNative.c -FeHelloNative.dll
/*第5步*/
class HelloNativeTest
{
    public static void main(String args[])
    {
      HelloNative.greeting();
    }
}
static
{
    System.loadLibrary("HelloNative");
}

java.lang.System类提供了两个静态方法用于在运行时加载共享库,void load(String filename)和void loadLibrary(String libname)。给loadLibrary()传的参数只是库名,JVM根据操作系统加上必要前后缀。

数据类型转换

Java数据类型分为基本数据类型和引用数据类型,JNI也区别对待这两种类型。JNI数据类型和方法是用于传入、传出Native代码的。如java中的函数public static native void exp(int x,double x ),需要两个参数x,y。在Native中实现时,x类型为jint,y类型为jdouble,函数为JNIEXPORT void JNICALL Java_HelloNative_exp(JNIEnv* env,jclass cl,jint x,jdouble y)。如果不用于互通用途,用Native原生的类型(int,double)、方法即可。

基本数据类型

引用数据类型

JNI API

引用类型以不透明的引用方式传递给原生代码,因此不能直接使用和修改,JNI提供了一组API操作引用类型,这些API通过JNIEnv接口指针提供给原生函数。

JNI API官方文档

字符串

JNI将字符串视为引用类型,针对Unicode和UTF-8编码格式的字符串,提供了两组函数进行处理。

UnicodeUTF-8
NewStringNewStringUTF
GetStringCharsGetStringUTFChars
ReleaseStringCharsReleaseStringUTFChars

1.用给定的C字符串创建Java字符串

jstring javastring;
javaString = (*env)->NewStringUTF(env,"Hello World!");

2.将Java字符串转换为C字符串

const jbyte* str;
str = (*env)->GetStringUTFChars(env,javastring);

3.释放字符串

(*env)->ReleaseStringUTFChars(env,javaString,str)

数组操作

1.创建数组,使用New<Type> Array创建数组,<Type>为Int,Char,Double等。

jintArray javaArray;
javaArray = (*env)->NewIntArray(env,10);

2.访问数组,JNI提供两种方式访问Java数组的元素。

2.1 使用Get<Type>ArrayRegion函数将Java数组复制为C数组,如此可以像对普通C数组一样使用,之后再使用Set<Type>ArrayRegion函数将C数组还原为Java数组。

jint nativeArray[10];
(*env)->GetIntArrayRegion(env,javaArray,0,10,nativeArray);
(*env)->SetIntArrayRegion(env,javaArray,0,10,nativeArray);

2.2 让JNI提供直接指向数组元素的指针。使用Get<Type>ArrayElements获取指针。JNI要求操作完成后后立即使用Release<Type>ArrayElements释放数组,否则回内存溢出。

jint* nativeDirectoryArray;
nativeDirectoryArray = (*env)->GetIntArrayElements(env,javaArray);
(*env)->ReleaseIntArrayElements(env,javaArray,nativeDirectoryArray,0);

访问域

Java有实例域和静态域,JNI提供了访问两类域的方法。

3.1使用GetObjectClass或FindClass方法通过给定实例的class对象获取域ID。

jcalss clazz;
clazz = (*env)->GetObjectClass(env,instance);

3.2 获取实例域和静态域的域ID

jfieldID InstanceFieldID;
instanceFieldId = (*env)->GetFieldID(env,clazz,"instanceField","Ljava/lang/String;");

jfieldID staticFieldID;
staticFieldId = (*env)->GetStaticFieldID(env,clazz,"staticField","Ljava/lang/String;");

3.3使用Get<Type>Field和GetStatic<Type>Field获取域

jstring InstanceField;
instanceField = (*env)->GetField(env,clazz,instance,instanceField);

jstring staticField;
staticField = (*env)->GetStaticFieldID(env,clazz,staticField);

调用方法

/*获取实例方法ID*/
jmethodID instanceMethodId;
instanceMethodId = (*env)->GetMethodID(env,clazz,"instanceMethod","()Ljava/lang/String;");

/*调用实例方法ID*/
jstring instanceMethodResult;
staticMethodResult = (*env)->CallStaticStringMethod(env,clazz,"staticMethod","()Ljava/lang/String;");

/*获取静态方法ID*/
jmethodID instanceMethodId;
instanceMethodId = (*env)->GetMethodID(env,clazz,"instanceMethod","()Ljava/lang/String;");

/*调用静态方法ID*/
jstring staticMethodResult;
staticMethodResult = (*env)->CallStaticStringMethod(env,clazz,instanceMethodId);
参考书籍:
《Java核心技术卷2 高级特性》
《Android C++高级编程使用NDK》
《Android高级开发实战-UI、NDK与安全》
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇