这是一篇让你少走弯路的 JNI/NDK 实例教程

2024-06-07 20:18

本文主要是介绍这是一篇让你少走弯路的 JNI/NDK 实例教程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

作者: 夏至 欢迎转载,但保留这段申明
http://blog.csdn.net/u011418943/article/details/79449108

关于 JNI 的基础就不多说了,这篇文章主要讲解如何在 AS 中用 ndk-build 和 用 cmake 去构建我们的 JNI 工程,并总结他们的特点以及优缺点。

本文代码链接:https://github.com/LillteZheng/JniDemo.git

通过这篇文章,你讲学习到:

  • 用 AS 构建自己的 JNI 工程
  • 学会使用 mk 去加载自己的 so 文件
  • 学会调用第三方 so 或 .a 的方法 (工程提供测试的 so )
  • 学会使用 camke,体验丝般顺滑的 C/C++ 编写体验
  • 3.5.1 as 之后的变化

一、ndk-build

先用传统的方式,即 ndk-build 的方式
首先,新建一个工程,配置 ndk 的环境:
这里写图片描述

然后,新建一个工程,在 gradle.properties 中,添加如下:
android.useDeprecatedNdk=true
这里写图片描述

接着,先使用 AS 自带的功能,在 module 中的 build.gradle 添加 so 库的名字:

这里写图片描述

新建一个类,用来生成 native 方法:

public class JniUtils {static {System.loadLibrary("JNIDemo");}public static native String getName();
}

接着,就是生成 class 文件了,先 build module 一下
(如果嫌麻烦,可以跳到快捷设置,不用写这么麻烦,不过我建议你还是操作一遍)

打开 cmd,或者用 as 的 Terminal ,这里用cmd演示,去到你的工程路径下,生成我们需要的 .h 文件 :
这里写图片描述

首先,我们需要设置 src 的根路径 ,如果不先设置根路径,一般会提示找不到类,用 set classpath 的命令,指向你的 java 文件:

这里写图片描述

然后,再使用 javah 去生成 .h 文件,即上面的 JniUtils:

这里写图片描述

就可以看到生成了 .h 文件,如下图:

这里写图片描述

接着,我们新建一个 jni 的文件夹:
这里写图片描述

把 .h 文件复制过去,然后复制多一份 .h 文件,后缀名改为.cpp ,如下:

#ifdef __cplusplus
#endif
#include <jni.h>
extern "C"
JNIEXPORT jstring JNICALL Java_com_zhengsr_jnidemo_JniUtils_getName(JNIEnv *env, jobject obj) {return env->NewStringUTF("这是个 jni 测试");
}

make module 一下,会发现,已经生成了 so 库:

这里写图片描述

最后再 MainActivity 中调用即可看到效果。

1.1、配置快捷方式

如果每次都这样,想想都觉得崩溃,这个时候,我们就可以配置快捷方式,这样就不用每次都开终端去输入,怎么配置呢?

去到 Setting 选择 external tools ,新建一个 ,命名为 javah,(忽略我配置的 ndk_build,后面会用到):
这里写图片描述

配置以下参数:

这里写图片描述

  • program 为要执行的命令
  • parameters ,先设置路径,然后就是把命令敲一遍,注意是 /src/main/jni ,如果你的路径不一样,记得修改
  • working directory 是 .h 的生成路径

然后在你的 jni 类中,按住右键:

这里写图片描述

之后会弹出一个弹窗,可以自己输入 .h 的名字 (ps:先把以前的去掉):

这里写图片描述

效果如下:

这里写图片描述

接下来的步骤,就跟上面的差不多了,这里就不赘述了。

1.2、编写自己的 mk

上面已经说过,我们并没有 mk 的文件,这是因为 as 用了自身的mk,如果我们需要引入第三方的so或者.a,或者需要特殊配置时,就需要编写自己的 mk 文件了。
关于 mk 的学习,可以参考这篇文章 (写得还不错),这里就不多说了:
http://blog.csdn.net/mynameishuangshuai/article/details/52577228

回到 build.gradle ,先把上面的 ndk 的属性去掉,然后添加:

这里写图片描述

在 jni 路径,添加 Android.mk 和 application.mk :

这里写图片描述

首先,先编写 Android.mk :

#设置路径
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)LOCAL_MODULE := jniutils
LOCAL_SRC_FILES := jniutils.cppinclude $(BUILD_SHARED_LIBRARY)

可以看到,我们把 jni 的 so 的名字改成了 jniutils,用于区别,记得改 JniUtils 中 loadLibrary 的名字,不然报错了,别怪我没提醒;

Application.mk 则如下:

APP_ABI:=all

指定生成所有平台下的 so。

由于我们使用了 mk 编译了,as 并不知道,我们要像刚才配置 javah 那样,配置一下 ndk-build ,配置信息如下:

这里写图片描述

参数已经解释过了,然后在 jni 的文件夹上右键,编译一下:

这里写图片描述

可以看到,生成的 so 包如下:

这里写图片描述

这样,我们就完成了我们的编译了,run 一下,就可以看到你想要的结果了。

1.3、在 build.gradle 中配置编译

从上面中,我们可以看到,如果改动了 .cpp 的方法,每次都要 ndk-build 一下,其实是很烦的;
所以我们可以在 build.gradle 中,添加任务,在每次 run 的时候,自动编译。

build 应该这样配置:

这里写图片描述

完整 build.gradle 文件如下:

apply plugin: 'com.android.application'android {compileSdkVersion 26buildToolsVersion "26.0.2"defaultConfig {applicationId "com.zhengsr.jnidemo"minSdkVersion 19targetSdkVersion 26versionCode 1versionName "1.0"testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}sourceSets {main{jni.srcDirs=[]; //禁用as自动生成mkjniLibs.srcDirs 'src/main/jniLibs' //这里设置 so 生成的位置}}//设置编译任务,编译ndkBuildtasks.withType(JavaCompile) {compileTask -> compileTask.dependsOn 'ndkBuild'}
}
task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {//应该都看得明白,就不解释了commandLine "C:\\Users\\Administrator\\AppData\\Local\\Android\\Sdk\\ndk-bundle\\ndk-build.cmd",'NDK_PROJECT_PATH=build/intermediates/ndk','NDK_LIBS_OUT=src/main/jniLibs','APP_BUILD_SCRIPT=src/main/jni/Android.mk','NDK_APPLICATION_MK=src/main/jni/Application.mk'
}....

接下来,我们在 jniutils.cpp 中,把返回的字符串改一下:

这里写图片描述

直接run,可以看到效果:
这里写图片描述

1.4、引入第三方 so,.a 包

很多时候,像一些比较涉及加密或者核心代码,都是用 so 库来实现,java 只要编写对应的 jni 即可,这里就涉及到引入第三方包的问题,怎么写呢?
首先,我们需要有个第三方的 so 库,这里我从网上下载了一个,下载地址在 github 的demo 中;目录如下:
这里写图片描述

在引入第三方 so 库的时候,需要特别注意的是,这个 so 你要选择好版本,如果你的 so 是32的,而你在 appliaction.mk 的API版本中,选择了 all 或者 arm64-v8a等,那么编译肯定是报错的;
一般手机是 armeabi ,模拟器是 x86 ,机顶盒等板子是 arm64-v8a 的, 我的模拟器刚好是 x86_64 的,所以,这里引入的 so 库是 x86_64 下的,导入之后,目录如下:
这里写图片描述

重新编写 mk 文件:

LOCAL_PATH := $(call my-dir)
#引入第三方 so 
include $(CLEAR_VARS)
LOCAL_MODULE    := vvw
#这里的so名字叫做 vvw,规则是lib 与 so 之间的名字,在加载时使用 vvw,如果是
# libvvw1.0.so,则在 loadlibaray 用 "vvw1.0",module 名字只是给下面加载的
LOCAL_SRC_FILES := libvvw.so
LOCAL_EXPORT_C_INCLUDES := include
include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)
LOCAL_MODULE    := jniutils
LOCAL_SRC_FILES := jniutils.cpp
LOCAL_LDLIBS :=-llog#引入第三方编译模块
LOCAL_SHARED_LIBRARIES := \
vvwinclude $(BUILD_SHARED_LIBRARY)

与前面相比,多了一个第三方模块的引入。接着,我们要指定 application.mk 的 API:

#模拟器是 x86_64 的
APP_ABI := x86_64

如果导入的工程报错,可以试着 APP_ABI 为 x86 ,替换相应的 so 。
接着,我们在 java 类这里,添加一个 调用 so 方法的 java 方法 getIntValue :

public class JniUtils {static {System.loadLibrary("jniutils");System.loadLibrary("vvw");}public static native String getName();public static native int getIntValue(int a,int b);
}

JniUtils.cpp 的代码如下:

#include <jni.h>
#include <string>
#include "include/vvwUtils.h"extern "C" jstring Java_com_zhengsr_jnidemo_JniUtils_getName(JNIEnv* env,jobject /* this */) {return env->NewStringUTF("获取两数字之和:");
}extern "C" jint Java_com_zhengsr_jnidemo_getIntValue(JNIEnv* env,jobject obj,jint a,jint b) {# addMethod 为 libvvw.so 的方法return addMethod(a,b);
}

修改一下 MainActivity.java

这里写图片描述

效果如下;
这里写图片描述

二、使用 cmake 的方式

上面的 demo 中,写 c/c++ 的时候,并没有任何提示,这真的是让人崩溃啊,写了都不知道写对了没有。所以,在 as 2.2.2 之后,as 就支持用 cmake 的方式去编写 jni 了,而使用 camke,除了 c/c++ 有提示之外,在 jni 的配置上,也更加的人性化,如果是新建项目,我是推荐你用 camke 的构建方式去编写。
官方中文文档如下
https://developer.android.google.cn/studio/projects/add-native-code.html

首先,在新建工程的时候,勾选上 c++ support ( 3.0 往下拉才有)

这里写图片描述

一路 next ,然后有两个提示框:

这里写图片描述
这两个也勾选上,解释如下:

  • Exceptions Support:如果您希望启用对 C++ 异常处理的支持,请选中此复选框。如果启用此复选框,Android Studio 会将 -fexceptions 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。
  • Runtime Type Information Support:如果您希望支持 RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。

工程已经给了我们一个 jni 的例子,而它的编译方式就是通过 CMakeLists.txt 来构建的。
下面是对 CMakeLists.txt 的解释,由于篇幅,这里会删掉一些注释:

cmake_minimum_required(VERSION 3.4.1)
#这里会把  native-lib.cpp 转换成共享库,并命名为  native-lib
add_library( # 库的名字native-lib# 设置成共享库SHARED# 库的原文件src/main/cpp/native-lib.cpp )#如果需要使用第三方库,则可以使用 find_library 来找到,比如这里的 log 这个库
find_library( # so库的变量路径名字,在关联的时候是使用log-lib#你需要关联的so名字log )#因为使用了第三方库,所以,这里我们通过 link 这这个库添加进来
target_link_libraries( # 关联的so的路径变量名native-lib#把上面的 log 中的关联的变量名 log-lib 添加进来即可${log-lib} )

如果要添加库,则使用 add_library,括号以空格区分,如果要使用第三方库,比如打印的 log 这个库,就通过 find_library 的方式添加,最后通过 target_link_libraries 把源文件的库,和第三方的库变量名引进来,注意第三方库是个路径变量名,所以 ${}的方式引用。

相较传统配置,如果对 mk 不熟悉的小伙伴,估计会很喜欢 cmake 的方式.

2.1 用 cmake 写 jni

按照上面的方式,新建 JniUtils.java 这个类:

public class JniUtils {static {System.loadLibrary("jniutils");}public static native String getName();
}

然后编写,jniutils.cpp,你会惊喜地发现,竟然有提示!!

#include <jni.h>
#include <string>
extern "C"
jstring
Java_com_zhengsr_jnidemo_camke_JniUtils_getName(JNIEnv* env,jobject /* this */) {std::string hello = "这是使用 camke 的编译方式啦";return env->NewStringUTF(hello.c_str());
}

接下来就是 用 add_library 的方式,我们把 jniutils 加进来:

这里写图片描述

同步一下即可,修改一下 mainactivity,运行,效果如下:

这里写图片描述

可以看到,使用 cmake 的方式,除了有代码提示,在添加类上,简直不能太方便了。

2.2、引入第三方 so 库

官方推荐,每次库变动之前,先 clean project 一下,所以,先clean 一下,免得出现找不到 so 的情况;
接着,我们添加一下第三方so,还是上面的 libvvw.so ,目录如下:

这里写图片描述

接着,我们需要制定一下 ndk 编译时的 类型,不然会增加一个 mips 的类型,这个是编不过的。

这里写图片描述

接着,则是配置最重要的 CMakeLists.txt 了 (as最新版的配置有所不同,参考2.3),具体如下:

cmake_minimum_required(VERSION 3.4.1)find_library( # Sets the name of the path variable.log-lib# Specifies the name of the NDK library that# you want CMake to locate.log )#导入第三方so包,并声明为 IMPORTED 属性,指明只是想把 so 导入到项目中
add_library( vvwSHAREDIMPORTED )
#指明 so 库的路径,CMAKE_SOURCE_DIR 表示 CMakeLists.txt 的路径
set_target_properties( vvwPROPERTIES IMPORTED_LOCATION${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libvvw.so )#指明头文件路径,不然会提示找不到 so 的方法
include_directories(src/main/cpp/include/ )add_library(jniutils SHARED src/main/cpp/jniutils.cpp)target_link_libraries( # Specifies the target library.jniutils#关联第三方 sovvw${log-lib} )

注释已经写得很清楚了,关键是要写对 so 的路径,不然会提示 missing and no rules to make 等错误;
jniutils.cpp 的代码如下:

#include <jni.h>
#include <string>
#include "include/vvwUtils.h"extern "C" jstring Java_com_zhengsr_jnidemo_1camke_JniUtils_getName(JNIEnv* env,jobject /* this */) {std::string hello = "这是使用 camke 的编译方式啦,还获取到两数之和啦: ";return env->NewStringUTF(hello.c_str());
}extern "C" jint Java_com_zhengsr_jnidemo_1camke_JniUtils_getIntValue(JNIEnv* env,jobject obj,jint a,jint b) {return addMethod(a,b);
}

效果如下:
这里写图片描述

2.3 3.5.1 之后的配置 (2020/06/08更新)

最新版的 CMakeLists.txt 放在了 cpp 的目录下,如果按照上面的路径去配置,肯定也会报 missing and no rules to make ,因为 so 的路径配置错了。
因此,我们把 jniLibs 放到 cpp 下,如:
在这里插入图片描述
后面,我们的 CMakeLists.txt 修改如下:

cmake_minimum_required(VERSION 3.4.1)# 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# 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.find_library( # Sets the name of the path variable.log-lib# Specifies the name of the NDK library that# you want CMake to locate.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.#导入第三方so包,并声明为 IMPORTED 属性,指明只是想把 so 导入到项目中
add_library( vvwSHAREDIMPORTED )
#指明 so 库的路径,CMAKE_SOURCE_DIR 表示 CMakeLists.txt 的路径
set_target_properties( # Specifies the target library.vvw# Specifies the parameter you want to define.PROPERTIES IMPORTED_LOCATION# Provides the path to the library you want to import.${CMAKE_SOURCE_DIR}/jniLibs/${ANDROID_ABI}/libvvw.so )#指明头文件路径,不然会提示找不到 so 的方法
include_directories( ${CMAKE_SOURCE_DIR}/myInclude/ )target_link_libraries( # Specifies the target library.native-lib#关联自己的库vvw# Links the target library to the log library# included in the NDK.${log-lib})

三、总结

不管是 ndk-build 传统的方式,还是 cmake 的方式,都有一定的可取之处,当然,在我看来, cmake 无论在学习成本还是代码编写提示上都要优于 ndk-build。
如果是新建项目,我建议还是用 cmake 的方式,毕竟只 c/c++ 有提示这一点,我相信你也拒绝不了的。
当然,实际项目上,还有动态加载 so 的方法,这里就不深入了,这里就当做个 入门介绍吧。

这篇关于这是一篇让你少走弯路的 JNI/NDK 实例教程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1040188

相关文章

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

CSP-J基础之数学基础 初等数论 一篇搞懂(一)

文章目录 前言声明初等数论是什么初等数论历史1. **古代时期**2. **中世纪时期**3. **文艺复兴与近代**4. **现代时期** 整数的整除性约数什么样的整数除什么样的整数才能得到整数?条件:举例说明:一般化: 判断两个数能否被整除 因数与倍数质数与复合数使用开根号法判定质数哥德巴赫猜想最大公因数与辗转相除法计算最大公因数的常用方法:举几个例子:例子 1: 计算 12 和 18

CSP-J基础之数学基础 初等数论 一篇搞懂(二)

文章目录 前言算术基本定理简介什么是质数?举个简单例子:重要的结论:算术基本定理公式解释:举例: 算术基本定理的求法如何找出质因数:举个简单的例子: 重要的步骤:C++实现 同余举个例子:同余的性质简介1. 同余的自反性2. 同余的对称性3. 同余的传递性4. 同余的加法性质5. 同余的乘法性质 推论 总结 前言 在计算机科学和数学中,初等数论是一个重要的基础领域,涉及到整数

【Linux】萌新看过来!一篇文章带你走进Linux世界

🚀个人主页:奋斗的小羊 🚀所属专栏:Linux 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 前言💥1、初识Linux💥1.1 什么是操作系统?💥1.2 各种操作系统对比💥1.3 现代Linux应用💥1.4 Linux常用版本 💥2、Linux 和 Windows 目录结构对比💥2.1 文件系统组织方式💥2.2

Linux平台下利用JNI+双向RMI实现远程推送

一、 前言  作为一种优秀的编程语言,Java在许多方面具有突出的优越性。其中,RMI技术充分展现了Java卓越的分布式计算能力,而JNI技术则体现了Java结合异种编程语言的强大能力。人们常说,RMI是“从Java到Java”,这种说法忽视了这样一个事实:Java可利用JNI技术很容易地与原有系统连接。JNI+RMI的技术解决方案极大地延伸了Java的分布式功能。  本文的写作是基于这样一种实

AI产品经理:ai产品经理从零基础到精通,非常详细收藏我这一篇就够了

在互联网的浪潮中,AI人工智能领域无疑是最引人注目的风口。AI产品经理,作为这一领域的新兴岗位,以其高薪、低压力、无年龄限制等优势,吸引了众多互联网从业者的目光。随着GPT等AIGC工具的兴起,AI产品经理的市场需求日益增长。 AI产品经理需不需要懂算法?🤔‍‍‍ AI产品经理不必像算法工程师那样精通算法,但必须能够与算法工程师有效沟通,了解如何管理AI项目,协调项目资源。 成功转行AI产

王立平--NDK

Android NDK是什么  1、NDK是一系列工具的集合。 NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。 NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、API等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。 NDK可以自动地将so和Java应

Windows编译Hikari-LLVM15[llvm-18.1.8rel]并集成到Android Studio NDK

Windows编译Hikari-LLVM15[llvm-18.1.8rel]并集成到Android Studio NDK 工具1、w64devkit2、ndk3、cmake 编译1、准备工作2、开始编译 集成1、替换文件2、使用 工具 1、w64devkit w64devkit 解压出来给个环境变量 验证一下 2、ndk 通过android studio安装 nd

Android 源码中jni项目 加载so目录小结

Android 源码中jni项目 加载so目录小结 文章目录 Android 源码中jni项目 加载so目录小结一、前言二、so目录验证测试1、jni so文件错误报错(1)报错1 - 未找到so文件:(2)报错2 - so文件中未找到native方法: 2、验证的几种情况(1)apk下面的 lib/arm64/ 放置正确的so文件(2)apk下面的 lib/arm64/ 放置错误的so文

【2024最新】Python入门教程(非常详细)从零基础入门到精通,看完这一篇就够了!

前言 本文罗列了了python零基础入门到精通的详细教程,内容均以知识目录的形式展开。 第一章:python基础之markdown Typora软件下载Typora基本使用Typora补充说明编程与编程语言计算机的本质计算机五大组成部分计算机三大核心硬件操作系统 第二章:编程语言的发展史和第一个Python程序 文件的概念计算机内部数据原理编程语言发展史编程语言的分类python解释器版