简介
JNI,全名 Java Native Interface,Java代表的使java语言,Native代表当前程序运行的本地环境,一般是Windows和Linux,这些操作系统都是通过C/C++实现的,所以Native通常也指C/C++语言,Interface代表java跟native两者之间的通信接口,JNI可以实现java和C/C++通信。
Java虽然跨平台,但仍然运行在具体平台(windows,linux)之上,对于需要操作硬件的功能,必须通过系统的C/C++方法对硬件进行直接操作;在一些拥有复杂算法的场景(音视频编解码,图像绘制等),Java的执行效率远低于C/C++的执行效率。鉴于上述情况,JNI技术就有了发挥的地方,Java通过JNI技术就可以调用C/C++来帮助自己取执行一些它所不擅长的事情。
JNI下一共涉及到三个角色:C/C++代码、本地方法接口类、Java层中具体业务类。
JNI命名方法
JNIExport jstring JNICALL Java_com_example_hellojni_MainActivity_stringFromJNI( JNIEnv* env,jobject thiz )
jstring
是返回值类型 Java_com_example_hellojni
是包名 MainActivity
是类名 stringFromJNI
是方法名
其中JNIExport
和JNICALL
是不固定保留的关键字不要修改
JNI的使用流程
第1步:在Java中先声明一个native方法
第2步:编译Java源文件javac得到.class文件
第3步:通过javah -jni命令导出JNI的.h头文件
第4步:使用Java需要交互的本地代码,实现在Java中声明的Native方法(如果Java需要与C++交互,那么就用C++实现Java的Native方法。)
第5步:将本地代码编译成动态库(Windows系统下是.dll文件,如果是Linux系统下是.so文件,如果是Mac系统下是.jnilib)
第6步:通过Java命令执行Java程序,最终实现Java调用本地代码。
示例代码
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (JNIEnv *env, jobject obj, jint i, jstring s)
{
const char *str = (*env)->GetStringUTFChars(env, s, 0);
(*env)->ReleaseStringUTFChars(env, s, str);
return 10;
}
传入参数分析
*env:一个接口指针
obj:在本地方法中声明的对象引用
i和s:用于传递的参数
关于obj、i和s的类型大家可以参考下面的JNI数据类型,JNI有自己的原始数据类型和数据引用类型如下:
在Android Studio中的安装
在Android SDK中安装NDK和Cmake
安装完成之后,创建工程选择Native C++
创建完成之后,会出现一个cpp文件夹
运行该工程出现如下结果,则标识创建成功
JNI中常用函数
创建Java中的对象
jobject NewObject(JNIEnv *env, jclass clazz,jmethodID methodID, ...)
jobject NewObjectA(JNIEnv *env, jclass clazz,jmethodID methodID, const jvalue *args)
jobject NewObjectV(JNIEnv *env, jclass clazz,jmethodID methodID, va_list args)
第一个参数jclass class 代表你要创建哪个类的对象,第二个参数jmethodID methodID代表你要使用那个构造方法ID来创建这个对象。只要有jclass和jmethodID,我们就可以在本地方法创建这个Java类的对象。
创建Java类中的String对象
jstring NewString(JNIEnv *env, const jchar *unicodeChars,jsize len)
通过Unicode字符的数组来创建一个新的String对象。 env是JNI接口指针;unicodeChars是指向Unicode字符串的指针;len是Unicode字符串的长度。返回值是Java字符串对象,如果无法构造该字符串,则为null。
创建类型为基本类型PrimitiveType的数组
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
指定一个长度然后返回相应的Java基本类型的数组
用于构造一个新的数组对象,类型是原始类型。基本的原始类型如下:
创建类型为elementClass的数组
jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
造一个新的数据组,类型是elementClass,所有类型都被初始化为initialElement。
获取数组中某个位置的元素
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);
返回Object数组的一个元素
获取数组的长度
jsize GetArrayLength(JNIEnv *env, jarray array);
获取array数组的长度
例程
package com.example.as_jni_project;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.example.as_jni_project.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("as_jni_project");
}
private String name = "Derry";
private ActivityMainBinding binding;
public native String stringFromJNI(); //实现在C++层
public native void changeName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
TextView tv = binding.sampleText;
tv.setText(stringFromJNI());
System.out.println("name修改前是:" + name);
changeName();
System.out.println("name修改后是:" + name);
}
}
上述例程实现的是一个更改变量内容的操作,其中更改内容的函数实在C++层实现的,选中函数名,alt+enter创建该函数的实现,在native-lib.cpp中。
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_as_1jni_1project_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C" //下面的代码采用C的编译方式,因为JNINativeInterface这个结构体中所使用的C语言比较多
JNIEXPORT void JNICALL //JNIEXPORT标识JNI中的重要关键字,不能少;void表示java中的void;JNICALL也是一个关键字
Java_com_example_as_1jni_1project_MainActivity_changeName(JNIEnv *env, jobject thiz) {
// TODO: implement changeName()
}
jobject == MainActivity thiz实例调用的 jclass(函数为static的时候,不用jobject) == MainActivity class类调用 如果是native-lib.c的情况,(*env)->xxx函数,因为是二级指针;如果是native-lib.cpp的情况,env->xxx函数,因为是一级指针。
如果是C++
struct _JNIEnv
typedef _JNIEnv JNIEnv
JNIEnv *env
一级指针
如果是C
struct _JNIEnv
typedef const struct JNINativeInterface* JNIEnv
JNIEnv *env
二级指针
JNIEnv这个类中,是用来决定C和C++的编译方式。
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
changName()函数如下
extern "C" //下面的代码采用C的编译方式
JNIEXPORT void JNICALL //JNIEXPORT标识JNI中的重要关键字,不能少;void表示java中的void;JNICALL也是一个关键字
Java_com_example_as_1jni_1project_MainActivity_changeName(JNIEnv *env, jobject thiz) {
// TODO: implement changeName()
//String为引用类型,JNI全部命名为object
jclass mainActivityCls = env->FindClass("com/example/as_jni_project/MainActivity");
jfieldID nameFid = env->GetFieldID(mainActivityCls, "name", "Ljava/lang/String;");
jstring value = env->NewStringUTF("Beyond");
env->SetObjectField(thiz, nameFid, value);
}