Android JNI深度解析:从零开始,手把手教你Java如何优雅地调用C/C++代码
文章描述(Meta Description): 本文是Android JNI(Java Native Interface)的终极指南,详细讲解JNI的概念、作用、环境搭建,并通过完整实例演示Java如何调用C/C++ native方法,无论你是想提升性能、复用现有代码,还是深入底层学习,本文都将带你彻底掌握Android JNI开发的核心技术,解决开发中的痛点。

引言:为什么在Android开发中,我们需要JNI?
作为一名Android开发者,我们每天都在使用Java(或Kotlin)构建应用,你是否遇到过以下困境:
- 性能瓶颈: 某些计算密集型任务(如图像处理、物理模拟)在Java层执行效率低下,难以达到性能要求。
- 代码复用: 项目中存在大量经过长期验证、性能优异的C/C++库(如FFmpeg、OpenGL驱动),我们希望在Android中直接复用,而无需用Java重写。
- 访问硬件/系统API: 某些底层的硬件接口或系统级功能,只提供了C/C++的SDK,无法直接在Java层调用。
这时,Android JNI(Java Native Interface) 就闪亮登场了,它是一座桥梁,连接着Java世界和C/C++世界,让我们能够无缝地在两种语言间进行交互,本文将作为你的导航图,带你从理论到实践,全面掌握Java调用C/C++的每一个细节。
JNI是什么?—— 连接Java与C/C++的桥梁
JNI(Java Native Interface) 是一个编程框架,它允许运行在Java虚拟机(JVM)中的Java代码与其它语言(如C、C++、汇编)编写的应用程序进行交互,JNI定义了一套规范和工具,让Java代码可以:
- 调用:调用本地方法(Native Methods),即用C/C++编写的函数。
- 被调用:让C/C++代码反过来调用Java的方法和访问Java的对象。
核心优势:

- 性能优化: 将关键性能代码用C/C++实现,摆脱JVM的性能开销。
- 代码复用: 直接集成现有的C/C++开源库或商业SDK。
- 访问底层: 与操作系统、硬件进行更直接的交互。
开发环境准备:你的Android Studio需要这些“装备”
在开始编码前,请确保你的开发环境已经配置妥当。
-
基础环境:
- 安装并配置好 JDK (Java Development Kit)。
- 安装最新版本的 Android Studio。
- 配置好 Android SDK 和 NDK (Native Development Kit)。NDK是JNI开发的基石,它提供了编译、打包本地代码所需的工具链和库。
-
在Android Studio中配置NDK:
- 打开
Tools->SDK Manager。 - 切换到
SDK Tools标签页。 - 勾选
NDK (Side by side)和CMake(一个跨平台的构建系统,推荐使用)。 - 点击
Apply进行安装。
(小技巧:为了方便,可以在Android Studio的
File->Project Structure->Local Properties中手动指定NDK和CMake的路径,方便项目迁移。)
(图片来源网络,侵删) - 打开
JNI调用全流程实战:一个Hello World级别的完整示例
理论说再多,不如动手写一个,下面,我们将通过一个经典案例,走完从Java声明到C/C++实现的全过程。
步骤1:创建支持C++的Android项目
- 在Android Studio中,选择
File->New->New Project...。 - 选择
Native C++模板(这会自动帮我们配置好CMake和JNI相关的目录结构),然后点击Next。 - 填写项目名称、包名等信息,完成创建。
创建后,你会看到项目结构中多了一个 cpp 目录,这就是我们存放C/C++代码的地方。
步骤2:在Java代码中声明Native方法
打开你的MainActivity.java(或其他任意Java类),声明一个或多个 native 方法。native 关键字告诉编译器,这个方法的实现在另一个本地库中。
package com.example.jnidemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// 1. 声明一个native方法
// 注意:方法名需要遵循特定的命名规则,后面会解释
public native String stringFromJNI();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
// 2. 调用native方法
tv.setText(stringFromJNI());
}
}
步骤3:使用CMake配置构建本地库
打开 app 目录下的 CMakeLists.txt 文件,这个文件告诉CMake如何编译我们的C++代码。
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.18.1)
project("jnidemo")
# 指定C++标准
set(CMAKE_CXX_STANDARD 11)
# 查找Android日志库
find_library(log-lib log)
# 添加你的本地库
# SHARED 表示生成一个动态链接库 (.so文件)
add_library( # Sets the name of the library.
jnidemo # 指定库名,通常与Java中System.loadLibrary("库名")的参数一致
SHARED # 指定库的类型
native-lib.cpp ) # 源文件名
# 将日志库链接到你的本地库
target_link_libraries( # Specifies the target library.
jnidemo
# Links the target library to the log library
# Includes the log library in the linking process.
${log-lib} )
步骤4:在C/C++中实现Native方法
最关键的一步来了,我们需要在 cpp/native-lib.cpp 文件中实现 stringFromJNI 方法。
关键点:JNI方法名规则
Java中声明的 native String stringFromJNI(),在C/C++中的实现函数名必须遵循以下规则:
Java_包名_类名_方法名
Java_: 固定前缀。包名: 将 替换为_。com.example.jnidemo->com_example_jnidemo。类名: Java类的全名。_方法名: Java方法名。
我们的函数名是:Java_com_example_jnidemo_MainActivity_stringFromJNI。
#include <jni.h>
#include <string>
// JNIEXPORT 和 JNICALL 是JNI宏,用于声明函数可以被外部调用
// JNICALL 在不同平台下可能有不同的实现,但通常可以省略
// jobject 是返回类型,对应Java中的Object
// JNIEnv* 是一个指向JNI环境的指针,是JNI函数的入口
// jobject thisObject 指的是调用这个native方法的Java对象实例
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
// 使用JNIEnv指针来操作Java世界
// env->NewStringUTF() 是一个JNI函数,用于将C++字符串创建为Java的String对象
std::string hello = "Hello from C++!";
return env->NewStringUTF(hello.c_str());
}
代码解析:
extern "C":告诉C++编译器,使用C语言的链接方式,因为C++支持函数重载,而C不支持,JNI方法名是固定的,C风格的链接可以确保函数名不被“修饰”(mangled)。JNIEnv* env:这是JNI的“魔法之手”,通过它,你可以在C/C++代码中创建Java对象、调用Java方法、获取/设置Java对象的字段等。jobject thisObject:当native方法是非静态时,这个参数代表调用该方法的Java对象实例,如果是静态native方法,则参数为jclass,代表Java的Class对象。jstring:这是JNI中的基本类型,对应Java中的String,JNI定义了一套自己的类型系统(如jint,jfloat,jobject等)。
步骤5:加载并运行本地库
我们需要在Java代码中加载我们创建的本地库,在 MainActivity 的 onCreate 方法中,调用 System.loadLibrary()。
// 在MainActivity的onCreate方法
