目录
[示例 1:基础数学运算(Java → C++)](#示例 1:基础数学运算(Java → C++))
[Java 层(MainActivity.java)](#Java 层(MainActivity.java))
[C++ 层(native-lib.cpp)](#C++ 层(native-lib.cpp))
[示例 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:大文件 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: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;
}
关键点:
GetStringUTFChars和ReleaseStringUTFChars必须配对使用- 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 - 注意
Release的mode参数 - 创建新数组用
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 入口,用于保存JavaVMJNIEnv是线程私有的,子线程必须通过AttachCurrentThread获取- 跨线程传递 Java 对象必须使用全局引用(
NewGlobalRef或NewWeakGlobalRef) - 线程退出前必须调用
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、安全、物联网等高级应用。