杰瑞科技汇

JNI中Java如何调用Android的C代码?

目录

  1. 什么是 JNI?
  2. 为什么在 Android 中使用 JNI?
  3. JNI 开发完整流程
    • 在 Java/Kotlin 中声明 native 方法
    • 生成 JNI 头文件(.h
    • 实现 C/C++ 方法(.c.cpp
    • 配置 CMake 构建脚本
    • 加载 C/C++ 库并调用
  4. 一个完整的实战示例
    • 创建项目
    • 编写 Java 代码
    • 生成和编写 C++ 代码
    • 配置 CMake
    • 调用与测试
  5. JNI 常用数据类型和方法映射
  6. 进阶与最佳实践
    • 处理字符串 (jstring)
    • 异常处理
    • 使用 C++ (C++风格的调用)
    • 日志输出 (__android_log_print)

什么是 JNI?

JNI (Java Native Interface) 是一个标准的编程接口,它允许运行在 Java 虚拟机 (JVM) 中的 Java 代码与其他语言(主要是 C 和 C++)编写的代码进行交互,它是一座连接 Java 世界和 C/C++ 世界的桥梁。

JNI中Java如何调用Android的C代码?-图1
(图片来源网络,侵删)

为什么在 Android 中使用 JNI?

在 Android 开发中,使用 JNI 主要有以下原因:

  • 性能优化:对于计算密集型任务,如图形处理、物理模拟、音视频编解码等,用 C/C++ 实现比 Java/Kotlin 快得多。
  • 访问硬件/系统 API:Android 系统的一些底层功能(如访问传感器原始数据、自定义驱动等)没有提供 Java API,只能通过 C/C++ 调用。
  • 复用现有库:很多成熟的、高效的库都是用 C/C++ 编写的(OpenCV, FFmpeg),通过 JNI 可以直接在 Android 项目中使用它们,而无需重新编写。
  • 保护代码:核心算法或逻辑放在 C/C++ 中,比在 Java/Kotlin 中更难被反编译和窃取。

JNI 开发完整流程

假设我们已经在 Android Studio 中创建了一个新的项目。

在 Java/Kotlin 中声明 native 方法

在 Java 或 Kotlin 类中声明一个或多个 native 方法,这些方法没有具体的实现,只是一个声明。

// 在 com.example.jnidemo 包下的 JniUtils.java 文件中
package com.example.jnidemo;
public class JniUtils {
    // 1. 声明一个 native 方法
    public native String stringFromJNI();
}

生成 JNI 头文件(.h

头文件包含了 Java native 方法的 C/C++ 函数签名,你可以使用 javacjavah 工具(在旧版 Android Studio 中)或直接使用 Android Studio 的外部工具(推荐)来生成。

JNI中Java如何调用Android的C代码?-图2
(图片来源网络,侵删)

在较新的 Android Studio (CMake/NDK 集成) 中,最简单的方法是:

  1. 将光标放在 native 方法声明上。
  2. Alt + Enter (或 Cmd + Enter on Mac)。
  3. 在弹出的菜单中选择 "Copy C++ Function Signature"
  4. 你会得到类似 Java_com_example_jnidemo_JniUtils_stringFromJNI(JNIEnv *, jobject) 的函数签名,这就是你在 C/C++ 中需要实现的函数名。

或者,你可以手动创建一个 .h 文件并包含以下内容:

// com_example_jnidemo_JniUtils.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jnidemo_JniUtils */
#ifndef _Included_com_example_jnidemo_JniUtils
#define _Included_com_example_jnidemo_JniUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jnidemo_JniUtils
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_jnidemo_JniUtils_stringFromJNI
  (JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

函数名规则Java_ + 包名(用下划线 _ 分隔) + 类名 + **方法名`。

实现 C/C++ 方法(.c.cpp

创建一个 C 或 C++ 源文件(native-lib.cpp),并实现你在头文件中定义的函数。

JNI中Java如何调用Android的C代码?-图3
(图片来源网络,侵删)
// native-lib.cpp
#include <jni.h>
#include <string>
#include <android/log.h> // 用于在 logcat 中打印日志
#define TAG "JniDemo"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
// 包含我们生成的头文件 (可选,但推荐)
// #include "com_example_jnidemo_JniUtils.h"
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_JniUtils_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    // 创建一个 C++ 字符串
    std::string hello = "Hello from C++";
    // 将 C++ 字符串转换为 jstring (Java 字符串)
    // env->NewStringUTF() 是一个 JNI 函数
    return env->NewStringUTF(hello.c_str());
}

配置 CMake 构建脚本

这是最关键的一步,你需要告诉 Android 的构建系统如何编译你的 C/C++ 代码。

  1. app 模块的 build.gradle 文件中,确保 ndkVersionexternalNativeBuild 已配置:

    // app/build.gradle
    android {
        // ... 其他配置
        defaultConfig {
            // ... 其他配置
            externalNativeBuild {
                cmake {
                    cppFlags ""
                    // 可以指定 ABI 架构
                    // abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
                }
            }
            ndkVersion "25.1.8937393" // 使用你安装的 NDK 版本
        }
        // ... 其他配置
        // 指定 CMakeLists.txt 文件的位置
        externalNativeBuild {
            cmake {
                path "src/main/cpp/CMakeLists.txt"
                version "3.18.1" // 使用你安装的 CMake 版本
            }
        }
    }
  2. app/src/main/cpp/ 目录下创建 CMakeLists.txt 文件:

    # CMakeLists.txt
    # 设置 C++ 标准
    cmake_minimum_required(VERSION 3.4.1)
    # 定义你的 native 库
    # SHARED 表示生成一个 .so (共享库) 文件
    add_library(
            native-lib           # 库的名字
            SHARED               # 库的类型
            native-lib.cpp)      # 源文件列表
    # 找到 Android 的 log 库
    find_library(
            log-lib
            log)
    # 将 log 库链接到你的 native-lib
    # 这样你就可以在 C++ 代码中使用 __android_log_print 了
    target_link_libraries(
            native-lib
            ${log-lib})

加载 C/C++ 库并调用

在你的 Java/Kotlin 代码中加载库并调用 native 方法。

// 在你的 Activity 或其他地方
package com.example.jnidemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
    // 加载名为 "native-lib" 的库
    // 这个名字必须和 CMakeLists.txt 中 add_library 的第一个参数一致
    static {
        System.loadLibrary("native-lib");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.sample_text);
        // 调用 native 方法
        String hello = JniUtils.stringFromJNI();
        tv.setText(hello);
        // 测试一下 Toast
        Toast.makeText(this, "Toast from Java!", Toast.LENGTH_SHORT).show();
    }
}

运行你的 App,你应该能在屏幕上看到 "Hello from C++",并且在 Logcat 中能看到通过 __android_log_print 打印的日志。


一个完整的实战示例(传递参数

分享:
扫描分享到社交APP
上一篇
下一篇