IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android NDK开发(六):Java调用本地函数 -> 正文阅读

[移动开发]Android NDK开发(六):Java调用本地函数

1 Java如何通过JNI调用本地(C/C++)方法?

????????主要分为4步:? ? ? ??
????????(1)在java中利用native关键字定义native方法,表示这个方法是映射到JNI层的,调用时实际上是调用的JNI层函数。
????????(2)根据JNI标准用C/C++语言建立与java方法对应的JNI层函数,并建立映射关系,实现java层与JNI层的相互调用。
? ? ? ? (3)实现JNI层与本地层的相互调用逻辑,并将本地层与JNI层源文件及库编译成so库。
????????(4)利用Java中System类的public static void loadLibrary(String libname)方法加载so库,成功加载即可实现java调用本地方法。
? ? ? ? 下面我们通过例子详细说明下。

2 理论结合实践

????????这里通过一个简单的例子,详细说明上述步骤。

(1)在java层创建native方法

? ? ? ? 通过Android studio创建一个新工程,选择Native C++,工程会自动为你配置好NDK、CMakeLists(Android studio默认使用的本地代码编译工具是Cmake)和JNI示例,如果你选的是普通工程,也可以自己编写CMakeLists,并在app的build.gradle中手动配置NDK、CMakeLists和JNI。我们定义一个LearnJNI类,专门用于定义java native方法,并在其中定义了实例native方法doPlus()和类native方法doMinus(),代码如下:

public class LearnJNI {
    ......
    public native int doPlus(int a,int b);
    public static native int doMinus(int a, int b);
}

(2)创建本地方法与Java方法的映射

? ? ? ? 创建映射的方法有两种,分别称为?静态注册 动态注册

1)静态注册

a.概念:按照JNI指定命名规则,通过方法名字建立JNI本地方法与java native方法的映射。
b.本地方法命名规则为:
? ? ? ? ? ? ? ? ? ? ??
Java_包名_类名_方法名?(注意:包名单词之间也用 _ 下划线分隔)
c.示例:在main\cpp\目录下创建learn_jni.cpp文件,将该cpp文件包含到CMakeLists.txt的源码变量中(这样能让该cpp文件拥有本地环境,可调用NDK的本地库),在该文件中直接定义(其实不用写头文件)与上述java native方法对应的本地方法为:

extern "C"
jint Java_com_boe_jnilearn_LearnJNI_doPlus(JNIEnv *env, jobject thiz, jint a, jint b) {
    return a+b;
}
extern "C"
jint Java_com_boe_jnilearn_LearnJNI_doMinus(JNIEnv *env, jclass clazz, jint a, jint b) {
    return a-b;
}

????????其中本地方法名就是按照静态注册的规则写的,上述就完成了java native方法在JNI层的静态注册。

d.说明:
?????????extern “C” 是指定该方法用C语言的编译方式编译,而不是C++的编译方式,因为由于C++支持函数重载,用C++编译器编译出的函数名字会变为_函数名_形参1类型_形参2类型_...的方式,例如:函数void foo(int x, int y),被C编译器编译后在符号库中的名字为_foo,被C++编译器编译后在符号库中的名字为_foo_int_int,名字变了,这会导致java native找不到对应的本地方法,所以extern“C”必须加
? ? ? ? ●?每个JNI本地方法必须有两个形参,一个是JNIEnv* 类型形参,一个是jobject/jclass类型形参,JNIEnv前面说过用于调用JNI预定义方法,线程唯一,同一个进程可以有多个;如果java native方法是实例方法,那么第二个参数类型就是jobject,表示调用该方法的java实例,如果java native方法是类方法,那么第二个参数类型就是jclass,表示该方法的类。
????????如果记不住命名规则,AS可帮助我们快捷静态注册,在JNI本地方法所在的C++文件中,输入java native名字就会有补全,选在下面的两个回车,即可生成。

或者在java native方法所在的类中,光标移动到native方法,alt+enter,选择create...,即可补全。

或者执行javah命令,先生成java native方法对应的JNI静态注册本地方法声明,然后再定义,也可以,这个方法这里不介绍了,网上比较多。
? ? ? ? 通过AS自动生成的JNI静态注册本地方法如下:

extern "C"
JNIEXPORT jint JNICALL
Java_com_boe_jnilearn_LearnJNI_doPlus(JNIEnv *env, jobject thiz, jint a, jint b) {
    return a+b;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_boe_jnilearn_LearnJNI_doMinus(JNIEnv *env, jclass clazz, jint a, jint b) {
    return a-b;
}

会多了JNIEXPORT及JNICALL是JNI的两个宏,上一章介绍过,前者说明该本地方法是可见的,能被java调用,可以不加,不加默认是对外可见;JNICALL只用于说明这是一个jni方法,用于区别普通本地方法的标识,可以不加

2)动态注册

a.概念:手动建立JNI本地方法与java native方法的映射关系并注册给JNI。
b.示例:

//1 首先定义JNI本地函数,名字随便取
extern "C"
jint do_plus(JNIEnv *env, jobject thiz, jint a, jint b) {
    return a+b;
}
extern "C"
jint do_minus(JNIEnv *env, jclass clazz, jint a, jint b) {
    return a-b;
}

//2 构建映射结构体数组,一个元素就是一个JNI本地方法与java native方法的映射
JNINativeMethod methodMap[] = {
        {"doPlus","(II)I",(void *) do_plus},
        {"doMinus","(II)I",(void *) do_minus}
};

//3 定义动态注册方法,在其中调用JNI的RegisterNatives()方法
int registNativeMethod(JNIEnv *env) {
    //获取java native方法所在类的class实例
    jclass class_LearnJNI = env->FindClass("com/boe/jnilearn/LearnJNI");
    //将映射关系注册给JNI,成功返回JNI_OK
    int result = env->RegisterNatives(class_LearnJNI, methodMap,
                                      sizeof(methodMap) / sizeof(methodMap[0]));
    if(result != JNI_OK){
        result = -1;
    }
    return result;
}

//4 定义jni.h中声明的JNI_OnLoad函数,并在该函数中调用注册逻辑。
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    int result = -1;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) == JNI_OK) {
        if (registNativeMethod(env) == JNI_OK) {
            result = JNI_VERSION_1_6;
        }
        return result;
    }
}

c.说明:
????????●?
JNINativeMethod?结构体用于存储java native方法与JNI本地方法的映射关系,其包括? 三个成员,分别为:java native方法名字;java native方法签名;JNI本地方法指针。使用时,JNI本地方法指针前需要加上(void *),是固定的,不要修改。
????????●?RegisterNatives() 方法的参数分别为:java native方法所在类的class实例;映射关系数组;映射关系个数。返回值为注册结果,成功返回JNI_OK。
????????JNI_OnLoad() :调用System.loadLibrary()加载so库时,如果so库中定义了该方法,会回调它,在该函数中获取JavaVM实例,从而获取JNIEnv实例,在其中调用注册逻辑,完成动态注册。该方法返回值为so库需要的JNI版本,可选值为JNI_VERSION_1_1,JNI_VERSION_1_2,JNI_VERSION_1_4,JNI_VERSION_1_6,如果返回的JNI版本虚拟机不能识别,则无法加载so库如果so库中不定义该方法。则so库默认使用JNI 1.1版本。

3)对比

静态注册动态注册
优点

代码少,使用简单

AS可以自动完成静态注册

代码多,使用稍显复杂

只能自己手动完成

缺点

JNI本地方法名字太长且固定

方法查找效率低

JNI本地方法名字随便取

方法查找效率高

(3)JNI层调用本地层函数,并编译so

? ? ? ? 我们创建两个本地层方法供JNI方法调用,实现JNI层和本地层的交互,并将JNI层和本地层编译成so,供java加载。在cpp目录下创建prebuilt目录,并在其中创建include、lib、source三个目录,include用于放置本地层头文件,source用于放置本地层源码,lib用于放置本地库(暂时不用),如下图:

my_math.h内容为:

int minus();
int plus(int a,int b);

my_math.cpp内容为:

#include <my_math.h>

int minus(int a, int b){
    return a - b;
}
int plus(int a,int b){
    return a + b;
}

在JNI层调用本地层函数,learn_jni.cpp即为JNI层具体实现,文件内容为:

#include <jni.h>
#include <string>
//引用本地层头文件
#include <my_math.h>

//静态注册
extern "C"
jint Java_com_boe_jnilearn_LearnJNI_doPlus(JNIEnv *env, jobject thiz, jint a, jint b) {
    //JNI层调用本地层函数
    return minus(a,b);
}
extern "C"
jint Java_com_boe_jnilearn_LearnJNI_doMinus(JNIEnv *env, jclass clazz, jint a, jint b) {
    //JNI层调用本地层函数
    return plus(a,b);
}

CMakeLists.txt文件内容为:

#指定cmake版本
cmake_minimum_required(VERSION 3.4.1)
#指定头文件路径
include_directories(./prebuilt/include)
#指定源文件
file(GLOB src_list
        ./*.cpp
        ./prebuilt/source/*.cpp)
#编译成动态库,名字为 native-lib
add_library(native-lib SHARED ${src_list})
#查找log系统库绝对路径
find_library(log-lib log)
#依赖log库
target_link_libraries(native-lib ${log-lib})

编译一下工程生成apk,双击apk,会发现gradle不仅帮我们编译好了so,还帮我们将so打到了apk中,不用我们手动将so放到jniLibs下了:

编译一下工程,生成so,在如下目录:

?(4)在java中加载so并调用

? ? ? ? 在LearnJni.java中添加加载so代码:

public class LearnJNI {
    static {
        //加载so名字,参数为libxxx.so的名字 xxx
        System.loadLibrary("native-lib");
    }
    public native int doPlus(int a,int b);
    public static native int doMinus(int a, int b);
}

? ? ? ? ?加载so库的逻辑一般放在静态代码块中,通过System.loadLibrary()方法加载,该方法的参数为so库名字。值得注意的是,当System.loadLibrary()方法刚开始加载so时,就会立即回调so中的JNI_OnLoad()函数,可在该函数中动态注册、持久化JavaVM实例等,JNI_OnLoad函数会返回so想用的JNI版本,若JNI版本没有被java虚拟机识别,则虚拟机不会加载so。在MainActivity中调用java native方法完成java层对本地层的调用:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.e("yy","" + LearnJNI.doMinus(1,2));
    }
}

? ? ? ? ?至此已完成java方法通过JNI调用本地函数的开发,运行下工程即可。细心的朋友会发现,整个过程中我们并没有手动为工程添加so依赖,即将so放到jniLibs中,这是因为gradle已经为我们自动将so添加到了apk中,双击apk,你就看到了:

? ? ? ? 至于为啥gradle会为我们自动添加了so依赖,是因在build.gradle中添加了externalNativeBuild:

apply plugin: 'com.android.application'

android {
......
    defaultConfig {
......
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }
......
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }
    ndkVersion '21.4.7075529'
}

dependencies {
    ......
}

? ? ? ? ?好了分享就到这里,如有问题请告知,希望大家点个赞支持一下!!!

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-05-01 15:53:47  更:2022-05-01 15:54:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 23:25:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码