前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android JNI 开发初体验(四)

Android JNI 开发初体验(四)

原创
作者头像
PengJie
修改2021-01-06 14:21:45
1.6K0
修改2021-01-06 14:21:45
举报
文章被收录于专栏:音视频修炼路

前言

我们都知道Java和C/C++不同 ,它不会直接编译成平台机器码,而是编译成虚拟机可以运行的Java字节码的.class文件,而Android底层的c/c++库。所以在音视频开发的时候,如果在java层处理数据,则要把数据从native层拷贝到java进行处理,处理完再拷贝回native层,这样处理效率会比较低下。为了提高代码的性能,会引入java和c,c++的混合开发。

什么是JNI ?

JNI(Java Native Interface)是java本地接口,它主要是为了实现Java调用c、c++等本地代码所封装的一层接口。通过JNI,Java可以调用c、c++,相反,c、c++也可以调用Java的相关代码。

1.使用Android Studio 创建Native C++工程

新建项目的时候有一个选项是选择Native C++的模板

点击next,配置项目的信息

点击next,选择使用哪种C++标准,选择Toolchain Default会使用默认的CMake设置即可

点击finish即可完成工程的创建。

2.工程结构

这时候主工程目录下会有cpp文件夹

cpp文件夹:存放C/C++代码文件,native-lib.cpp文件默认生成的;

cpp文件夹下有两个文件,一个是native-lib.cpp文件,一个是CMakeLists.txt文件。CMakeLists.txt文件是cmake脚本配置文件,cmake会根据该脚本文件中的指令去编译相关的C/C++源文件,并将编译后产物生成共享库或静态块,然后Gradle将其打包到APK中。

CMakeLists.txt的相关配置如下:

代码语言: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的相关配置

image.png
image.png

3.Java调用native层c/c++代码

在MainActivity.java,static{}语句中使用了加载so库,在类加载中只执行一次。

代码语言:txt
复制
 static {
        System.loadLibrary("native-lib");
    }

然后,编写了原生的函数,函数名中要带有native。

代码语言:txt
复制
public native String stringFromJNI();

最后,编写相对应的c函数,注意函数名的构成Java_com_pengjie0668_demo_myapplication_MainActivity_stringFromJNI加上包名、类型、方法名的下划线连成一起。

注意:要按照jni的规范定义方法(Java_包名_类名_native方法名,其中包名中的点用_代替)

native-lib.cpp文件

代码语言:txt
复制
#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对象引用。

4.native层c/c++调用java层代码

反过来如果需要在native层调用java层代码,我们可以在native-lib.cpp文件中添加方法

代码语言:txt
复制
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 文件夹下执行这个命令

代码语言:txt
复制
  javap -s 包名.类名

能查询到。如图

这里对于void类型的无参方法,它的签名是“()V”,对于有参数的int OnCallArgu(int arg),它的方法签名就是这样“(I)I”。最后JNI同步回调java方法就处理完了。

然后在MainActivity.java中添加一个native方法

代码语言:txt
复制
//java层调用native层方法
public native void callJavaMethodTest(String text);

用于从java层触发进入native层,最后添加一个java方法供native层调用

代码语言:txt
复制
//native层回调java层方法
    public void callJavaMethod(String text) {
        Toast.makeText(this, text, Toast.LENGTH_LONG).show();
    }

JNIEnv是什么?

通过观察我们Java层的函数stringFromJNI没有参数,但是原生方法还是自带了两个参数,其中第一个参数是JNIEnv.

JNIEnv是指向可用JNI函数表的接口指针,原生代码通过JNIEnv接口指针提供的各种函数来使用虚拟机的功能。JNIEnv是一个指向线程-局部数据的指针,而线程-局部数据中包含指向线程表的指针。实现原生方法的函数将JNIEnv接口指针作为它们的第一个参数。

原生代码是C以及原生代码是C++其调用JNI函数的语法不同,C代码中,JNIEnv是指向JNINativeInterface结构的指针,为了访问任何一个JNI函数,该指针需要首先被解引用。因为C代码中的JNI函数不了解当前的JNI环境,JNIEnv实例应该作为第一个参数传递给每一个JNI函数调用者。

正确的写法应该是下面这样

代码语言:txt
复制
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++中,完成同样的功能代码应该是下面这样

代码语言:txt
复制
extern "C" jstring Java_com_example_jni_MainActivity_stringFromCpp(JNIEnv\* env,jobject thiz){

return env->NewStringUTF("Hello from C++");

}

Interface Function Table(接口函数表)

上面提到JNIEnv是指向可用JNI函数表的接口指针,所以每个函数都可以通过JNIEnv参数访问,JNIEnv类型是指向一个存放所有JNI接口指针的指针,其定义如下:

代码语言:txt
复制
typedef const struct JNINativeInterface \*JNIEnv;

我们可以看下JNINativeInterface 内部定义

代码语言:txt
复制
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版本信息

代码语言:txt
复制
jint GetVersion(JNIEnv \*env);

查找类

代码语言:txt
复制
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

Github Demo下载链接

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 什么是JNI ?
    • 1.使用Android Studio 创建Native C++工程
      • 2.工程结构
        • 3.Java调用native层c/c++代码
          • 4.native层c/c++调用java层代码
          • JNIEnv是什么?
          • Interface Function Table(接口函数表)
          • 数据类型
            • 基本类型
              • 引用类型对比
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档