一:源代码
native-lib.cpp
#include "native-lib.h"
JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_add(JNIEnv* env, jobject, jint a, jint b) {
return a + b;
}
JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_subtract(JNIEnv* env, jobject, jint a, jint b) {
return a - b + 3;
}
JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_multiply(JNIEnv* env, jobject, jint a, jint b) {
return a * b + 3;
}
JNIEXPORT jdouble JNICALL
Java_com_example_jnidemo_MainActivity_divide(JNIEnv* env, jobject, jint a, jint b) {
if (b == 0) return 0;
return static_cast<jdouble>(a) / static_cast<jdouble>(b);
}
native-lib.h
#ifndef NATIVE_LIB_H
#define NATIVE_LIB_H
#include <jni.h>
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jint JNICALL Java_com_example_jnidemo_MainActivity_add(JNIEnv* env, jobject, jint a, jint b);
JNIEXPORT jint JNICALL Java_com_example_jnidemo_MainActivity_subtract(JNIEnv* env, jobject, jint a, jint b);
JNIEXPORT jint JNICALL Java_com_example_jnidemo_MainActivity_multiply(JNIEnv* env, jobject, jint a, jint b);
JNIEXPORT jdouble JNICALL Java_com_example_jnidemo_MainActivity_divide(JNIEnv* env, jobject, jint a, jint b);
#ifdef __cplusplus
}
#endif
#endif // NATIVE_LIB_H
MainActivity
package com.example.jnidemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
private native int add(int a, int b);
private native int subtract(int a, int b);
private native int multiply(int a, int b);
private native double divide(int a, int b);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int a = 10;
int b = 5;
TextView tv = findViewById(R.id.sample_text);
StringBuilder sb = new StringBuilder();
sb.append("Add: ").append(add(a, b)).append("\n");
sb.append("Subtract: ").append(subtract(a, b)).append("\n");
sb.append("Multiply: ").append(multiply(a, b)).append("\n");
sb.append("Divide: ").append(divide(a, b)).append("\n");
tv.setText(sb.toString());
}
}
CMakeLists.txt
尤其注意下面的第一行camke的版本要与settings中的SDK TOOL中的保持一致:cmake_minimum_required(VERSION 3.10.2)
# 设置CMake的最低版本要求
cmake_minimum_required(VERSION 3.10.2)
# 设置项目名称
project("native-lib")
# 添加共享库
# add_library函数用来定义库的名称、类型和源文件
add_library(
# 设置库的名称
native-lib
# 设置库的类型为共享库
SHARED
# 提供库的源文件路径
E:/a_own_codes/android_java/JNIdemo/app/src/main/cpp/native-lib.cpp)
# find_library函数用于查找系统中已存在的库,并设置其路径
find_library(
# 设置路径变量的名称
log-lib
# 指定要查找的NDK库的名称
log
)
# target_link_libraries函数用于将目标库与其依赖库链接在一起
target_link_libraries(
# 指定目标库
native-lib
# 将目标库链接到NDK中包含的log库
${log-lib}
)
代码结构
二:关于NDK工具
2.1 AS中配置
2.2 环境变量配置
2.3验证安装
三:为什么使用JNI
JNI
JNI 全程:JNI(Java Native Interface),通俗翻译:Java本地方法
官方说法:提供一种Java字节码调用C/C++的解决方案,JNI描述的是一种技术。所以这里的Nativie的本地的意思就是C/C++,所以JNI通俗理解就是Java调用C/C++的方案技术。
NDK
NDK(Native Development Kit),通俗翻译:本地发展(扩展)工具
Android NDK 是一组允许您将 C 或 C++(“原生代码”)嵌入到 Android 应用中的工具,NDK描述的是工具集。
同样,把这里的Native理解成C/C++,那么NDK的简单理解就是能把C/C++编译成Java识别的工具模块。Android Studio中已经集成了NDK,所以才可以在Java代码中很方便就可以调用到C/C++的代码。网上有些示例是用NDK命令来编译cpp生成so,并调用测试,这里不做介绍。
Jni的开发背景:
需要调用Java语言不支持的依赖于操作系统平台特性的一些功能,比如
● 需要调用当前UNIX系统的某个功能,而Java不支持这个功能的时候,就要用到JNI
● 在程序对时间敏感或对性能要求特别高时,有必要用到更底层的语言来提高运行效率
● 音视频开发涉及到的音视频编解码需要更快的处理速度,这就需要用到JNI
● 为了整合一些以前的非Java语言开发的系统
● 需要用到早期实现的C/C++语言开发的一些功能或者系统,将这些功能整合到当前的系统或者新的版本中
其实就是为了调用C/C++代码
JNI是完善Java的一个重要功能,它让Java更加全面、封装了各个平台的差异性
JNI在 Android 开发里的主要应用场景:
● 音视频开发
● 热修复
● 插件化
● 逆向开发
● 等等…
这些都是比较复杂的模块,很多实现Java代码无法实现或者使用Java代码实现会很低效的情况。所以这些模块开发的就要提前掌握Jni相关技术才能实现复杂功能。其实这些概念,看一百遍也是很容易忘记的,只要记住一点就行了:
JNI就是Java为了调用C语言的技术,其中过程用到了NDK相关工具。
java-jni-c/c++ 关系图也是比较简单明了的:
Jni基础很简单,比如:Java 代码中加载so库,定义native方法,jni代码中执行简单的实现,相信很多人都是会的;
Jni的进阶知识:jni添加日志,复制对象的调用,C++调用Java方法,Jni方法的动态注册和静态注册,Jni报错分析等等,这些都是有一定的难度的,经过一定的学习了解就可以掌握了。
这些Jni相关知识的学习,不需要系统源码环境,只需要电脑安装Android Studio,安装模拟器或者有安卓真机调试验证就可以了。JNI在系统源码环境中也是有很多相关的代码和使用场景,如果是入门学习,优先使用Android Stduio 创建的项目会好入手很多。
原文链接:https://blog.csdn.net/wenzhi20102321/article/details/136291126
四:文件名称路径以及方法名的关系
在JNI(Java Native Interface)中,Java中的本地方法和C/C++中的实现方法之间通过特定的命名约定进行映射。这个命名约定由JNI定义,确保Java虚拟机能够正确找到并调用对应的本地实现方法。具体来说,这种映射关系由以下规则构成:
JNI 方法命名约定
- 前缀:所有的本地方法在C/C++实现中都以
Java_
开头。 - 包名:将包名中的点号(
.
)替换为下划线(_
)。 - 类名:直接使用类名。
- 方法名:直接使用方法名。
- 参数类型(可选):如果方法名相同且参数不同,参数类型也需要编码在方法名中。
例如,Java类中的方法签名如下:
package com.example.jnidemo;
public class Calculator {
static {
System.loadLibrary("calculator");
}
public native int add(int a, int b);
}
根据命名约定,这个方法在C文件中的实现应该是:
#include <jni.h>
#include "com_example_jnidemo_Calculator.h"
JNIEXPORT jint JNICALL Java_com_example_jnidemo_Calculator_add(JNIEnv *env, jobject obj, jint a, jint b) {
return a + b;
}
命名规则详细解释
Java_
: 前缀,所有JNI方法必须以这个前缀开头。com_example_jnidemo
: 包名,将包名中的.
替换为_
。Calculator
: 类名。add
: 方法名。
使用过程中如何映射
-
加载共享库:
static {
System.loadLibrary("calculator");
}
这行代码告诉Java虚拟机在加载类时加载名为 libcalculator.so
的共享库。System.loadLibrary("calculator")
会寻找 libcalculator.so
文件并将其加载到进程的地址空间中。
2.声明本地方法:
public native int add(int a, int b);
声明本地方法时,并没有提供实现,表示这个方法将在本地代码中实现。
3.调用本地方法:
当Java代码中调用 add
方法时,JNI机制会查找对应的本地方法实现。根据命名约定,Java虚拟机会寻找名为 Java_com_example_jnidemo_Calculator_add
的C函数。
4.映射过程:
Java虚拟机加载 libcalculator.so 文件时,会建立共享库中的符号表。当调用 add 方法时,Java虚拟机根据命名约定查找符号表中的 Java_com_example_jnidemo_Calculator_add 函数地址。找到地址后,执行对应的C函数,实现本地方法调用。
详细方法定义与调用以及方法体的代码见第一节
五:.so的位置
注意事项
*/01/*
原来源文件的路径写的是相对路径:
src/main/cpp/native-lib.cpp
报错:could not find souce file
# 提供库的源文件路径:
E:/a_own_codes/android_java/JNIdemo/app/src/main/cpp/native-lib.cpp
*/02/*
Invalidate Caches / Restart:清除所有缓存,并重启 IDE,适合解决缓存问题和一些难以解决的错误。
Just Restart:仅重启 IDE,不清除缓存,适用于普通的重启需求
*/03/*
[CXX5106] NDK was located by using ndk.dir property. This method is deprecated and will be removed in a future release. Please delete ndk.dir from local.properties and set android.ndkVersion to [21.4.7075529] in all native modules in the project. https://developer.android.com/r/studio-ui/ndk-dir
错误信息 [CXX5106] NDK was located by using ndk.dir property. This method is deprecated and will be removed in a future release. 表示使用了已弃用的方式来指定NDK路径。您需要更新配置,将ndk.dir从 local.properties 文件中移除,并使用 android.ndkVersion 配置(见上面的build.gradle文件中的ndkVersion '21.4.7075529')。
*/04/*
在Android Studio中,先点击“Clean Project”,点击“File” -> “Sync Project with Gradle Files”来同步项目,然后点击“Build” -> “Make Project”来构建项目。