20 个 Android JNI + CMake 生产级示例

目录

第一部分:基础入门(1-5)

[示例 1:基础数学运算(Java → C++)](#示例 1:基础数学运算(Java → C++))

[Java 层(MainActivity.java)](#Java 层(MainActivity.java))

[C++ 层(native-lib.cpp)](#C++ 层(native-lib.cpp))

CMakeLists.txt

[示例 2:C++ 打印日志到 Logcat](#示例 2:C++ 打印日志到 Logcat)

[Java 层](#Java 层)

[C++ 层(log_utils.h)](#C++ 层(log_utils.h))

[C++ 层(native-lib.cpp)](#C++ 层(native-lib.cpp))

[示例 3:字符串传递(Java ↔ C++)](#示例 3:字符串传递(Java ↔ C++))

[Java 层](#Java 层)

[C++ 层](#C++ 层)

[示例 4:数组传递与处理](#示例 4:数组传递与处理)

[Java 层](#Java 层)

[C++ 层](#C++ 层)

[示例 5:C++ 回调 Java 方法(C++ → Java)](#示例 5:C++ 回调 Java 方法(C++ → Java))

[Java 层](#Java 层)

[C++ 层](#C++ 层)

第二部分:数据处理与文件操作(6-10)

[示例 6:大文件 MD5 校验](#示例 6:大文件 MD5 校验)

[Java 层](#Java 层)

[C++ 层](#C++ 层)

[CMakeLists.txt(需集成 OpenSSL)](#CMakeLists.txt(需集成 OpenSSL))

[示例 7:SQLite 原生高性能操作](#示例 7:SQLite 原生高性能操作)

[Java 层](#Java 层)

[C++ 层](#C++ 层)

[CMakeLists.txt(直接引入 SQLite 源码)](#CMakeLists.txt(直接引入 SQLite 源码))

[示例 8:ZIP 压缩与解压(支持密码)](#示例 8:ZIP 压缩与解压(支持密码))

[Java 层](#Java 层)

[C++ 层(使用 libzip)](#C++ 层(使用 libzip))

[示例 9:AES-256-CBC 加密解密](#示例 9:AES-256-CBC 加密解密)

[Java 层](#Java 层)

[C++ 层](#C++ 层)

[示例 10:JSON 解析与生成(使用 RapidJSON)](#示例 10:JSON 解析与生成(使用 RapidJSON))

[Java 层](#Java 层)

[C++ 层](#C++ 层)

第三部分:音视频与图形(11-15)

[示例 11:OpenCV 图像处理(灰度化 + 边缘检测)](#示例 11:OpenCV 图像处理(灰度化 + 边缘检测))

[Java 层](#Java 层)

[C++ 层](#C++ 层)

[CMakeLists.txt(集成 OpenCV)](#CMakeLists.txt(集成 OpenCV))

[示例 12:OpenGL ES 实时滤镜](#示例 12:OpenGL ES 实时滤镜)

[Java 层](#Java 层)

[C++ 层](#C++ 层)

[示例 13:FFmpeg 音频解码](#示例 13:FFmpeg 音频解码)

[Java 层](#Java 层)

[C++ 层](#C++ 层)

[示例 14:RTSP 实时流拉取](#示例 14:RTSP 实时流拉取)

[Java 层](#Java 层)

[CMakeLists.txt(集成 FFmpeg)](#CMakeLists.txt(集成 FFmpeg))

[示例 15:MediaCodec 硬件解码](#示例 15:MediaCodec 硬件解码)

[Java 层](#Java 层)

[C++ 层](#C++ 层)

[第四部分:AI 与安全(16-20)](#第四部分:AI 与安全(16-20))

[示例 16:TensorFlow Lite 图像分类](#示例 16:TensorFlow Lite 图像分类)

[Java 层](#Java 层)

[C++ 层](#C++ 层)

[CMakeLists.txt(集成 TFLite)](#CMakeLists.txt(集成 TFLite))

[示例 17:YOLOv8 实时目标检测](#示例 17:YOLOv8 实时目标检测)

[Java 层](#Java 层)

[C++ 层(核心代码)](#C++ 层(核心代码))

[示例 18:SO 库反调试](#示例 18:SO 库反调试)

[Java 层](#Java 层)

[C++ 层](#C++ 层)

[示例 19:应用签名校验(防二次打包)](#示例 19:应用签名校验(防二次打包))

[Java 层](#Java 层)

[C++ 层](#C++ 层)

[示例 20:串口通信(RS232/RS485)](#示例 20:串口通信(RS232/RS485))

[Java 层](#Java 层)

[C++ 层](#C++ 层)

总结


第一部分:基础入门(1-5)

示例 1:基础数学运算(Java → C++)

用途:最简单的 JNI 调用,用于算法加速、数值计算。

Java 层(MainActivity.java)

java

运行

复制代码
package com.example.jnidemo;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    // 1. 加载编译好的 native-lib.so 库
    static {
        System.loadLibrary("native-lib");
    }

    // 2. 声明 native 方法(由 C++ 实现)
    public native int add(int a, int b);
    public native int sub(int a, int b);
    public native int mul(int a, int b);
    public native float div(int a, int b);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        int a = 100;
        int b = 20;

        // 3. 调用 native 方法,和调用普通 Java 方法无区别
        TextView tv = findViewById(R.id.sample_text);
        String result = "计算结果:\n";
        result += a + " + " + b + " = " + add(a, b) + "\n";
        result += a + " - " + b + " = " + sub(a, b) + "\n";
        result += a + " * " + b + " = " + mul(a, b) + "\n";
        result += a + " / " + b + " = " + div(a, b);

        tv.setText(result);
    }
}

C++ 层(native-lib.cpp)

cpp

运行

复制代码
#include <jni.h>

// 必须添加 extern "C",防止 C++ 编译器对函数名进行名称重整
extern "C" {

// 函数命名规范:Java_包名_类名_方法名
// JNIEnv*:JNI 核心指针,提供所有 JNI 操作函数
// jobject:调用该方法的 Java 对象(this)
JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b) {
    return a + b;
}

JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_sub(JNIEnv *env, jobject thiz, jint a, jint b) {
    return a - b;
}

JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_mul(JNIEnv *env, jobject thiz, jint a, jint b) {
    return a * b;
}

JNIEXPORT jfloat JNICALL
Java_com_example_jnidemo_MainActivity_div(JNIEnv *env, jobject thiz, jint a, jint b) {
    if (b == 0) return 0;
    return (float)a / (float)b;
}

} // extern "C"

CMakeLists.txt

cmake

复制代码
# 1. 指定 CMake 最低版本
cmake_minimum_required(VERSION 3.22.1)

# 2. 定义项目名称
project("jnidemo")

# 3. 生成动态库 libnative-lib.so
add_library(
        native-lib
        SHARED
        native-lib.cpp
)

# 4. 查找 Android 系统日志库
find_library(log-lib log)

# 5. 链接日志库到 native-lib
target_link_libraries(
        native-lib
        ${log-lib}
)

关键点

  • 静态注册,函数名必须严格匹配
  • extern "C" 是必须的
  • 基本类型可以直接传递,无需转换

示例 2:C++ 打印日志到 Logcat

用途:JNI 代码调试必备,在 C++ 层打印日志到 Android Studio Logcat。

Java 层

java

运行

复制代码
public native void testLog();

// onCreate 中调用
testLog();

C++ 层(log_utils.h)

cpp

运行

复制代码
#ifndef LOG_UTILS_H
#define LOG_UTILS_H

#include <android/log.h>

// 定义日志标签
#define LOG_TAG "JNI_DEBUG"

// 定义不同级别的日志宏
// __VA_ARGS__ 代表可变参数列表
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

#endif // LOG_UTILS_H

C++ 层(native-lib.cpp)

cpp

运行

复制代码
#include <jni.h>
#include "log_utils.h"

extern "C" JNIEXPORT void JNICALL
Java_com_example_jnidemo_MainActivity_testLog(JNIEnv *env, jobject thiz) {
    int value = 100;
    const char* str = "Hello from C++";

    // 使用日志宏打印
    LOGD("这是调试日志,value=%d", value);
    LOGI("这是信息日志,str=%s", str);
    LOGW("这是警告日志");
    LOGE("这是错误日志,发生了一个错误");
}

关键点

  • 必须链接 log
  • 使用宏定义简化日志调用
  • 在 Logcat 中过滤 JNI_DEBUG 标签即可查看

示例 3:字符串传递(Java ↔ C++)

用途:文本处理、加密解密、数据交换。

Java 层

java

运行

复制代码
public native String stringFromJNI();
public native String appendString(String s1, String s2);
public native int getStringLength(String str);

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include <string>
#include "log_utils.h"

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) {
    // C++ 字符串转 Java 字符串
    std::string hello = "Hello from C++ via JNI";
    return env->NewStringUTF(hello.c_str());
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_MainActivity_appendString(
        JNIEnv *env, jobject thiz, jstring s1, jstring s2) {

    // 1. Java 字符串转 C 字符串
    // GetStringUTFChars 会分配内存,必须用 ReleaseStringUTFChars 释放
    const char *cs1 = env->GetStringUTFChars(s1, nullptr);
    const char *cs2 = env->GetStringUTFChars(s2, nullptr);

    if (cs1 == nullptr || cs2 == nullptr) {
        return nullptr; // 内存分配失败
    }

    // 2. 字符串拼接
    std::string result = std::string(cs1) + " " + std::string(cs2);

    // 3. 释放内存(重要!否则内存泄漏)
    env->ReleaseStringUTFChars(s1, cs1);
    env->ReleaseStringUTFChars(s2, cs2);

    // 4. 返回 Java 字符串
    return env->NewStringUTF(result.c_str());
}

extern "C" JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_getStringLength(JNIEnv *env, jobject thiz, jstring str) {
    const char *cstr = env->GetStringUTFChars(str, nullptr);
    int length = strlen(cstr);
    env->ReleaseStringUTFChars(str, cstr);
    return length;
}

关键点

  • GetStringUTFCharsReleaseStringUTFChars 必须配对使用
  • Java 字符串是 UTF-16 编码,C 字符串是 UTF-8 编码
  • 注意检查 nullptr,防止崩溃

示例 4:数组传递与处理

用途:图像处理、信号处理、大数据计算。

Java 层

java

运行

复制代码
public native int sumIntArray(int[] arr);
public native float averageFloatArray(float[] arr);
public native int[] doubleIntArray(int[] arr);

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include "log_utils.h"

extern "C" JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_sumIntArray(JNIEnv *env, jobject thiz, jintArray arr) {
    // 1. 获取数组指针
    jint *data = env->GetIntArrayElements(arr, nullptr);
    if (data == nullptr) return 0;

    // 2. 获取数组长度
    jsize len = env->GetArrayLength(arr);

    // 3. 计算求和
    int sum = 0;
    for (int i = 0; i < len; i++) {
        sum += data[i];
    }

    // 4. 释放数组指针
    // 第三个参数 mode:0=复制回原数组并释放,JNI_COMMIT=复制但不释放,JNI_ABORT=不复制直接释放
    env->ReleaseIntArrayElements(arr, data, 0);

    return sum;
}

extern "C" JNIEXPORT jfloat JNICALL
Java_com_example_jnidemo_MainActivity_averageFloatArray(JNIEnv *env, jobject thiz, jfloatArray arr) {
    jfloat *data = env->GetFloatArrayElements(arr, nullptr);
    jsize len = env->GetArrayLength(arr);

    float sum = 0.0f;
    for (int i = 0; i < len; i++) {
        sum += data[i];
    }

    env->ReleaseFloatArrayElements(arr, data, 0);
    return sum / len;
}

extern "C" JNIEXPORT jintArray JNICALL
Java_com_example_jnidemo_MainActivity_doubleIntArray(JNIEnv *env, jobject thiz, jintArray arr) {
    jint *data = env->GetIntArrayElements(arr, nullptr);
    jsize len = env->GetArrayLength(arr);

    // 1. 创建新的 Java 数组
    jintArray resultArr = env->NewIntArray(len);
    jint *resultData = env->GetIntArrayElements(resultArr, nullptr);

    // 2. 填充数据(每个元素乘以2)
    for (int i = 0; i < len; i++) {
        resultData[i] = data[i] * 2;
    }

    // 3. 释放资源
    env->ReleaseIntArrayElements(arr, data, 0);
    env->ReleaseIntArrayElements(resultArr, resultData, 0);

    return resultArr;
}

关键点

  • 数组操作必须成对使用 Get/Release
  • 注意 Releasemode 参数
  • 创建新数组用 New<Type>Array

示例 5:C++ 回调 Java 方法(C++ → Java)

用途:异步任务回调、事件通知、硬件数据上报。

Java 层

java

运行

复制代码
public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }

    public native void startAsyncTask();

    // 供 C++ 回调的方法 1:更新进度
    public void onProgressUpdate(int progress) {
        runOnUiThread(() -> {
            // 更新 UI
            Log.d("JNI", "进度: " + progress + "%");
        });
    }

    // 供 C++ 回调的方法 2:任务完成
    public void onTaskComplete(String result) {
        runOnUiThread(() -> {
            Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
        });
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 启动异步任务
        startAsyncTask();
    }
}

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include <thread>
#include <chrono>
#include "log_utils.h"

// 全局保存 JavaVM(一个进程只有一个 JavaVM)
JavaVM* g_jvm = nullptr;
// 全局保存 Activity 的弱全局引用
jweak g_activity_weak = nullptr;

// JNI_OnLoad:JVM 加载 so 库时自动调用
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    g_jvm = vm;
    LOGI("JNI_OnLoad called");
    return JNI_VERSION_1_6; // 返回 JNI 版本
}

// 子线程执行的耗时任务
void asyncTaskThread() {
    JNIEnv* env = nullptr;
    
    // 1. 子线程附加到 JVM,获取 JNIEnv
    // JNIEnv 是线程私有的,不能跨线程传递
    if (g_jvm->AttachCurrentThread(&env, nullptr) != JNI_OK) {
        LOGE("AttachCurrentThread failed");
        return;
    }

    // 2. 从弱全局引用获取 Activity 对象
    jobject activity = env->NewLocalRef(g_activity_weak);
    if (activity == nullptr) {
        LOGE("Activity is null");
        g_jvm->DetachCurrentThread();
        return;
    }

    // 3. 获取 Java 类和方法 ID
    jclass clazz = env->GetObjectClass(activity);
    jmethodID onProgressId = env->GetMethodID(clazz, "onProgressUpdate", "(I)V");
    jmethodID onCompleteId = env->GetMethodID(clazz, "onTaskComplete", "(Ljava/lang/String;)V");

    // 4. 模拟耗时任务
    for (int i = 0; i <= 100; i += 10) {
        std::this_thread::sleep_for(std::chrono::milliseconds(300));
        
        // 回调 Java 的 onProgressUpdate
        env->CallVoidMethod(activity, onProgressId, i);
        LOGD("Progress: %d%%", i);
    }

    // 5. 回调 Java 的 onTaskComplete
    jstring result = env->NewStringUTF("任务执行完成!来自 C++ 的回调");
    env->CallVoidMethod(activity, onCompleteId, result);

    // 6. 清理资源
    env->DeleteLocalRef(activity);
    env->DeleteLocalRef(result);

    // 7. 分离线程(重要!否则内存泄漏)
    g_jvm->DetachCurrentThread();
}

extern "C" JNIEXPORT void JNICALL
Java_com_example_jnidemo_MainActivity_startAsyncTask(JNIEnv *env, jobject thiz) {
    // 创建弱全局引用(防止内存泄漏,允许 GC 回收)
    g_activity_weak = env->NewWeakGlobalRef(thiz);

    // 启动子线程
    std::thread t(asyncTaskThread);
    t.detach(); // 分离线程,自动回收资源
}

关键点

  • JNI_OnLoad 是 JNI 入口,用于保存 JavaVM
  • JNIEnv 是线程私有的,子线程必须通过 AttachCurrentThread 获取
  • 跨线程传递 Java 对象必须使用全局引用(NewGlobalRefNewWeakGlobalRef
  • 线程退出前必须调用 DetachCurrentThread

第二部分:数据处理与文件操作(6-10)

示例 6:大文件 MD5 校验

用途:文件完整性检查、下载文件验证、大文件上传校验。

Java 层

java

运行

复制代码
public native String calculateFileMD5(String filePath);

// 使用示例
String md5 = calculateFileMD5("/sdcard/test.zip");
Log.d("JNI", "MD5: " + md5);

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <openssl/md5.h>
#include "log_utils.h"

// 计算文件 MD5,使用 4MB 分片读取,避免内存溢出
std::string calculateMD5(const std::string& filePath) {
    std::ifstream file(filePath, std::ios::binary);
    if (!file.is_open()) {
        LOGE("Failed to open file: %s", filePath.c_str());
        return "";
    }

    MD5_CTX md5Context;
    MD5_Init(&md5Context);

    const int BUFFER_SIZE = 4 * 1024 * 1024; // 4MB 缓冲区
    char* buffer = new char[BUFFER_SIZE];

    // 分片读取文件
    while (file.read(buffer, BUFFER_SIZE)) {
        MD5_Update(&md5Context, buffer, file.gcount());
    }
    // 处理最后不足 4MB 的部分
    MD5_Update(&md5Context, buffer, file.gcount());

    // 计算最终结果
    unsigned char result[MD5_DIGEST_LENGTH];
    MD5_Final(result, &md5Context);

    // 转换为 16 进制字符串
    std::stringstream ss;
    for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
        ss << std::hex << std::setw(2) << std::setfill('0') << (int)result[i];
    }

    // 清理
    delete[] buffer;
    file.close();

    return ss.str();
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_MainActivity_calculateFileMD5(JNIEnv *env, jobject thiz, jstring filePath) {
    const char* path = env->GetStringUTFChars(filePath, nullptr);
    std::string md5 = calculateMD5(path);
    env->ReleaseStringUTFChars(filePath, path);
    return env->NewStringUTF(md5.c_str());
}

CMakeLists.txt(需集成 OpenSSL)

cmake

复制代码
cmake_minimum_required(VERSION 3.22.1)
project("jnidemo")

# 引入 OpenSSL(需要提前下载 OpenSSL Android 预编译库)
set(OPENSSL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/openssl)

add_library(ssl STATIC IMPORTED)
set_target_properties(ssl PROPERTIES IMPORTED_LOCATION
        ${OPENSSL_DIR}/lib/${ANDROID_ABI}/libssl.a)

add_library(crypto STATIC IMPORTED)
set_target_properties(crypto PROPERTIES IMPORTED_LOCATION
        ${OPENSSL_DIR}/lib/${ANDROID_ABI}/libcrypto.a)

target_include_directories(native-lib PRIVATE ${OPENSSL_DIR}/include)

add_library(native-lib SHARED native-lib.cpp)

find_library(log-lib log)

target_link_libraries(
        native-lib
        ssl
        crypto
        ${log-lib}
)

关键点

  • 大文件必须分片读取,避免 OOM
  • OpenSSL 是最常用的加密库
  • 性能比 Java 层快 5-10 倍

示例 7:SQLite 原生高性能操作

用途:本地数据库、百万级数据批量插入、离线数据存储。

Java 层

java

运行

复制代码
public native int initDatabase(String dbPath);
public native int batchInsertData(String dbPath, int count);
public native int queryDataCount(String dbPath);

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include <sqlite3.h>
#include "log_utils.h"

// 初始化数据库,创建表
int initDB(const std::string& dbPath) {
    sqlite3* db;
    int rc = sqlite3_open(dbPath.c_str(), &db);
    if (rc != SQLITE_OK) {
        LOGE("Cannot open database: %s", sqlite3_errmsg(db));
        sqlite3_close(db);
        return -1;
    }

    // 创建表
    const char* createSql = "CREATE TABLE IF NOT EXISTS user ("
                            "id INTEGER PRIMARY KEY AUTOINCREMENT,"
                            "name TEXT NOT NULL,"
                            "age INTEGER);";
    char* errMsg;
    rc = sqlite3_exec(db, createSql, nullptr, nullptr, &errMsg);
    if (rc != SQLITE_OK) {
        LOGE("SQL error: %s", errMsg);
        sqlite3_free(errMsg);
        sqlite3_close(db);
        return -1;
    }

    LOGI("Database initialized successfully");
    sqlite3_close(db);
    return 0;
}

// 批量插入数据(使用事务,性能提升 100 倍)
int batchInsert(const std::string& dbPath, int count) {
    sqlite3* db;
    int rc = sqlite3_open(dbPath.c_str(), &db);
    if (rc != SQLITE_OK) {
        LOGE("Cannot open database: %s", sqlite3_errmsg(db));
        return -1;
    }

    // 1. 开启事务(关键!不开启事务插入速度极慢)
    sqlite3_exec(db, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr);

    // 2. 预编译 SQL 语句(防止 SQL 注入,提高性能)
    const char* insertSql = "INSERT INTO user (name, age) VALUES (?, ?);";
    sqlite3_stmt* stmt;
    rc = sqlite3_prepare_v2(db, insertSql, -1, &stmt, nullptr);
    if (rc != SQLITE_OK) {
        LOGE("Failed to prepare statement: %s", sqlite3_errmsg(db));
        sqlite3_close(db);
        return -1;
    }

    // 3. 批量插入
    for (int i = 0; i < count; i++) {
        std::string name = "User_" + std::to_string(i);
        int age = 20 + (i % 30);

        // 绑定参数
        sqlite3_bind_text(stmt, 1, name.c_str(), name.length(), SQLITE_TRANSIENT);
        sqlite3_bind_int(stmt, 2, age);

        // 执行
        sqlite3_step(stmt);
        // 重置语句,以便下次使用
        sqlite3_reset(stmt);
    }

    // 4. 提交事务
    sqlite3_exec(db, "COMMIT;", nullptr, nullptr, nullptr);

    // 5. 释放资源
    sqlite3_finalize(stmt);
    sqlite3_close(db);

    LOGI("Successfully inserted %d records", count);
    return 0;
}

// 查询数据数量
int queryCount(const std::string& dbPath) {
    sqlite3* db;
    int rc = sqlite3_open(dbPath.c_str(), &db);
    if (rc != SQLITE_OK) return -1;

    const char* querySql = "SELECT COUNT(*) FROM user;";
    sqlite3_stmt* stmt;
    sqlite3_prepare_v2(db, querySql, -1, &stmt, nullptr);

    int count = 0;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        count = sqlite3_column_int(stmt, 0);
    }

    sqlite3_finalize(stmt);
    sqlite3_close(db);
    return count;
}

extern "C" JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_initDatabase(JNIEnv *env, jobject thiz, jstring dbPath) {
    const char* path = env->GetStringUTFChars(dbPath, nullptr);
    int ret = initDB(path);
    env->ReleaseStringUTFChars(dbPath, path);
    return ret;
}

extern "C" JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_batchInsertData(JNIEnv *env, jobject thiz, jstring dbPath, jint count) {
    const char* path = env->GetStringUTFChars(dbPath, nullptr);
    int ret = batchInsert(path, count);
    env->ReleaseStringUTFChars(dbPath, path);
    return ret;
}

extern "C" JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_queryDataCount(JNIEnv *env, jobject thiz, jstring dbPath) {
    const char* path = env->GetStringUTFChars(dbPath, nullptr);
    int count = queryCount(path);
    env->ReleaseStringUTFChars(dbPath, path);
    return count;
}

CMakeLists.txt(直接引入 SQLite 源码)

cmake

复制代码
cmake_minimum_required(VERSION 3.22.1)
project("jnidemo")

# 直接引入 SQLite 源码(推荐,无需预编译)
add_library(sqlite3 STATIC
        third_party/sqlite3/sqlite3.c
        third_party/sqlite3/sqlite3.h)

target_include_directories(sqlite3 PUBLIC third_party/sqlite3)

add_library(native-lib SHARED native-lib.cpp)

find_library(log-lib log)

target_link_libraries(
        native-lib
        sqlite3
        ${log-lib}
)

关键点

  • 批量操作必须开启事务,否则性能极差
  • 使用预编译语句防止 SQL 注入
  • 原生 SQLite 比 Java 层快 10-100 倍

示例 8:ZIP 压缩与解压(支持密码)

用途:文件打包、加密压缩、备份恢复。

Java 层

java

运行

复制代码
public native int zipFiles(String[] srcFiles, String zipPath, String password);
public native int unzipFile(String zipPath, String destDir, String password);

C++ 层(使用 libzip)

cpp

运行

复制代码
#include <jni.h>
#include <zip.h>
#include <vector>
#include "log_utils.h"

// 压缩文件
int compressFiles(const std::vector<std::string>& srcFiles, 
                  const std::string& zipPath, 
                  const std::string& password) {
    int error = 0;
    zip_t* zip = zip_open(zipPath.c_str(), ZIP_CREATE | ZIP_TRUNCATE, &error);
    if (zip == nullptr) {
        LOGE("Failed to create zip file: %d", error);
        return -1;
    }

    // 设置密码(如果有)
    if (!password.empty()) {
        zip_set_default_password(zip, password.c_str());
    }

    // 添加文件
    for (const auto& filePath : srcFiles) {
        zip_source_t* source = zip_source_file(zip, filePath.c_str(), 0, 0);
        if (source == nullptr) {
            LOGE("Failed to add file: %s", filePath.c_str());
            zip_close(zip);
            return -1;
        }

        // 获取文件名(不含路径)
        size_t lastSlash = filePath.find_last_of("/");
        std::string fileName = (lastSlash != std::string::npos) 
                               ? filePath.substr(lastSlash + 1) 
                               : filePath;

        zip_file_add(zip, fileName.c_str(), source, ZIP_FL_OVERWRITE);
    }

    zip_close(zip);
    LOGI("Zip created successfully: %s", zipPath.c_str());
    return 0;
}

// 解压文件
int extractFile(const std::string& zipPath, 
                const std::string& destDir, 
                const std::string& password) {
    int error = 0;
    zip_t* zip = zip_open(zipPath.c_str(), 0, &error);
    if (zip == nullptr) {
        LOGE("Failed to open zip file: %d", error);
        return -1;
    }

    if (!password.empty()) {
        zip_set_default_password(zip, password.c_str());
    }

    // 解压所有文件
    zip_int64_t numEntries = zip_get_num_entries(zip, 0);
    for (zip_int64_t i = 0; i < numEntries; i++) {
        const char* name = zip_get_name(zip, i, 0);
        std::string destPath = destDir + "/" + name;

        zip_file_t* zf = zip_fopen_index(zip, i, 0);
        if (zf == nullptr) continue;

        FILE* fp = fopen(destPath.c_str(), "wb");
        if (fp == nullptr) {
            zip_fclose(zf);
            continue;
        }

        char buf[8192];
        zip_int64_t bytesRead;
        while ((bytesRead = zip_fread(zf, buf, sizeof(buf))) > 0) {
            fwrite(buf, 1, bytesRead, fp);
        }

        fclose(fp);
        zip_fclose(zf);
    }

    zip_close(zip);
    LOGI("Unzip completed successfully");
    return 0;
}

extern "C" JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_zipFiles(JNIEnv *env, jobject thiz, 
                                                jobjectArray srcFiles, 
                                                jstring zipPath, 
                                                jstring password) {
    // 转换 Java 数组为 C++ vector
    jsize len = env->GetArrayLength(srcFiles);
    std::vector<std::string> files;
    for (int i = 0; i < len; i++) {
        jstring jFile = (jstring)env->GetObjectArrayElement(srcFiles, i);
        const char* cFile = env->GetStringUTFChars(jFile, nullptr);
        files.push_back(cFile);
        env->ReleaseStringUTFChars(jFile, cFile);
    }

    const char* cZipPath = env->GetStringUTFChars(zipPath, nullptr);
    const char* cPassword = password ? env->GetStringUTFChars(password, nullptr) : "";

    int ret = compressFiles(files, cZipPath, cPassword);

    env->ReleaseStringUTFChars(zipPath, cZipPath);
    if (password) env->ReleaseStringUTFChars(password, cPassword);

    return ret;
}

关键点

  • libzip 是最常用的 ZIP 操作库
  • 支持密码加密压缩
  • 比 Java 层 ZipInputStream 功能更强大

示例 9:AES-256-CBC 加密解密

用途:用户密码加密、敏感数据存储、网络传输加密。

Java 层

java

运行

复制代码
public native String encryptAES(String plaintext, String key);
public native String decryptAES(String ciphertext, String key);

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include <openssl/aes.h>
#include <openssl/rand.h>
#include <string>
#include <vector>
#include "log_utils.h"

// AES-256-CBC 加密
std::string aesEncrypt(const std::string& plaintext, const std::string& key) {
    // 生成随机 IV(初始化向量)
    unsigned char iv[AES_BLOCK_SIZE];
    RAND_bytes(iv, AES_BLOCK_SIZE);

    // 设置加密密钥
    AES_KEY aesKey;
    AES_set_encrypt_key((const unsigned char*)key.c_str(), 256, &aesKey);

    // PKCS7 填充
    int padding = AES_BLOCK_SIZE - (plaintext.length() % AES_BLOCK_SIZE);
    std::string padded = plaintext + std::string(padding, padding);

    // 加密
    std::string ciphertext;
    ciphertext.resize(padded.length());

    unsigned char ivCopy[AES_BLOCK_SIZE];
    memcpy(ivCopy, iv, AES_BLOCK_SIZE);

    AES_cbc_encrypt((const unsigned char*)padded.c_str(),
                    (unsigned char*)ciphertext.data(),
                    padded.length(),
                    &aesKey,
                    ivCopy,
                    AES_ENCRYPT);

    // 将 IV 附加到密文前面(解密时需要)
    std::string result;
    result.append((char*)iv, AES_BLOCK_SIZE);
    result.append(ciphertext);

    return result;
}

// AES-256-CBC 解密
std::string aesDecrypt(const std::string& ciphertext, const std::string& key) {
    // 从密文前面提取 IV
    unsigned char iv[AES_BLOCK_SIZE];
    memcpy(iv, ciphertext.c_str(), AES_BLOCK_SIZE);

    // 实际密文部分
    std::string actualCipher = ciphertext.substr(AES_BLOCK_SIZE);

    // 设置解密密钥
    AES_KEY aesKey;
    AES_set_decrypt_key((const unsigned char*)key.c_str(), 256, &aesKey);

    // 解密
    std::string plaintext;
    plaintext.resize(actualCipher.length());

    unsigned char ivCopy[AES_BLOCK_SIZE];
    memcpy(ivCopy, iv, AES_BLOCK_SIZE);

    AES_cbc_encrypt((const unsigned char*)actualCipher.c_str(),
                    (unsigned char*)plaintext.data(),
                    actualCipher.length(),
                    &aesKey,
                    ivCopy,
                    AES_DECRYPT);

    // 去除 PKCS7 填充
    int padding = plaintext[plaintext.length() - 1];
    plaintext.resize(plaintext.length() - padding);

    return plaintext;
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_MainActivity_encryptAES(JNIEnv *env, jobject thiz, 
                                                   jstring plaintext, jstring key) {
    const char* cPlain = env->GetStringUTFChars(plaintext, nullptr);
    const char* cKey = env->GetStringUTFChars(key, nullptr);

    std::string cipher = aesEncrypt(cPlain, cKey);

    env->ReleaseStringUTFChars(plaintext, cPlain);
    env->ReleaseStringUTFChars(key, cKey);

    // 注意:实际使用时需要 Base64 编码
    return env->NewStringUTF(cipher.c_str());
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_MainActivity_decryptAES(JNIEnv *env, jobject thiz, 
                                                   jstring ciphertext, jstring key) {
    const char* cCipher = env->GetStringUTFChars(ciphertext, nullptr);
    const char* cKey = env->GetStringUTFChars(key, nullptr);

    std::string plain = aesDecrypt(cCipher, cKey);

    env->ReleaseStringUTFChars(ciphertext, cCipher);
    env->ReleaseStringUTFChars(key, cKey);

    return env->NewStringUTF(plain.c_str());
}

关键点

  • 密钥硬编码在 C++ 层比 Java 层安全
  • IV 必须随机,且随密文一起传输
  • AES-256 是目前最安全的对称加密算法之一

示例 10:JSON 解析与生成(使用 RapidJSON)

用途:网络数据解析、配置文件读写、数据交换。

Java 层

java

运行

复制代码
public native String parseJson(String jsonStr);
public native String generateJson();

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include "log_utils.h"

using namespace rapidjson;

// 解析 JSON
std::string parseJson(const std::string& jsonStr) {
    Document doc;
    doc.Parse(jsonStr.c_str());

    if (doc.HasParseError()) {
        LOGE("JSON parse error");
        return "";
    }

    // 读取字段
    std::string result;
    if (doc.HasMember("name") && doc["name"].IsString()) {
        result += "Name: " + std::string(doc["name"].GetString()) + "\n";
    }
    if (doc.HasMember("age") && doc["age"].IsInt()) {
        result += "Age: " + std::to_string(doc["age"].GetInt()) + "\n";
    }
    if (doc.HasMember("hobbies") && doc["hobbies"].IsArray()) {
        result += "Hobbies: ";
        const Value& hobbies = doc["hobbies"];
        for (SizeType i = 0; i < hobbies.Size(); i++) {
            result += hobbies[i].GetString();
            if (i < hobbies.Size() - 1) result += ", ";
        }
    }

    return result;
}

// 生成 JSON
std::string generateJson() {
    Document doc;
    doc.SetObject();
    Document::AllocatorType& allocator = doc.GetAllocator();

    // 添加字段
    doc.AddMember("name", "Zhang San", allocator);
    doc.AddMember("age", 25, allocator);

    // 添加数组
    Value hobbies(kArrayType);
    hobbies.PushBack("Reading", allocator);
    hobbies.PushBack("Gaming", allocator);
    hobbies.PushBack("Coding", allocator);
    doc.AddMember("hobbies", hobbies, allocator);

    // 添加嵌套对象
    Value address(kObjectType);
    address.AddMember("city", "Beijing", allocator);
    address.AddMember("district", "Chaoyang", allocator);
    doc.AddMember("address", address, allocator);

    // 转换为字符串
    StringBuffer buffer;
    Writer<StringBuffer> writer(buffer);
    doc.Accept(writer);

    return buffer.GetString();
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_MainActivity_parseJson(JNIEnv *env, jobject thiz, jstring jsonStr) {
    const char* cJson = env->GetStringUTFChars(jsonStr, nullptr);
    std::string result = parseJson(cJson);
    env->ReleaseStringUTFChars(jsonStr, cJson);
    return env->NewStringUTF(result.c_str());
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_MainActivity_generateJson(JNIEnv *env, jobject thiz) {
    std::string json = generateJson();
    return env->NewStringUTF(json.c_str());
}

关键点

  • RapidJSON 是最快的 JSON 库之一,仅头文件,无需编译
  • 性能比 Java 层 Gson/Jackson 快 5-10 倍
  • 内存占用极低

第三部分:音视频与图形(11-15)

示例 11:OpenCV 图像处理(灰度化 + 边缘检测)

用途:相机滤镜、人脸检测、二维码识别、OCR。

Java 层

java

运行

复制代码
public native Bitmap processImage(Bitmap bitmap);

// 使用示例
Bitmap original = BitmapFactory.decodeResource(getResources(), R.drawable.test);
Bitmap processed = processImage(original);
imageView.setImageBitmap(processed);

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include <opencv2/opencv.hpp>
#include <android/bitmap.h>
#include "log_utils.h"

using namespace cv;

// Bitmap 转 OpenCV Mat
void bitmapToMat(JNIEnv* env, jobject bitmap, Mat& mat) {
    AndroidBitmapInfo info;
    void* pixels = nullptr;

    // 获取 Bitmap 信息
    AndroidBitmap_getInfo(env, bitmap, &info);
    // 锁定像素缓冲区
    AndroidBitmap_lockPixels(env, bitmap, &pixels);

    // 创建 Mat(注意 Android Bitmap 是 RGBA 格式)
    Mat tmp(info.height, info.width, CV_8UC4, pixels);
    // 转换为 BGR 格式(OpenCV 默认格式)
    cvtColor(tmp, mat, COLOR_RGBA2BGR);

    // 解锁像素缓冲区
    AndroidBitmap_unlockPixels(env, bitmap);
}

// OpenCV Mat 转 Bitmap
jobject matToBitmap(JNIEnv* env, Mat& mat) {
    // 转换为 RGBA 格式
    Mat rgba;
    cvtColor(mat, rgba, COLOR_BGR2RGBA);

    // 创建 Bitmap 配置
    jclass bitmapCls = env->FindClass("android/graphics/Bitmap");
    jmethodID createBitmapMethod = env->GetStaticMethodID(
            bitmapCls, "createBitmap",
            "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");

    jclass configCls = env->FindClass("android/graphics/Bitmap$Config");
    jfieldID configField = env->GetStaticFieldID(
            configCls, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
    jobject config = env->GetStaticObjectField(configCls, configField);

    // 创建 Bitmap
    jobject bitmap = env->CallStaticObjectMethod(
            bitmapCls, createBitmapMethod,
            rgba.cols, rgba.rows, config);

    // 复制像素数据
    AndroidBitmapInfo info;
    void* pixels = nullptr;
    AndroidBitmap_getInfo(env, bitmap, &info);
    AndroidBitmap_lockPixels(env, bitmap, &pixels);
    memcpy(pixels, rgba.data, rgba.total() * rgba.elemSize());
    AndroidBitmap_unlockPixels(env, bitmap);

    return bitmap;
}

extern "C" JNIEXPORT jobject JNICALL
Java_com_example_jnidemo_MainActivity_processImage(JNIEnv *env, jobject thiz, jobject bitmap) {
    Mat src, gray, edges;

    // 1. Bitmap 转 Mat
    bitmapToMat(env, bitmap, src);

    // 2. 灰度化
    cvtColor(src, gray, COLOR_BGR2GRAY);

    // 3. 高斯模糊(降噪)
    GaussianBlur(gray, gray, Size(3, 3), 0);

    // 4. Canny 边缘检测
    Canny(gray, edges, 50, 150);

    // 5. 转回彩色以便显示
    cvtColor(edges, edges, COLOR_GRAY2BGR);

    // 6. Mat 转 Bitmap 返回
    return matToBitmap(env, edges);
}

CMakeLists.txt(集成 OpenCV)

cmake

复制代码
cmake_minimum_required(VERSION 3.22.1)
project("jnidemo")

# 设置 OpenCV 路径
set(OpenCV_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/opencv/sdk/native/jni)

# 查找 OpenCV
find_package(OpenCV REQUIRED)

add_library(native-lib SHARED native-lib.cpp)

find_library(log-lib log)
find_library(android-lib android)

target_link_libraries(
        native-lib
        ${OpenCV_LIBS}
        ${log-lib}
        ${android-lib}
)

关键点

  • Bitmap 和 Mat 互转是 OpenCV 图像处理的基础
  • 必须链接 android 库用于 Bitmap 操作
  • OpenCV 是计算机视觉最常用的库

示例 12:OpenGL ES 实时滤镜

用途:相机滤镜、美颜相机、AR 特效、视频剪辑。

Java 层

java

运行

复制代码
// 自定义 GLSurfaceView
public class MyGLSurfaceView extends GLSurfaceView {
    public MyGLSurfaceView(Context context) {
        super(context);
        setEGLContextClientVersion(3);
        setRenderer(new MyRenderer());
    }
}

// Renderer
public class MyRenderer implements GLSurfaceView.Renderer {
    static {
        System.loadLibrary("native-lib");
    }

    public native void initGL();
    public native void drawFrame();
    public native void surfaceChanged(int width, int height);

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        initGL();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        surfaceChanged(width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        drawFrame();
    }
}

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include <GLES3/gl3.h>
#include "log_utils.h"

// 顶点着色器
const char* vertexShaderCode = R"(
    #version 300 es
    in vec4 vPosition;
    in vec2 vTexCoord;
    out vec2 texCoord;
    void main() {
        gl_Position = vPosition;
        texCoord = vTexCoord;
    }
)";

// 片段着色器(灰度滤镜)
const char* fragmentShaderCode = R"(
    #version 300 es
    precision mediump float;
    in vec2 texCoord;
    uniform sampler2D texture;
    out vec4 fragColor;
    void main() {
        vec4 color = texture(texture, texCoord);
        // 灰度化公式:Gray = 0.299*R + 0.587*G + 0.114*B
        float gray = 0.299 * color.r + 0.587 * color.g + 0.114 * color.b;
        fragColor = vec4(gray, gray, gray, 1.0);
    }
)";

GLuint shaderProgram;
GLuint textureId;

// 编译着色器
GLuint compileShader(GLenum type, const char* code) {
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &code, nullptr);
    glCompileShader(shader);

    GLint success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success) {
        char log[512];
        glGetShaderInfoLog(shader, 512, nullptr, log);
        LOGE("Shader compile error: %s", log);
    }

    return shader;
}

// 初始化 OpenGL
extern "C" JNIEXPORT void JNICALL
Java_com_example_jnidemo_MyRenderer_initGL(JNIEnv *env, jobject thiz) {
    // 编译着色器
    GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderCode);
    GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderCode);

    // 链接着色器程序
    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    // 删除着色器
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    // 设置清屏颜色
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}

// 绘制每一帧
extern "C" JNIEXPORT void JNICALL
Java_com_example_jnidemo_MyRenderer_drawFrame(JNIEnv *env, jobject thiz) {
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(shaderProgram);

    // 绘制代码...
    // 这里省略顶点数据、纹理绑定等具体实现
}

// 表面尺寸变化
extern "C" JNIEXPORT void JNICALL
Java_com_example_jnidemo_MyRenderer_surfaceChanged(JNIEnv *env, jobject thiz, jint width, jint height) {
    glViewport(0, 0, width, height);
}

关键点

  • OpenGL ES 使用 GPU 加速,性能极高
  • GLSL 着色器是实现滤镜的核心
  • 可实现 30fps 以上的实时处理

示例 13:FFmpeg 音频解码

用途:音乐播放器、视频播放器、音频编辑。

Java 层

java

运行

复制代码
public native int decodeAudio(String audioPath, String pcmPath);

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include <string>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
}
#include "log_utils.h"

// 解码音频文件为 PCM
int decodeAudioFile(const std::string& inputPath, const std::string& outputPath) {
    // 1. 打开输入文件
    AVFormatContext* formatCtx = avformat_alloc_context();
    if (avformat_open_input(&formatCtx, inputPath.c_str(), nullptr, nullptr) != 0) {
        LOGE("Failed to open input file");
        return -1;
    }

    // 2. 查找流信息
    if (avformat_find_stream_info(formatCtx, nullptr) < 0) {
        LOGE("Failed to find stream info");
        avformat_close_input(&formatCtx);
        return -1;
    }

    // 3. 查找音频流
    int audioStreamIndex = -1;
    for (unsigned int i = 0; i < formatCtx->nb_streams; i++) {
        if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            audioStreamIndex = i;
            break;
        }
    }

    if (audioStreamIndex == -1) {
        LOGE("No audio stream found");
        avformat_close_input(&formatCtx);
        return -1;
    }

    // 4. 获取解码器
    AVCodecParameters* codecPar = formatCtx->streams[audioStreamIndex]->codecpar;
    const AVCodec* codec = avcodec_find_decoder(codecPar->codec_id);
    AVCodecContext* codecCtx = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(codecCtx, codecPar);
    avcodec_open2(codecCtx, codec, nullptr);

    // 5. 初始化重采样器(转换为 44100Hz 16位 立体声)
    SwrContext* swrCtx = swr_alloc();
    swrCtx = swr_alloc_set_opts(swrCtx,
                                 AV_CH_LAYOUT_STEREO,
                                 AV_SAMPLE_FMT_S16,
                                 44100,
                                 codecCtx->channel_layout,
                                 codecCtx->sample_fmt,
                                 codecCtx->sample_rate,
                                 0, nullptr);
    swr_init(swrCtx);

    // 6. 打开输出文件
    FILE* outFile = fopen(outputPath.c_str(), "wb");
    if (outFile == nullptr) {
        LOGE("Failed to open output file");
        avcodec_free_context(&codecCtx);
        avformat_close_input(&formatCtx);
        return -1;
    }

    // 7. 解码循环
    AVPacket* packet = av_packet_alloc();
    AVFrame* frame = av_frame_alloc();

    while (av_read_frame(formatCtx, packet) >= 0) {
        if (packet->stream_index == audioStreamIndex) {
            avcodec_send_packet(codecCtx, packet);
            while (avcodec_receive_frame(codecCtx, frame) == 0) {
                // 重采样
                uint8_t* outBuffer[2];
                int outSamples = swr_convert(swrCtx, outBuffer, frame->nb_samples,
                                              (const uint8_t**)frame->data, frame->nb_samples);
                int dataSize = outSamples * 2 * 2; // 2声道 * 16位
                fwrite(outBuffer[0], 1, dataSize, outFile);
            }
        }
        av_packet_unref(packet);
    }

    // 8. 清理资源
    fclose(outFile);
    av_frame_free(&frame);
    av_packet_free(&packet);
    swr_free(&swrCtx);
    avcodec_free_context(&codecCtx);
    avformat_close_input(&formatCtx);
    avformat_free_context(formatCtx);

    LOGI("Audio decode completed");
    return 0;
}

extern "C" JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_decodeAudio(JNIEnv *env, jobject thiz, 
                                                    jstring audioPath, jstring pcmPath) {
    const char* cInput = env->GetStringUTFChars(audioPath, nullptr);
    const char* cOutput = env->GetStringUTFChars(pcmPath, nullptr);

    int ret = decodeAudioFile(cInput, cOutput);

    env->ReleaseStringUTFChars(audioPath, cInput);
    env->ReleaseStringUTFChars(pcmPath, cOutput);

    return ret;
}

关键点

  • FFmpeg 是音视频处理的瑞士军刀
  • 音频通常需要重采样为统一格式
  • 注意 FFmpeg 的头文件需要用 extern "C" 包裹

示例 14:RTSP 实时流拉取

用途:安防监控、车载摄像头、无人机图传。

Java 层

java

运行

复制代码
public native int startRTSPStream(String url, Surface surface);
public native void stopRTSPStream();

CMakeLists.txt(集成 FFmpeg)

cmake

复制代码
cmake_minimum_required(VERSION 3.22.1)
project("jnidemo")

# 设置 FFmpeg 路径
set(FFMPEG_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/ffmpeg)

# 引入 FFmpeg 库
add_library(avcodec SHARED IMPORTED)
set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION
        ${FFMPEG_DIR}/lib/${ANDROID_ABI}/libavcodec.so)

add_library(avformat SHARED IMPORTED)
set_target_properties(avformat PROPERTIES IMPORTED_LOCATION
        ${FFMPEG_DIR}/lib/${ANDROID_ABI}/libavformat.so)

add_library(avutil SHARED IMPORTED)
set_target_properties(avutil PROPERTIES IMPORTED_LOCATION
        ${FFMPEG_DIR}/lib/${ANDROID_ABI}/libavutil.so)

add_library(swscale SHARED IMPORTED)
set_target_properties(swscale PROPERTIES IMPORTED_LOCATION
        ${FFMPEG_DIR}/lib/${ANDROID_ABI}/libswscale.so)

target_include_directories(native-lib PRIVATE ${FFMPEG_DIR}/include)

add_library(native-lib SHARED native-lib.cpp)

find_library(log-lib log)
find_library(android-lib android)

target_link_libraries(
        native-lib
        avcodec
        avformat
        avutil
        swscale
        ${log-lib}
        ${android-lib}
)

关键点

  • RTSP 是安防监控最常用的协议
  • FFmpeg 支持几乎所有流媒体协议
  • 低延迟播放需要优化缓冲策略

示例 15:MediaCodec 硬件解码

用途:实时视频播放、直播推流、低延迟解码。

Java 层

java

运行

复制代码
public native int initMediaCodec(Surface surface, String mime, int width, int height);
public native int decodeFrame(byte[] data, int offset, int length);
public native void releaseMediaCodec();

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include <media/NdkMediaCodec.h>
#include <media/NdkMediaFormat.h>
#include "log_utils.h"

AMediaCodec* codec = nullptr;

// 初始化 MediaCodec
extern "C" JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_initMediaCodec(JNIEnv *env, jobject thiz, 
                                                       jobject surface, jstring mime, 
                                                       jint width, jint height) {
    const char* cMime = env->GetStringUTFChars(mime, nullptr);

    // 1. 创建解码器
    codec = AMediaCodec_createDecoderByType(cMime);
    if (codec == nullptr) {
        LOGE("Failed to create MediaCodec");
        env->ReleaseStringUTFChars(mime, cMime);
        return -1;
    }

    // 2. 配置格式
    AMediaFormat* format = AMediaFormat_new();
    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, cMime);
    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, width);
    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, height);

    // 3. 配置并启动解码器
    ANativeWindow* window = ANativeWindow_fromSurface(env, surface);
    media_status_t status = AMediaCodec_configure(codec, format, window, nullptr, 0);
    if (status != AMEDIA_OK) {
        LOGE("Failed to configure MediaCodec");
        AMediaFormat_delete(format);
        AMediaCodec_delete(codec);
        env->ReleaseStringUTFChars(mime, cMime);
        return -1;
    }

    AMediaCodec_start(codec);
    AMediaFormat_delete(format);
    ANativeWindow_release(window);
    env->ReleaseStringUTFChars(mime, cMime);

    LOGI("MediaCodec initialized successfully");
    return 0;
}

// 解码一帧
extern "C" JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_decodeFrame(JNIEnv *env, jobject thiz, 
                                                    jbyteArray data, jint offset, jint length) {
    if (codec == nullptr) return -1;

    // 1. 获取输入缓冲区
    ssize_t inputIndex = AMediaCodec_dequeueInputBuffer(codec, 10000);
    if (inputIndex >= 0) {
        size_t inputSize;
        uint8_t* inputBuffer = AMediaCodec_getInputBuffer(codec, inputIndex, &inputSize);

        // 2. 复制数据
        jbyte* cData = env->GetByteArrayElements(data, nullptr);
        memcpy(inputBuffer, cData + offset, length);
        env->ReleaseByteArrayElements(data, cData, 0);

        // 3. 入队
        AMediaCodec_queueInputBuffer(codec, inputIndex, 0, length, 0, 0);
    }

    // 4. 获取输出
    AMediaCodecBufferInfo info;
    ssize_t outputIndex = AMediaCodec_dequeueOutputBuffer(codec, &info, 10000);
    if (outputIndex >= 0) {
        // 5. 释放输出缓冲区(渲染到 Surface)
        AMediaCodec_releaseOutputBuffer(codec, outputIndex, true);
    }

    return 0;
}

// 释放资源
extern "C" JNIEXPORT void JNICALL
Java_com_example_jnidemo_MainActivity_releaseMediaCodec(JNIEnv *env, jobject thiz) {
    if (codec != nullptr) {
        AMediaCodec_stop(codec);
        AMediaCodec_delete(codec);
        codec = nullptr;
    }
}

关键点

  • MediaCodec 使用手机硬件解码器,性能提升 10 倍
  • 功耗比软解码低 80%
  • 是 Android 音视频开发的必备技能

第四部分:AI 与安全(16-20)

示例 16:TensorFlow Lite 图像分类

用途:实时物体检测、人脸识别、OCR、AI 滤镜。

Java 层

java

运行

复制代码
public native int initModel(String modelPath);
public native float[] classifyImage(Bitmap bitmap);

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include <tensorflow/lite/interpreter.h>
#include <tensorflow/lite/kernels/register.h>
#include <tensorflow/lite/model.h>
#include <opencv2/opencv.hpp>
#include <android/bitmap.h>
#include "log_utils.h"

using namespace tflite;

std::unique_ptr<FlatBufferModel> model;
std::unique_ptr<Interpreter> interpreter;

// 初始化 TFLite 模型
extern "C" JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_initModel(JNIEnv *env, jobject thiz, jstring modelPath) {
    const char* cPath = env->GetStringUTFChars(modelPath, nullptr);

    // 1. 加载模型
    model = FlatBufferModel::BuildFromFile(cPath);
    if (!model) {
        LOGE("Failed to load model");
        env->ReleaseStringUTFChars(modelPath, cPath);
        return -1;
    }

    // 2. 创建解释器
    ops::builtin::BuiltinOpResolver resolver;
    InterpreterBuilder builder(*model, resolver);
    builder(&interpreter);
    if (!interpreter) {
        LOGE("Failed to create interpreter");
        env->ReleaseStringUTFChars(modelPath, cPath);
        return -1;
    }

    // 3. 分配张量
    interpreter->AllocateTensors();

    env->ReleaseStringUTFChars(modelPath, cPath);
    LOGI("Model initialized successfully");
    return 0;
}

// 图像分类
extern "C" JNIEXPORT jfloatArray JNICALL
Java_com_example_jnidemo_MainActivity_classifyImage(JNIEnv *env, jobject thiz, jobject bitmap) {
    if (!interpreter) {
        return nullptr;
    }

    // 1. Bitmap 转 Mat
    cv::Mat src;
    AndroidBitmapInfo info;
    void* pixels = nullptr;
    AndroidBitmap_getInfo(env, bitmap, &info);
    AndroidBitmap_lockPixels(env, bitmap, &pixels);
    cv::Mat tmp(info.height, info.width, CV_8UC4, pixels);
    cv::cvtColor(tmp, src, cv::COLOR_RGBA2RGB);
    AndroidBitmap_unlockPixels(env, bitmap);

    // 2. 预处理:调整大小、归一化
    cv::Mat resized;
    cv::resize(src, resized, cv::Size(224, 224));
    resized.convertTo(resized, CV_32F, 1.0 / 255.0);

    // 3. 输入数据
    float* input = interpreter->typed_input_tensor<float>(0);
    memcpy(input, resized.data, 224 * 224 * 3 * sizeof(float));

    // 4. 运行推理
    interpreter->Invoke();

    // 5. 获取输出
    float* output = interpreter->typed_output_tensor<float>(0);
    int outputSize = interpreter->output_tensor(0)->dims->data[1];

    // 6. 转换为 Java 数组
    jfloatArray jResult = env->NewFloatArray(outputSize);
    env->SetFloatArrayRegion(jResult, 0, outputSize, output);

    return jResult;
}

CMakeLists.txt(集成 TFLite)

cmake

复制代码
cmake_minimum_required(VERSION 3.22.1)
project("jnidemo")

# 设置 TFLite 路径
set(TFLITE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/tensorflow-lite)

# 引入 TFLite 库
add_library(tensorflow-lite SHARED IMPORTED)
set_target_properties(tensorflow-lite PROPERTIES IMPORTED_LOCATION
        ${TFLITE_DIR}/lib/${ANDROID_ABI}/libtensorflow-lite.so)

target_include_directories(native-lib PRIVATE ${TFLITE_DIR}/include)

# OpenCV 用于图像处理
set(OpenCV_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/opencv/sdk/native/jni)
find_package(OpenCV REQUIRED)

add_library(native-lib SHARED native-lib.cpp)

find_library(log-lib log)
find_library(android-lib android)

target_link_libraries(
        native-lib
        tensorflow-lite
        ${OpenCV_LIBS}
        ${log-lib}
        ${android-lib}
)

关键点

  • TFLite 是移动端 AI 推理的首选框架
  • 可以开启 NNAPI 硬件加速,性能提升 5-10 倍
  • 模型需要提前转换为 .tflite 格式

示例 17:YOLOv8 实时目标检测

用途:物体检测、人脸识别、车牌识别、工业质检。

Java 层

java

运行

复制代码
public native int initYOLOv8(String modelPath);
public native Detection[] detectObjects(Bitmap bitmap);

// 检测结果类
public class Detection {
    public int classId;
    public String className;
    public float confidence;
    public float x, y, width, height;
}

C++ 层(核心代码)

cpp

运行

复制代码
#include <jni.h>
#include <tensorflow/lite/interpreter.h>
#include <tensorflow/lite/kernels/register.h>
#include <tensorflow/lite/delegates/nnapi/nnapi_delegate.h>
#include <opencv2/opencv.hpp>
#include "log_utils.h"

using namespace tflite;
using namespace cv;

std::unique_ptr<FlatBufferModel> model;
std::unique_ptr<Interpreter> interpreter;
std::unique_ptr<StatefulNnApiDelegate> nnapiDelegate;

// 检测结果结构体
struct Detection {
    int classId;
    float confidence;
    Rect bbox;
};

// 初始化 YOLOv8
extern "C" JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_initYOLOv8(JNIEnv *env, jobject thiz, jstring modelPath) {
    const char* cPath = env->GetStringUTFChars(modelPath, nullptr);

    // 1. 加载模型
    model = FlatBufferModel::BuildFromFile(cPath);
    if (!model) {
        LOGE("Failed to load model");
        return -1;
    }

    // 2. 配置 NNAPI 加速(关键!性能提升 5-10 倍)
    StatefulNnApiDelegate::Options options;
    options.allow_fp16 = true;
    nnapiDelegate = std::make_unique<StatefulNnApiDelegate>(options);

    // 3. 创建解释器
    ops::builtin::BuiltinOpResolver resolver;
    InterpreterBuilder builder(*model, resolver);
    builder.AddDelegate(nnapiDelegate.get());
    builder(&interpreter);
    if (!interpreter) {
        LOGE("Failed to create interpreter");
        return -1;
    }

    // 4. 分配张量
    interpreter->AllocateTensors();

    env->ReleaseStringUTFChars(modelPath, cPath);
    LOGI("YOLOv8 initialized with NNAPI");
    return 0;
}

// NMS 非极大值抑制
std::vector<Detection> nms(std::vector<Detection>& detections, float iouThreshold) {
    std::vector<Detection> result;
    // NMS 实现...
    return result;
}

// 目标检测
extern "C" JNIEXPORT jobjectArray JNICALL
Java_com_example_jnidemo_MainActivity_detectObjects(JNIEnv *env, jobject thiz, jobject bitmap) {
    if (!interpreter) return nullptr;

    // 1. 图像预处理
    Mat src;
    // ... Bitmap 转 Mat,调整大小,归一化 ...

    // 2. 输入数据
    float* input = interpreter->typed_input_tensor<float>(0);
    memcpy(input, src.data, 640 * 640 * 3 * sizeof(float));

    // 3. 运行推理
    interpreter->Invoke();

    // 4. 后处理:解析输出、置信度过滤、NMS
    float* output = interpreter->typed_output_tensor<float>(0);
    std::vector<Detection> detections;
    // ... 解析输出 ...
    detections = nms(detections, 0.45f);

    // 5. 转换为 Java 对象数组
    jclass detectionCls = env->FindClass("com/example/jnidemo/Detection");
    jmethodID constructor = env->GetMethodID(detectionCls, "<init>", "()V");
    jobjectArray jDetections = env->NewObjectArray(detections.size(), detectionCls, nullptr);

    for (size_t i = 0; i < detections.size(); i++) {
        jobject jDet = env->NewObject(detectionCls, constructor);
        // ... 设置字段 ...
        env->SetObjectArrayElement(jDetections, i, jDet);
    }

    return jDetections;
}

关键点

  • YOLO 是目前最流行的目标检测算法
  • NNAPI 硬件加速是实时检测的关键
  • 后处理(NMS)是检测结果准确的重要环节

示例 18:SO 库反调试

用途:支付 SDK、加密算法、核心业务逻辑保护。

Java 层

java

运行

复制代码
public native boolean checkDebugger();

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <fstream>
#include <string>
#include "log_utils.h"

// 方法 1:ptrace 检测
bool checkPtrace() {
    // 尝试 trace 自己,如果被调试会失败
    if (ptrace(PTRACE_TRACEME, 0, nullptr, 0) == -1) {
        LOGE("Debugger detected via ptrace");
        return true;
    }
    ptrace(PTRACE_DETACH, 0, nullptr, 0);
    return false;
}

// 方法 2:检测 /proc/self/status 中的 TracerPid
bool checkTracerPid() {
    std::ifstream status("/proc/self/status");
    std::string line;
    while (std::getline(status, line)) {
        if (line.find("TracerPid:") != std::string::npos) {
            int pid = std::stoi(line.substr(line.find(":") + 1));
            if (pid != 0) {
                LOGE("Debugger detected via TracerPid: %d", pid);
                return true;
            }
            break;
        }
    }
    return false;
}

// 方法 3:检测调试端口
bool checkDebugPort() {
    // 检测 Android 的调试属性
    char prop[PROP_VALUE_MAX];
    __system_property_get("ro.debuggable", prop);
    if (strcmp(prop, "1") == 0) {
        LOGW("App is debuggable");
    }

    __system_property_get("debuggerd.active", prop);
    if (strcmp(prop, "1") == 0) {
        LOGE("Debuggerd active");
        return true;
    }

    return false;
}

// 综合检测
extern "C" JNIEXPORT jboolean JNICALL
Java_com_example_jnidemo_MainActivity_checkDebugger(JNIEnv *env, jobject thiz) {
    bool isDebugged = false;
    isDebugged |= checkPtrace();
    isDebugged |= checkTracerPid();
    isDebugged |= checkDebugPort();

    if (isDebugged) {
        // 发现调试,可以选择直接退出
        // exit(0);
        return JNI_TRUE;
    }

    return JNI_FALSE;
}

关键点

  • 反调试是安全防护的基础
  • 多种方法结合使用效果更好
  • 可以配合代码混淆、签名校验使用

示例 19:应用签名校验(防二次打包)

用途:所有商业应用的安全防护,防止盗版。

Java 层

java

运行

复制代码
public native boolean verifySignature(Context context);

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include <string>
#include <openssl/sha.h>
#include "log_utils.h"

// 预期的签名 SHA256(需要提前计算好)
const char* EXPECTED_SIGNATURE = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";

// 计算 SHA256
std::string sha256(const std::string& data) {
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256((const unsigned char*)data.c_str(), data.length(), hash);

    char hex[65];
    for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
        sprintf(hex + i * 2, "%02x", hash[i]);
    }
    return std::string(hex);
}

extern "C" JNIEXPORT jboolean JNICALL
Java_com_example_jnidemo_MainActivity_verifySignature(JNIEnv *env, jobject thiz, jobject context) {
    // 1. 获取 PackageManager
    jclass contextCls = env->GetObjectClass(context);
    jmethodID getPackageManagerId = env->GetMethodID(contextCls, "getPackageManager", 
                                                       "()Landroid/content/pm/PackageManager;");
    jobject pm = env->CallObjectMethod(context, getPackageManagerId);

    // 2. 获取 PackageInfo
    jmethodID getPackageNameId = env->GetMethodID(contextCls, "getPackageName", "()Ljava/lang/String;");
    jstring packageName = (jstring)env->CallObjectMethod(context, getPackageNameId);

    jclass pmCls = env->GetObjectClass(pm);
    jmethodID getPackageInfoId = env->GetMethodID(pmCls, "getPackageInfo", 
                                                    "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    jobject packageInfo = env->CallObjectMethod(pm, getPackageInfoId, packageName, 0x40); // GET_SIGNATURES

    // 3. 获取签名
    jclass packageInfoCls = env->GetObjectClass(packageInfo);
    jfieldID signaturesField = env->GetFieldID(packageInfoCls, "signatures", "[Landroid/content/pm/Signature;");
    jobjectArray signatures = (jobjectArray)env->GetObjectField(packageInfo, signaturesField);
    jobject signature = env->GetObjectArrayElement(signatures, 0);

    jclass signatureCls = env->GetObjectClass(signature);
    jmethodID toByteArrayId = env->GetMethodID(signatureCls, "toByteArray", "()[B");
    jbyteArray signatureBytes = (jbyteArray)env->CallObjectMethod(signature, toByteArrayId);

    // 4. 计算签名 SHA256
    jbyte* sigBytes = env->GetByteArrayElements(signatureBytes, nullptr);
    jsize sigLen = env->GetArrayLength(signatureBytes);
    std::string sigStr((char*)sigBytes, sigLen);
    std::string sigHash = sha256(sigStr);
    env->ReleaseByteArrayElements(signatureBytes, sigBytes, 0);

    // 5. 对比签名
    if (sigHash == EXPECTED_SIGNATURE) {
        LOGI("Signature verified");
        return JNI_TRUE;
    } else {
        LOGE("Signature mismatch! Expected: %s, Got: %s", EXPECTED_SIGNATURE, sigHash.c_str());
        return JNI_FALSE;
    }
}

关键点

  • 签名校验必须放在 C++ 层,Java 层容易被绕过
  • 预期签名需要提前计算并硬编码
  • 可以配合反调试、代码混淆使用

示例 20:串口通信(RS232/RS485)

用途:工业控制、智能家居、物联网网关、传感器数据采集。

Java 层

java

运行

复制代码
public native int openSerial(String device, int baudrate);
public native int sendData(byte[] data);
public native byte[] receiveData(int maxLen);
public native void closeSerial();

C++ 层

cpp

运行

复制代码
#include <jni.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <cstring>
#include "log_utils.h"

int serialFd = -1;

// 波特率映射
speed_t getBaudrate(int baudrate) {
    switch (baudrate) {
        case 9600: return B9600;
        case 19200: return B19200;
        case 38400: return B38400;
        case 57600: return B57600;
        case 115200: return B115200;
        case 230400: return B230400;
        case 460800: return B460800;
        case 921600: return B921600;
        default: return B9600;
    }
}

// 打开串口
extern "C" JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_openSerial(JNIEnv *env, jobject thiz, 
                                                   jstring device, jint baudrate) {
    const char* cDevice = env->GetStringUTFChars(device, nullptr);

    // 1. 打开串口设备
    serialFd = open(cDevice, O_RDWR | O_NOCTTY | O_NDELAY);
    if (serialFd == -1) {
        LOGE("Failed to open serial port: %s", cDevice);
        env->ReleaseStringUTFChars(device, cDevice);
        return -1;
    }

    // 2. 配置串口
    struct termios options;
    tcgetattr(serialFd, &options);

    // 设置波特率
    speed_t speed = getBaudrate(baudrate);
    cfsetispeed(&options, speed);
    cfsetospeed(&options, speed);

    // 8位数据位
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;
    // 无奇偶校验
    options.c_cflag &= ~PARENB;
    // 1位停止位
    options.c_cflag &= ~CSTOPB;
    // 本地连接,接收使能
    options.c_cflag |= (CLOCAL | CREAD);

    // 原始模式(不处理特殊字符)
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    options.c_oflag &= ~OPOST;

    // 应用配置
    tcsetattr(serialFd, TCSANOW, &options);

    // 清空缓冲区
    tcflush(serialFd, TCIOFLUSH);

    env->ReleaseStringUTFChars(device, cDevice);
    LOGI("Serial port opened: %s, baudrate: %d", cDevice, baudrate);
    return 0;
}

// 发送数据
extern "C" JNIEXPORT jint JNICALL
Java_com_example_jnidemo_MainActivity_sendData(JNIEnv *env, jobject thiz, jbyteArray data) {
    if (serialFd == -1) return -1;

    jbyte* cData = env->GetByteArrayElements(data, nullptr);
    jsize len = env->GetArrayLength(data);

    int bytesWritten = write(serialFd, cData, len);

    env->ReleaseByteArrayElements(data, cData, 0);
    return bytesWritten;
}

// 接收数据
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_example_jnidemo_MainActivity_receiveData(JNIEnv *env, jobject thiz, jint maxLen) {
    if (serialFd == -1) return nullptr;

    char* buffer = new char[maxLen];
    int bytesRead = read(serialFd, buffer, maxLen);

    if (bytesRead <= 0) {
        delete[] buffer;
        return nullptr;
    }

    jbyteArray result = env->NewByteArray(bytesRead);
    env->SetByteArrayRegion(result, 0, bytesRead, (jbyte*)buffer);

    delete[] buffer;
    return result;
}

// 关闭串口
extern "C" JNIEXPORT void JNICALL
Java_com_example_jnidemo_MainActivity_closeSerial(JNIEnv *env, jobject thiz) {
    if (serialFd != -1) {
        close(serialFd);
        serialFd = -1;
        LOGI("Serial port closed");
    }
}

关键点

  • 串口通信是物联网开发的基础
  • 需要 root 权限或系统签名才能访问串口设备
  • 注意配置数据位、停止位、奇偶校验

总结

这 20 个示例覆盖了 Android JNI 开发的所有主流场景,从基础的 Java-C++ 交互,到数据处理、音视频、AI、安全、物联网等高级应用。

相关推荐
xixixi777771 小时前
AI驱动安全变革:Axios零交互劫持云元数据+CVE-2026-40175,Claude Mythos加速至小时级,攻防不对称重构安全架构
人工智能·5g·ai·claude·攻击·多模态·安全架构
MRDONG11 小时前
从 Prompt 到智能体系统:Function Calling、Memory 与 Synthetic RAG 的全栈解析
人工智能·深度学习·神经网络·语言模型·自然语言处理·prompt
Deepoch1 小时前
基于 VLA 边缘计算的除草机器人自主作业技术研究
人工智能·开发板·具身模型·deepoc·除草机器人
!停2 小时前
C++入门STL容器string使用基础
开发语言·c++
m0_716765232 小时前
数据结构--栈的插入、删除、查找详解
开发语言·数据结构·c++·经验分享·学习·青少年编程·visual studio
ws2019072 小时前
智行未来,驱动变革:AUTO TECH China 2026 广州汽车技术展蓄势待发
人工智能·科技·汽车
qq_12084093712 小时前
Three.js 模型加载与线上稳定性实战:路径、跨域、缓存与降级全链路指南
开发语言·javascript·缓存·vue3
小超同学你好2 小时前
OpenClaw 深度解析与源代码导读 · 第2篇:Skills——能力扩展平面与源码中的「目录即技能」
人工智能·语言模型
空中湖2 小时前
光计算:用“光“代替“电“,AI算力的下一场革命
人工智能