Android Studio NDK JNI 编程最小白最简单入门Demo

2023-11-07

Android Studio 编写JNI有两种方式

  1. 通过ndk-build编写,和eclipse类似,需要配置Android.mk、Application.mk文件。之前的一些开源库还是使用此种方式编写.so,因此还是需要了解此种方式。
  2. 通过cmake 编写,Android Studio 2.2(含)之后引入更方便的cmake,需要配置CMakeLists.txt。
    下面我们就通过实例一步步了解这两种方式异同点。

ndk-build

首先下载ndk,可以在单独下载ndk包,解压到本地目录,再将工程里配置ndk路径至解压的目录,或者直接在Android Studio里下载,下载解压成功后,自动配置路径,无需手动配置。Android Studio里下载方式如下:
下载ndk

配置路径:
配置ndk路径

ndk准备好后,接下来开始专注于工程本身。
首先创建一个Android Library Module
创建一个加法器功能的类:CalNum

public class CalNum {
    //暴露给外界的接口
    public float testAdd(float a, float b) {
        return addFloat(a, b);
    }

    //通过jni,调用c/c++ 函数
    private static native float addFloat(float a, float b);
}

上面的addFloat是本地方法,该怎么实现呢?我们知道C语言需要一个头文件(.h)声明函数原型,需要一个源文件(.c)实现函数功能,因此需要创建两个文件。

  1. 创建头文件
    先了解下我们的工程目录结构
TestJni/testnum/src/main/java

TestJni是工程名,testnum是Module名,java目录下存放的是纯java代码。
头文件需要声明addFloat函数,jni函数名比较特殊,通过javah -jni命令生成。
进入Android Studio Terminal,cd 到 java 目录,执行如下命令:

javah -jni com.fish.testnum.CalNum
  • com.fish.testnum 是包名
  • CalNum 是本地方法所在类的类名
  • javah 命令需要配置java jre环境变量
    该命令成功在java目录下生成.h文件
    .h文件
    之前生成的.h文件所在目录是临时的,我们一般会将c/c++文件放入一个特定目录:jni,因此我们需要创建jni目录,右键点击Module:
    创建jni目录
    将之前的.h文件拷贝到jni目录下(生成的目录名为"jni",Android Studio 展示时为"cpp",下面提到的jni等同cpp)。有了.h文件,现在我们来编写.c文件,在jni目录下创建.c文件,右键点击jni:
    创建.c文件
    实现.c文件函数功能:
    .c文件功能
    该函数功能实际就是计算两个数加结果。
    好了,现在已经文成.h和.c文件的编写,那么如何将c文件编写为.so文件呢?这个时候就需要借助Android.mk和Application.mk文件了,这两个文件通过特定语法配置一些参数,这些文件将决定如何生成一个makefile文件,编译器就会依据makefile文件编译c源文件,最终生成.so文件。
    如何编写Android.mk文件呢?在jni目录下新建Android.mk文件:
    Android.mk
  • LOCAL_PATH 指的是当前目录
  • include $(CLEAR_VARS) 指的是清空变量
  • LOCAL_MODULE := calnum 指的是生成.so文件的名称,全称:libcalnum.so
  • LOCAL_SRC_FILES 指的是待编译的源文件
  • include $(BUILD_SHARED_LIBRARY) 指的是生成的库类型,这里是动态库
    Android.mk 还有其它语法参数,这里就不展开说明了。
    如何编写Application.mk文件呢?在jni目录下新建Application.mk文件。
    Application.mk
  • APP_ABI 指的是生成哪些cpu架构支持的.so文件,all表示所有支持的架构,如果只需要生成某一种或几种平台支持的.so,填相应的名字即可,比如x86、armeabi-v7a等。

ps:经测试,这里无论怎么填,都默认生成所有支持平台的.so。

至此,jni目录下的文件已经准备齐全:
jni目录

这时候我们开始make module,然而令人失望的是却是报错,原因是我们仅仅准备了jni相关文件,编译器并不知道如何去操作jni文件,而我们又知道Android.mk记录这编译相关东西,因此应当先让编译器找到Android.mk文件。
在module的build.gradle里,android层级内,指定Android.mk位置:

    externalNativeBuild {
        ndkBuild {
            path "src/main/jni/Android.mk"
        }
    }

这里需要注意的是路径的确定

“src/main/jni/Android.mk”
build.gradle在app 目录下,而Android.mk 在/src/main/jni/ 目录下,因此需要通过上级目录索引到Android.mk

这个时候我们再make module,成功了!那么生成的.so文件在哪呢?首先定位到app build/intermediates 目录下,搜索".so"文件,经过筛选,找到如下目录:

build/intermediates/ndkBuild/debug/obj/local

该目录下文件为:
生成.so
每个目录下有对应平台的.so库,如下:
.so
我们应该注意到了,这里只是生成了4种平台下的.so库,我们明明记得一般是支持7种平台呢?没错,这里确实少了 armeabi,mips,mips64平台,因为在ndk17开始不再支持这三种平台,而我们这里使用的ndk版本是20。那么如果想所有平台都支持呢?那么使用的ndk版本需要低于17(经过测试,ndk16也不行,最好是15及其以下)。

要查看ndk支持的abi,定位到ndk目录下,执行ndk-which命令,即可输出该版本ndk支持的abi。

上面我们说了支持的平台不够多,但是我们还想减少支持的平台数呢,这时候需要在module build.gradle android { {defaultConfig xxx}} 添加如下代码:

ndk {
      // Specifies the ABI configurations of your native
      // libraries Gradle should build and package with your APK.
      abiFilters "x86", "x86_64", "armeabi-v7a",
                   "arm64-v8a"
    }

想要生成哪个平台,填其名字即可。
现在.so文件已经生成,另一个模块如何调用呢?记得我们创建模块的时候是创建了Android Library,也就是说我们module编译成了.jar文件,该.jar文件负责调用.so文件里的函数,并且.jar文件暴露给外界模块接口,外界模块间接调用了.so,整个流程下来就完成了一个最简单的jni编程、实例调用。那具体怎么配置module调用呢?有两种方法:

1、调用者工程内直接依赖Android Library module,优点是方便调试,前提是我们有Android Library module源码
2、 调用者工程内依赖.jar包,现成的第三方库一般以jar包形式提供。

下面分别简要说明两者的配置方式

  • 直接依赖module:
    选择依赖
    选中Module dependency,选择需要依赖的module,确定后,再到调用者的module build.gradle里查看:
    依赖module
    最后一项就是之前界面操作的结果。
  • 间接依赖module:
    先找到jar包,定位到libary module app/build/intermediates 目录下,搜索".jar",
    最后定位到:build/intermediates/packaged-classes/debug 目录下的class.jar,就是我们要找的jar包,可以将之改为比较好记名字,这里改为calnum.jar,将该文件拷贝至调用者module app/lib目录下,再在调用者module里引入该.jar包,依然可以通过界面操作依赖:
    选择依赖
    选中jar dependency,选择需要依赖的jar,确定后,再到调用者的module build.gradle里查看:
    依赖jar
    最后一项就是之前界面操作的结果。
    jar包依赖已经搞定,还有so库呢?也是有两种方式,对应上面和两种依赖jar包方式:
  • 直接依赖module
    这种方式下不用配置so库位置
  • 间接依赖module
    定位到调用者module src/main 目录下,新建文件夹,名为:"jniLibs“,然后将之前生成的各个平台的so库放入该文件夹下:
    jniLibs
    当然,如果不想放在该目录下,也可以和jar包一起放在"libs"文件夹下,前提是需要在build.gradle android 层级下指明so库的位置
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

至此,jar包和so库都准备好了,调用者就可以调用暴露出来的接口进行访问了。

        btnNdk.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                CalNum calNum = new CalNum();
                String toast = calNum.testAdd(20, 30) + " nukbuild";
                Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show();
            }
        });

其中calNum 类就是jar包种的类
jar包里的类

cmake

通过上面ndk-build方式可知,需要我们配置Android.mk Application.mk文件,比较繁琐。Google为此推出了新的编译方式-cmake,那么cmake需要怎么做呢?
首先下载cmake工具
cmake工具
其次,新建project的时候,会有一个c++支持选项,勾选即可。project创建完毕后,会发现比没勾选时多了几个文件:
1、src/main 目录下新建了jni文件夹,并且预先放置了一个cpp文件:
native-lib.cpp
和ndk-build jni目录一致的,当然也可以放.c 和 .h文件。
2、app 目录下多了个CMakeLists.txt,该文件的作用和ndk-build时使用的.mk文件类似。
cmakelists.txt
我们来看看该文件里边的语法:
cmakelists.txt 内容

  • add_library
    native-lib 指的是要生成的库名称
    SHARED 指的是生成的库为动态库
    src/main/cpp/native-lib.cpp 指的是需要编译的源文件
  • find_library 配置的是需要依赖的外部库
  • target_link_libraries 配置的是最终将多个库链接起来
    3、build.gradle 新增了几项配置:
    build.gradle
    配置 cmake 编译参数等。
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }

让编译器知道CMakeLists.txt 位置

    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }

注:第三点是手动新增的,新建的project并没有,编译会报错,加上第三点解决编译报错问题。

至此,cmake方式编译jni配置工作就完成了,只需要简单的勾选就可以支持ndk编程,是不是觉得比之前方便多了。也许你会问,创建工程时忘了添加c++支持,后面有需要编译jni怎么办呢?还是按照上面的方法,手动添加:

1、下载cmake工具
2、新建jni,编写.c/.h 、c++源文件
3、新建CMakeLists.txt,配置其中参数
4、配置build.gradle

ndk-build方式和cmake方式编写简单入门jni程序已经梳理完毕,总结几个比较关键的点:

1、两种方式需要哪些配置文件容易搞混乱(相对来说,具体配置文件语法都可以查得到,反而比较简单)
2、配置时路径容易搞糊涂,实际上只要厘清当前配置文件所在的位置、待指向的配置的文件所在的位置,相对位置就整明白了,进而填上相对索引即可访问

最后,整个效果图:
效果

本文基于Android Studio 3.2 NDK 20

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android Studio NDK JNI 编程最小白最简单入门Demo 的相关文章

随机推荐

  • Unity开发:一个PC项目转WebGL遇到的问题解决汇总

    这段时间遇到一个PC版本发布WebGL的工程 遇到很多的问题 在这里做下记录 1 发布报错 程序中出现DLL调用的情况 删除调用DLL相关代码 发布路径不要出现中文 2 WebGL无法通过IO流读取本地json文件 改成TextAsset绑
  • Web前端:html烟花代码

    代码如下
  • 【c语言】新手初学while循环,for循环的一些想法与思考

    循环语句有三种 while循环 for循环 do while循环 由于只学了前两种 因此只能将两种进行比较 但在比较两者前 我认为新手有必要认真的学习良好的代码习惯 这里我使用的是 高质量 C C 编程指南 尽管年代比较久远但依旧受益良多
  • 一、基础架构

    架构是什么 软件架构指软件系统的顶层结构 主要是由系统是一群关联个体组成 个体可以是模块或者子系统 他们按照某种特定的规则来运作和协作 系统架构的目的 架构设计的主要目的是为了解决软件系统复杂度带来的问题 要有的放矢 不是胡编乱造 这个结论
  • HTML5特效动画

    11 10款造型奇特的CSS3进度条 Loading动画DEMO演示 10款造型奇特的CSS3进度条 Loading动画 今天我们要分享10款造型奇特的CSS3进度条 Loading动画 这10款进度条应用不仅有创意的外观 而且还有非常特别
  • VS Code插件live server的安装和使用

    https blog csdn net sinat 37024730 article details 128902967
  • MS08-067远程代码执行漏洞(CVE-2008-4250)

    MS08 067远程代码执行漏洞 CVE 2008 4250 Windows Server服务RPC请求缓冲区溢出漏洞复现 文章目录 MS08 067远程代码执行漏洞 CVE 2008 4250 Windows Server服务RPC请求缓
  • anaconda使用系列教程--5)安装anaconda环境到指定位置

    背景 anaconda的环境都比较大 如果安装到根目录很快就会把根目录占满 最好能指定新建环境的安装路径 方法 方法就是在conda create命令加上选项 prefix即可 安装虚拟环境到指定路径的命令如下 conda create p
  • 数据库文件加密

    配置MySQL数据库连接 spring datasource driver class name com mysql jdbc Driver spring datasource url jdbc mysql 121 196 xxx xxx
  • STM32自己写printf函数方法(不用重定向)

    stm32有几个串口可以使用 当我们想用printf打印数据到上位机时 通常是使用fput重定向 当我们要多个串口使用printf格式函数时 可以在写一个类似printf的函数 include
  • PyTorch 源码解读之 nn.Module:核心网络模块接口详解

    目录 0 设计 1 nn Module 实现 1 1 常用接口 1 1 1 init 函数 1 1 2 状态的转换 1 1 3 参数的转换或转移 1 1 4 Apply 函数 1 2 属性的增删改查 1 2 1 属性设置 1 2 2 属性删
  • Spring Boot(一):开始使用

    背景 其实Spring Boot很早就使用过 但由于之前未做深入的研究且目前工作中所使用的技术也并未涉及 所以导致对其使用依然不是很熟悉 正是因为用过Spring Boot才懂得它的好用之处 但目前开发的项目使用的技术较为落后 导致开发效率
  • 探究:秒杀系统

    1 秒杀系统的特点 瞬时高并发 2 预防措施 2 1 流量限制 对于一个相同的用户 限制请求的频次 对于一个相同的IP 限制请求的频次 验证码 减缓用户请求的次数 活动开启之前 按钮先置灰 防止无效的请求流入系统 给系统造成冲击 2 2 页
  • LOL-v2数据集和VE-LOL数据集的区别

    LOL v2数据集和VE LOL数据集的区别 LOL v2 LOL v2数据集 64 包括两个不同的子集 即LOL v2 real和LOL v2 synthetic LOL v2 real子集是通过改变ISO和曝光时间在真实场景中捕获的 包
  • git merge&rebase区别

    merge rebase两个分支合并操作 各有利弊 我们先看看表现吧 假如master和feature分支如下 如果我们merge操作 我们看到 合并时候 作为一个新提交作为一个新节点 head指针移动到最新master分支 feature
  • linux定时执行php脚本方法

    网站运营过程中 经常会遇到需要定时执行php脚本的情况 这次介绍linux系统定时执行php脚本方法 crontab crontab的服务进程名为crond 英文意为周期任务 crontab在Linux主要用于周期定时任务管理 通常安装操作
  • 胡立阳:如何判断股价见底

    胡立阳 如何判断股价见底 发表时间 2007年11月28日 17时56分 评论 浏览 248 213844 腾讯证券 最近A股指数在6100点一路下滑 跌破半年线 国内各个媒体众说纷纭 观点不一 都在判断底部到底在哪里 就此问题 腾讯证券连
  • AntD的Table表头title加Icon图标和气泡提示Tooltip

    如图 想在Table表单中的描述后面加上Icon图标和气泡提示Tooltip 第一时间找了AntD文档 找到搜索功能及相应图标 filterDropdown和filterIcon 也在百度上找到https www jianshu com p
  • QT基础(入门)

    对QT文件中的 pro文件解释 QT core gui QT包含的模块 QT中的快捷键 注释 ctr 运行 ctrl r 编译 ctrl b 查找 ctrl f 整行移动 ctrl shift 向上键 向下键 帮助文档 F1 自动对齐 ct
  • Android Studio NDK JNI 编程最小白最简单入门Demo

    Android Studio 编写JNI有两种方式 通过ndk build编写 和eclipse类似 需要配置Android mk Application mk文件 之前的一些开源库还是使用此种方式编写 so 因此还是需要了解此种方式 通过