我们都知道Java和C/C++不同 ,它不会直接编译成平台机器码,而是编译成虚拟机可以运行的Java字节码的.class文件,而Android底层的c/c++库。所以在音视频开发的时候,如果在java层处理数据,则要把数据从native层拷贝到java进行处理,处理完再拷贝回native层,这样处理效率会比较低下。为了提高代码的性能,会引入java和c,c++的混合开发。
JNI(Java Native Interface)是java本地接口,它主要是为了实现Java调用c、c++等本地代码所封装的一层接口。通过JNI,Java可以调用c、c++,相反,c、c++也可以调用Java的相关代码。
新建项目的时候有一个选项是选择Native C++的模板
点击next,配置项目的信息
点击next,选择使用哪种C++标准,选择Toolchain Default会使用默认的CMake设置即可
点击finish即可完成工程的创建。
这时候主工程目录下会有cpp文件夹
cpp文件夹:存放C/C++代码文件,native-lib.cpp文件默认生成的;
cpp文件夹下有两个文件,一个是native-lib.cpp文件,一个是CMakeLists.txt文件。CMakeLists.txt文件是cmake脚本配置文件,cmake会根据该脚本文件中的指令去编译相关的C/C++源文件,并将编译后产物生成共享库或静态块,然后Gradle将其打包到APK中。
CMakeLists.txt的相关配置如下:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.10.2)
# Declares and names the project.
project("myapplication")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
native-lib #设置库的名称。即SO文件的名称,生产的so文件为“libnative-lib.so”,
# Sets the library as a shared library.
SHARED # 将库设置为共享库。
# Provides a relative path to your source file(s).
native-lib.cpp ) # 提供一个源文件的相对路径
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
# 搜索指定的预构建库,并将该路径存储为一个变量。因为cbuild默认包含了搜索路径中的系统库,所以您只需要指定您想要添加的公共NDK库的名称。cbuild在完成构建之前验证这个库是否存在。
find_library( # Sets the name of the path variable.
log-lib # 设置path变量的名称。
# Specifies the name of the NDK library that
# you want CMake to locate.
# 指定NDK库的名称 你想让CMake来定位。
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
#指定库的库应该链接到你的目标库。您可以链接多个库,比如在这个构建脚本中定义的库、预构建的第三方库或系统库。
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
build.gradle中有CMake的相关配置
在MainActivity.java,static{}语句中使用了加载so库,在类加载中只执行一次。
static {
System.loadLibrary("native-lib");
}
然后,编写了原生的函数,函数名中要带有native。
public native String stringFromJNI();
最后,编写相对应的c函数,注意函数名的构成Java_com_pengjie0668_demo_myapplication_MainActivity_stringFromJNI加上包名、类型、方法名的下划线连成一起。
注意:要按照jni的规范定义方法(Java_包名_类名_native方法名,其中包名中的点用_代替)
native-lib.cpp文件
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_pengjie0668_demo_myapplication_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
这就是一个JNI方法调用示例。
虽然Java函数不带参数,但是原生方法却带了两个参数,第一个参数JNIEnv是指向可用JNI函数表的接口指针,第二个参数jobject是Java函数所在类的实例的Java对象引用。
反过来如果需要在native层调用java层代码,我们可以在native-lib.cpp文件中添加方法
extern "C"
JNIEXPORT void JNICALL
Java_com_pengjie0668_demo_myapplication_MainActivity_callJavaMethodTest(JNIEnv *env, jobject thiz,jstring text) {
//通过传进来的对象找到该类
jclass javaClass = env->GetObjectClass(thiz);
if (javaClass == 0) {
return;
}
//获取要回调的方法ID,回调java方法
jmethodID javaMethodId = env->GetMethodID( javaClass, "callJavaMethod", "(Ljava/lang/String;)V");
env->CallVoidMethod( thiz, javaMethodId ,text);
}
这个方法是java 调用native方法。如果要回调java方法,我们首先要通过 jobject 获取外层的 Java 对象,其中在调用JNI的GetMethodID方法时,最后一个参数看起来比较奇怪。这里特别讲一下,这个参数传递的内容叫做“java方法签名”,如果使用的是AndroidStudio开发工具,我们可以在工程目录app/build/intermediates/javac/debug/classes 文件夹下执行这个命令
javap -s 包名.类名
能查询到。如图
这里对于void类型的无参方法,它的签名是“()V”,对于有参数的int OnCallArgu(int arg),它的方法签名就是这样“(I)I”。最后JNI同步回调java方法就处理完了。
然后在MainActivity.java中添加一个native方法
//java层调用native层方法
public native void callJavaMethodTest(String text);
用于从java层触发进入native层,最后添加一个java方法供native层调用
//native层回调java层方法
public void callJavaMethod(String text) {
Toast.makeText(this, text, Toast.LENGTH_LONG).show();
}
通过观察我们Java层的函数stringFromJNI没有参数,但是原生方法还是自带了两个参数,其中第一个参数是JNIEnv.
JNIEnv是指向可用JNI函数表的接口指针,原生代码通过JNIEnv接口指针提供的各种函数来使用虚拟机的功能。JNIEnv是一个指向线程-局部数据的指针,而线程-局部数据中包含指向线程表的指针。实现原生方法的函数将JNIEnv接口指针作为它们的第一个参数。
原生代码是C以及原生代码是C++其调用JNI函数的语法不同,C代码中,JNIEnv是指向JNINativeInterface结构的指针,为了访问任何一个JNI函数,该指针需要首先被解引用。因为C代码中的JNI函数不了解当前的JNI环境,JNIEnv实例应该作为第一个参数传递给每一个JNI函数调用者。
正确的写法应该是下面这样
jstring Java_com_example_jni_MainActivity_stringFromC(JNIEnv\* env,jobject thiz){
return (*env)->NewStringUTF(env,"Hello from C");
}
然而,在C++代码中,JNIEnv实际上是C++类实例,JNI函数以成员函数形式存在,因为JNI方法已经访问了当前的JNI环境,因此JNI方法调用不要求JNIEnv实例作参数,在C++中,完成同样的功能代码应该是下面这样
extern "C" jstring Java_com_example_jni_MainActivity_stringFromCpp(JNIEnv\* env,jobject thiz){
return env->NewStringUTF("Hello from C++");
}
上面提到JNIEnv是指向可用JNI函数表的接口指针,所以每个函数都可以通过JNIEnv参数访问,JNIEnv类型是指向一个存放所有JNI接口指针的指针,其定义如下:
typedef const struct JNINativeInterface \*JNIEnv;
我们可以看下JNINativeInterface 内部定义
const struct JNINativeInterface ... = {
NULL,
NULL,
NULL,
NULL,
GetVersion,
DefineClass,
FindClass,
FromReflectedMethod,
FromReflectedField,
ToReflectedMethod,
GetSuperclass,
IsAssignableFrom,
ToReflectedField,
Throw,
ThrowNew,
ExceptionOccurred,
ExceptionDescribe,
ExceptionClear,
FatalError,
PushLocalFrame,
PopLocalFrame,
NewGlobalRef,
DeleteGlobalRef,
DeleteLocalRef,
IsSameObject,
NewLocalRef,
EnsureLocalCapacity,
AllocObject,
NewObject,
NewObjectV,
NewObjectA,
GetObjectClass,
IsInstanceOf,
GetMethodID,
CallObjectMethod,
CallObjectMethodV,
CallObjectMethodA,
CallBooleanMethod,
CallBooleanMethodV,
CallBooleanMethodA,
CallByteMethod,
CallByteMethodV,
CallByteMethodA,
CallCharMethod,
CallCharMethodV,
CallCharMethodA,
CallShortMethod,
CallShortMethodV,
CallShortMethodA,
CallIntMethod,
CallIntMethodV,
CallIntMethodA,
CallLongMethod,
CallLongMethodV,
CallLongMethodA,
CallFloatMethod,
CallFloatMethodV,
CallFloatMethodA,
CallDoubleMethod,
CallDoubleMethodV,
CallDoubleMethodA,
CallVoidMethod,
CallVoidMethodV,
CallVoidMethodA,
CallNonvirtualObjectMethod,
CallNonvirtualObjectMethodV,
CallNonvirtualObjectMethodA,
CallNonvirtualBooleanMethod,
CallNonvirtualBooleanMethodV,
CallNonvirtualBooleanMethodA,
CallNonvirtualByteMethod,
CallNonvirtualByteMethodV,
CallNonvirtualByteMethodA,
...
..
CallNonvirtualCharMethod,
CallNonvirtualCharMethodV,
CallNonvirtualCharMethodA,
GetDirectBufferAddress,
GetDirectBufferCapacity,
GetObjectRefType
};
所以我们可以通过JNIEnv指针,调用相关函数方法,例如获取JNI版本信息
jint GetVersion(JNIEnv \*env);
查找类
jclass FindClass(JNIEnv _env,const char_ name);
JNI的数据类型包含两种:基本类型和引用类型。
JNI类型 | Java类型 |
---|---|
jboolean | boolean |
jbyte | byte |
jchar | char |
jshort | short |
jint | int |
jlong | long |
jfloat | float |
jdouble | double |
void | void |
JNI类型 | Java类型 |
---|---|
jobject | Object |
jclass | Class |
jstring | String |
jobjectArray | Object[] |
jbooleanArray | boolean[] |
jbyteArray | char[] |
jshortArray | short[] |
jintArray | int[] |
jlongArray | long[] |
jfloatArray | float[] |
jdoubleArray | double[] |
jthrowable | Throwable |
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。