目录
[1. 核心功能需求](#1. 核心功能需求)
[2. 硬件物料清单](#2. 硬件物料清单)
[3. 硬件接线规则](#3. 硬件接线规则)
[4. 软件分层架构](#4. 软件分层架构)
[1. build.gradle 配置](#1. build.gradle 配置)
[2. AndroidManifest.xml 权限配置](#2. AndroidManifest.xml 权限配置)
[3. CMakeLists.txt 配置](#3. CMakeLists.txt 配置)
[4. 项目目录结构](#4. 项目目录结构)
[1. C++ 层功能模块封装](#1. C++ 层功能模块封装)
[(1)GPIO 控制模块(gpio_control.h/cpp)](#(1)GPIO 控制模块(gpio_control.h/cpp))
[(2)I2C SHT30 传感器模块(sht30.h/cpp)](#(2)I2C SHT30 传感器模块(sht30.h/cpp))
[(3)RGA 图像处理模块(rga_process.h/cpp)](#(3)RGA 图像处理模块(rga_process.h/cpp))
[2. JNI 层接口实现(native-lib.cpp)](#2. JNI 层接口实现(native-lib.cpp))
[3. Java 层 UI 与业务逻辑实现](#3. Java 层 UI 与业务逻辑实现)
[1. 前置权限配置](#1. 前置权限配置)
[2. 编译运行](#2. 编译运行)
[3. 常见问题排查](#3. 常见问题排查)
[1. 核心知识体系](#1. 核心知识体系)
[2. 新手核心避坑总览](#2. 新手核心避坑总览)
[1. 安卓 HAL 层开发](#1. 安卓 HAL 层开发)
[2. RK3576 NPU 推理开发](#2. RK3576 NPU 推理开发)
[3. Linux 内核驱动开发](#3. Linux 内核驱动开发)
[4. 工业级项目优化](#4. 工业级项目优化)
[5. 音视频流媒体开发](#5. 音视频流媒体开发)
本章核心说明
本章为系列最终章,将前 9 章的所有知识点进行整合落地,完成一个可直接用于工业场景的RK3576 智能环境监测终端项目。项目完整覆盖 JNI 基础语法、NDK 构建、Linux 驱动访问、GPIO 硬件控制、I2C 传感器读取、RGA 硬件加速图像处理全流程,所有代码适配 RK3576 原厂安卓固件,新手可直接复制复用,同时完成整个系列的知识体系总结与进阶学习路线规划。
一、项目整体规划
1. 核心功能需求
本项目整合系列所有核心知识点,实现以下工业级常用功能:
- 环境数据采集:通过 I2C 总线读取 SHT30 温湿度传感器数据,1 秒刷新一次,安卓 UI 实时显示
- 智能温控:可配置温度阈值,超过阈值自动通过 GPIO 开启散热风扇,低于阈值自动关闭
- 实时图像预处理:外接 USB 摄像头采集画面,通过 RGA 硬件加速完成图像缩放、格式转换,在 UI 上实时预览
- 异常状态回调:传感器读取失败、硬件异常时,通过 JNI 回调 Java 层弹窗提示
- 全链路线程安全:所有硬件操作均在子线程执行,不阻塞主线程,无 ANR 风险
2. 硬件物料清单
表格
| 物料 | 规格说明 | 用途 |
|---|---|---|
| RK3576 开发板 | 任意厂商量产板,带安卓 10/11 固件 | 核心控制单元 |
| SHT30 温湿度传感器 | I2C 接口,工业级精度 | 环境温湿度采集 |
| 5V 散热风扇 + 继电器模块 | 3.3V 电平触发继电器 | 温度超限散热控制 |
| USB 摄像头 | UVC 协议免驱摄像头 | 实时画面采集 |
| 杜邦线、面包板 | 2.54mm 间距 | 硬件接线 |
| 12V/2A 电源 | 适配开发板供电 | 整机供电 |
3. 硬件接线规则
表格
| 外设模块 | 模块引脚 | RK3576 开发板引脚 |
|---|---|---|
| SHT30 传感器 | VCC | 3.3V |
| GND | GND | |
| SDA | I2C2_SDA | |
| SCL | I2C2_SCL | |
| 继电器模块 | VCC | 5V |
| GND | GND | |
| IN | GPIO3_A5(全局编号 101,bank 内偏移 5) | |
| 散热风扇 | 正极 | 继电器常开触点 NO |
| 负极 | 电源 GND | |
| USB 摄像头 | USB 接口 | 开发板 USB Host 口 |
4. 软件分层架构
完全贴合系列开篇的安卓底层开发架构,所有模块严格分层,可维护性强,工业级项目标准架构:
plaintext
┌─────────────────────────────────────────────────┐
│ 【应用层】Java UI + 业务逻辑 │
│ 功能:界面显示、阈值配置、摄像头预览、异常提示 │
├─────────────────────────────────────────────────┤
│ 【JNI层】Java ↔ C++ 交互桥梁 │
│ 功能:native接口封装、Java对象回调、类型转换 │
├─────────────────────────────────────────────────┤
│ 【原生层】C++ 核心功能模块 │
│ ├─ GPIO控制模块:风扇开关控制 │
│ ├─ I2C传感器模块:SHT30温湿度读取 │
│ ├─ RGA图像处理模块:硬件加速缩放、格式转换 │
│ └─ 工具模块:日志、线程、异常处理 │
├─────────────────────────────────────────────────┤
│ 【内核层】Linux 标准驱动 │
│ /dev/gpiochip3、/dev/i2c-2、/dev/rga、UVC驱动 │
├─────────────────────────────────────────────────┤
│ 【硬件层】RK3576 + 外设模块 │
└─────────────────────────────────────────────────┘
二、项目基础环境配置
1. build.gradle 配置
锁定系列统一的版本配置,适配 RK3576 arm64-v8a 架构,添加摄像头、网络等必要权限:
gradle
plugins {
id 'com.android.application'
}
android {
namespace 'com.heipiao.rk3576.envmonitor'
compileSdk 29
defaultConfig {
applicationId "com.heipiao.rk3576.envmonitor"
minSdk 29
targetSdk 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
abiFilters 'arm64-v8a' // 仅适配RK3576 64位架构
}
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
arguments "-DANDROID_STL=c++_static"
}
}
}
ndkVersion '21.4.7075529' // 锁定系列统一NDK版本
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.10.2'
}
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
// 摄像头预览库,简化UVC摄像头操作
implementation 'com.jiangdg.uvccamera:libuvccamera:1.2.8'
}
2. AndroidManifest.xml 权限配置
添加硬件操作、摄像头、网络等必要权限:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.heipiao.rk3576.envmonitor">
<!-- 硬件操作权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 摄像头功能声明 -->
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.usb.host" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.RK3576EnvMonitor"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3. CMakeLists.txt 配置
整合 GPIO、I2C、RGA 所有模块的库链接与头文件配置:
cmake
cmake_minimum_required(VERSION 3.10.2)
project("env_monitor")
# 锁定C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 头文件搜索路径
include_directories(
# 项目模块头文件
modules/gpio
modules/i2c
modules/rga
modules/utils
# RGA官方库头文件
3rdparty/rga/include
)
# 批量添加所有源文件
file(GLOB_RECURSE SOURCE_FILES
*.cpp
modules/gpio/*.cpp
modules/i2c/*.cpp
modules/rga/*.cpp
modules/utils/*.cpp
)
# 生成动态库
add_library(
env_monitor
SHARED
${SOURCE_FILES})
# 导入预编译librga库
add_library(rga SHARED IMPORTED)
set_target_properties(rga PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/3rdparty/rga/libs/${CMAKE_ANDROID_ARCH_ABI}/librga.so)
# 查找安卓系统库
find_library(log-lib log)
find_library(jnigraphics-lib jnigraphics)
# 链接所有库
target_link_libraries(
env_monitor
rga
${log-lib}
${jnigraphics-lib})
4. 项目目录结构
采用工业级模块化设计,每个功能独立封装,便于维护与扩展:
plaintext
app/src/main/
├── java/com/heipiao/rk3576/envmonitor/
│ ├── MainActivity.java # 主界面
│ ├── SensorCallback.java # 传感器数据回调接口
│ └── HardwareExceptionCallback.java # 硬件异常回调接口
├── cpp/
│ ├── 3rdparty/ # 第三方库(librga)
│ ├── modules/ # 功能模块
│ │ ├── gpio/ # GPIO控制模块
│ │ │ ├── gpio_control.h
│ │ │ └── gpio_control.cpp
│ │ ├── i2c/ # I2C传感器模块
│ │ │ ├── sht30.h
│ │ │ └── sht30.cpp
│ │ ├── rga/ # RGA图像处理模块
│ │ │ ├── rga_process.h
│ │ │ └── rga_process.cpp
│ │ └── utils/ # 工具模块
│ │ ├── log_utils.h
│ │ └── thread_utils.h
│ ├── native-lib.cpp # JNI接口实现
│ └── CMakeLists.txt
├── res/ # 布局与资源文件
└── AndroidManifest.xml
三、核心模块代码实现
1. C++ 层功能模块封装
所有模块均采用单例模式封装,线程安全,可直接复用,核心代码均来自系列前序章节的实战内容,做了工业级封装优化。
(1)GPIO 控制模块(gpio_control.h/cpp)
cpp
运行
// gpio_control.h
#ifndef GPIO_CONTROL_H
#define GPIO_CONTROL_H
class GpioControl {
public:
static GpioControl* getInstance();
// 初始化GPIO引脚为输出模式,default_level默认电平
int init(int gpio_bank, int offset, int default_level);
// 设置引脚电平
int setLevel(int level);
// 获取引脚当前电平
int getLevel();
// 释放资源
void release();
private:
GpioControl() = default;
~GpioControl();
static GpioControl* instance;
int gpio_chip_fd = -1;
int line_fd = -1;
};
#endif // GPIO_CONTROL_H
cpp
运行
// gpio_control.cpp
#include "gpio_control.h"
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <linux/gpio.h>
#include "log_utils.h"
GpioControl* GpioControl::instance = nullptr;
GpioControl* GpioControl::getInstance() {
if (instance == nullptr) {
instance = new GpioControl();
}
return instance;
}
GpioControl::~GpioControl() {
release();
}
int GpioControl::init(int gpio_bank, int offset, int default_level) {
char chip_path[64];
sprintf(chip_path, "/dev/gpiochip%d", gpio_bank);
// 打开GPIO芯片
gpio_chip_fd = open(chip_path, O_RDWR);
if (gpio_chip_fd < 0) {
LOGE("打开GPIO芯片失败:%s", strerror(errno));
return -1;
}
// 获取引脚句柄
struct gpiohandle_request req;
memset(&req, 0, sizeof(req));
req.lineoffsets[0] = offset;
req.lines = 1;
req.flags = GPIOHANDLE_REQUEST_OUTPUT;
req.default_values[0] = default_level;
strcpy(req.consumer_label, "env_monitor_fan");
int ret = ioctl(gpio_chip_fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
if (ret < 0) {
LOGE("获取GPIO句柄失败:%s", strerror(errno));
close(gpio_chip_fd);
gpio_chip_fd = -1;
return -1;
}
line_fd = req.fd;
LOGD("GPIO初始化成功,bank=%d, offset=%d", gpio_bank, offset);
return 0;
}
int GpioControl::setLevel(int level) {
if (line_fd < 0) {
LOGE("GPIO未初始化");
return -1;
}
struct gpiohandle_data data;
memset(&data, 0, sizeof(data));
data.values[0] = level;
int ret = ioctl(line_fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
if (ret < 0) {
LOGE("设置GPIO电平失败:%s", strerror(errno));
return -1;
}
LOGD("GPIO电平设置为:%d", level);
return 0;
}
int GpioControl::getLevel() {
if (line_fd < 0) {
LOGE("GPIO未初始化");
return -1;
}
struct gpiohandle_data data;
memset(&data, 0, sizeof(data));
int ret = ioctl(line_fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
if (ret < 0) {
LOGE("读取GPIO电平失败:%s", strerror(errno));
return -1;
}
return data.values[0];
}
void GpioControl::release() {
if (line_fd >= 0) {
close(line_fd);
line_fd = -1;
}
if (gpio_chip_fd >= 0) {
close(gpio_chip_fd);
gpio_chip_fd = -1;
}
LOGD("GPIO资源已释放");
}
(2)I2C SHT30 传感器模块(sht30.h/cpp)
cpp
运行
// sht30.h
#ifndef SHT30_H
#define SHT30_H
class Sht30Sensor {
public:
static Sht30Sensor* getInstance();
// 初始化I2C总线
int init(int i2c_bus_num, uint8_t slave_addr = 0x44);
// 读取温湿度数据,temperature输出温度,humidity输出湿度
int readData(float *temperature, float *humidity);
// 释放资源
void release();
private:
Sht30Sensor() = default;
~Sht30Sensor();
static Sht30Sensor* instance;
int i2c_fd = -1;
uint8_t slave_addr;
// 通用I2C读写函数
int i2cWriteRead(uint8_t *write_buf, uint8_t write_len, uint8_t *read_buf, uint8_t read_len);
};
#endif // SHT30_H
cpp
运行
// sht30.cpp
#include "sht30.h"
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include "log_utils.h"
Sht30Sensor* Sht30Sensor::instance = nullptr;
Sht30Sensor* Sht30Sensor::getInstance() {
if (instance == nullptr) {
instance = new Sht30Sensor();
}
return instance;
}
Sht30Sensor::~Sht30Sensor() {
release();
}
int Sht30Sensor::init(int i2c_bus_num, uint8_t slave_addr) {
char dev_path[64];
sprintf(dev_path, "/dev/i2c-%d", i2c_bus_num);
i2c_fd = open(dev_path, O_RDWR);
if (i2c_fd < 0) {
LOGE("打开I2C设备失败:%s", strerror(errno));
return -1;
}
this->slave_addr = slave_addr;
LOGD("SHT30初始化成功,I2C总线:%d,地址:0x%02X", i2c_bus_num, slave_addr);
return 0;
}
int Sht30Sensor::i2cWriteRead(uint8_t *write_buf, uint8_t write_len, uint8_t *read_buf, uint8_t read_len) {
if (i2c_fd < 0) {
LOGE("I2C设备未初始化");
return -1;
}
struct i2c_msg msgs[2];
struct i2c_rdwr_ioctl_data rdwr_data;
msgs[0].addr = slave_addr;
msgs[0].flags = 0;
msgs[0].len = write_len;
msgs[0].buf = write_buf;
msgs[1].addr = slave_addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = read_len;
msgs[1].buf = read_buf;
rdwr_data.msgs = msgs;
rdwr_data.nmsgs = 2;
int ret = ioctl(i2c_fd, I2C_RDWR, &rdwr_data);
if (ret < 0) {
LOGE("I2C读写失败:%s", strerror(errno));
return -1;
}
return 0;
}
int Sht30Sensor::readData(float *temperature, float *humidity) {
if (temperature == nullptr || humidity == nullptr) {
LOGE("输出参数为空");
return -1;
}
uint8_t write_buf[2] = {0x2C, 0x06}; // 单次高精度测量命令
uint8_t read_buf[6] = {0};
int ret = i2cWriteRead(write_buf, 2, read_buf, 6);
if (ret < 0) {
LOGE("SHT30读取数据失败");
return -1;
}
uint16_t raw_temp = (read_buf[0] << 8) | read_buf[1];
uint16_t raw_hum = (read_buf[3] << 8) | read_buf[4];
*temperature = -45.0f + 175.0f * ((float)raw_temp / 65535.0f);
*humidity = 100.0f * ((float)raw_hum / 65535.0f);
LOGD("SHT30读取成功:温度=%.2f℃,湿度=%.2f%%", *temperature, *humidity);
return 0;
}
void Sht30Sensor::release() {
if (i2c_fd >= 0) {
close(i2c_fd);
i2c_fd = -1;
}
LOGD("SHT30资源已释放");
}
(3)RGA 图像处理模块(rga_process.h/cpp)
cpp
运行
// rga_process.h
#ifndef RGA_PROCESS_H
#define RGA_PROCESS_H
#include <stdint.h>
class RgaProcess {
public:
static RgaProcess* getInstance();
// 初始化RGA引擎
int init();
// 图像缩放+转灰度图
int rgbaScaleToGray(uint8_t *src_buf, int src_w, int src_h, uint8_t *dst_buf, int dst_w, int dst_h);
// 释放资源
void release();
private:
RgaProcess() = default;
~RgaProcess();
static RgaProcess* instance;
bool is_init = false;
};
#endif // RGA_PROCESS_H
cpp
运行
// rga_process.cpp
#include "rga_process.h"
#include "RgaApi.h"
#include "im2d_type.h"
#include "log_utils.h"
RgaProcess* RgaProcess::instance = nullptr;
RgaProcess* RgaProcess::getInstance() {
if (instance == nullptr) {
instance = new RgaProcess();
}
return instance;
}
RgaProcess::~RgaProcess() {
release();
}
int RgaProcess::init() {
if (is_init) {
return 0;
}
int ret = RgaInit();
if (ret != 0) {
LOGE("RGA初始化失败,错误码:%d", ret);
return -1;
}
is_init = true;
LOGD("RGA初始化成功,版本号:%d", c_RkRgaGetVersion());
return 0;
}
int RgaProcess::rgbaScaleToGray(uint8_t *src_buf, int src_w, int src_h, uint8_t *dst_buf, int dst_w, int dst_h) {
if (!is_init) {
LOGE("RGA未初始化");
return -1;
}
if (src_buf == nullptr || dst_buf == nullptr) {
LOGE("图像缓冲区为空");
return -1;
}
rga_info_t src_info;
memset(&src_info, 0, sizeof(rga_info_t));
src_info.src.vir_addr = src_buf;
src_info.src.w = src_w;
src_info.src.h = src_h;
src_info.src.wstride = src_w;
src_info.src.hstride = src_h;
src_info.src.format = HAL_PIXEL_FORMAT_RGBA_8888;
rga_info_t dst_info;
memset(&dst_info, 0, sizeof(dst_info_t));
dst_info.dst.vir_addr = dst_buf;
dst_info.dst.w = dst_w;
dst_info.dst.h = dst_h;
dst_info.dst.wstride = dst_w;
dst_info.dst.hstride = dst_h;
dst_info.dst.format = HAL_PIXEL_FORMAT_GRAY_8;
int ret = c_RkRgaBlit(&src_info, &dst_info, nullptr);
if (ret != 0) {
LOGE("RGA图像处理失败,错误码:%d", ret);
return -1;
}
LOGD("RGA图像处理完成:%dx%d RGBA → %dx%d GRAY", src_w, src_h, dst_w, dst_h);
return 0;
}
void RgaProcess::release() {
if (is_init) {
RgaDeInit();
is_init = false;
}
LOGD("RGA资源已释放");
}
2. JNI 层接口实现(native-lib.cpp)
封装 Java 层可调用的 native 接口,实现硬件初始化、数据读取、控制指令、Java 回调设置:
cpp
运行
#include <jni.h>
#include <string>
#include "modules/gpio/gpio_control.h"
#include "modules/i2c/sht30.h"
#include "modules/rga/rga_process.h"
#include "modules/utils/log_utils.h"
#include <android/bitmap.h>
// 全局回调对象引用
static jobject g_sensor_callback = nullptr;
static jobject g_exception_callback = nullptr;
static jmethodID g_on_data_received_mid = nullptr;
static jmethodID g_on_exception_mid = nullptr;
// 硬件初始化
extern "C" JNIEXPORT jint JNICALL
Java_com_heipiao_rk3576_envmonitor_MainActivity_hardwareInit(JNIEnv *env, jobject thiz) {
// 初始化SHT30传感器,I2C-2总线
int ret = Sht30Sensor::getInstance()->init(2);
if (ret != 0) {
return -1;
}
// 初始化GPIO风扇控制,GPIO3_A5,默认低电平(关闭)
ret = GpioControl::getInstance()->init(3, 5, 0);
if (ret != 0) {
Sht30Sensor::getInstance()->release();
return -2;
}
// 初始化RGA硬件加速
ret = RgaProcess::getInstance()->init();
if (ret != 0) {
Sht30Sensor::getInstance()->release();
GpioControl::getInstance()->release();
return -3;
}
LOGD("所有硬件初始化成功");
return 0;
}
// 设置传感器数据回调
extern "C" JNIEXPORT void JNICALL
Java_com_heipiao_rk3576_envmonitor_MainActivity_setSensorCallback(JNIEnv *env, jobject thiz, jobject callback) {
if (g_sensor_callback != nullptr) {
env->DeleteGlobalRef(g_sensor_callback);
}
g_sensor_callback = env->NewGlobalRef(callback);
jclass callback_class = env->GetObjectClass(g_sensor_callback);
g_on_data_received_mid = env->GetMethodID(callback_class, "onDataReceived", "(FF)V");
}
// 设置异常回调
extern "C" JNIEXPORT void JNICALL
Java_com_heipiao_rk3576_envmonitor_MainActivity_setExceptionCallback(JNIEnv *env, jobject thiz, jobject callback) {
if (g_exception_callback != nullptr) {
env->DeleteGlobalRef(g_exception_callback);
}
g_exception_callback = env->NewGlobalRef(callback);
jclass callback_class = env->GetObjectClass(g_exception_callback);
g_on_exception_mid = env->GetMethodID(callback_class, "onException", "(Ljava/lang/String;)V");
}
// 读取温湿度数据
extern "C" JNIEXPORT jint JNICALL
Java_com_heipiao_rk3576_envmonitor_MainActivity_readSensorData(JNIEnv *env, jobject thiz) {
float temp, hum;
int ret = Sht30Sensor::getInstance()->readData(&temp, &hum);
if (ret != 0) {
// 回调异常
if (g_exception_callback != nullptr && g_on_exception_mid != nullptr) {
jstring error_msg = env->NewStringUTF("传感器读取失败");
env->CallVoidMethod(g_exception_callback, g_on_exception_mid, error_msg);
}
return -1;
}
// 回调数据
if (g_sensor_callback != nullptr && g_on_data_received_mid != nullptr) {
env->CallVoidMethod(g_sensor_callback, g_on_data_received_mid, temp, hum);
}
return 0;
}
// 控制风扇开关
extern "C" JNIEXPORT jint JNICALL
Java_com_heipiao_rk3576_envmonitor_MainActivity_setFanState(JNIEnv *env, jobject thiz, jint is_on) {
return GpioControl::getInstance()->setLevel(is_on);
}
// RGA图像处理
extern "C" JNIEXPORT jint JNICALL
Java_com_heipiao_rk3576_envmonitor_MainActivity_rgaProcessImage(JNIEnv *env, jobject thiz,
jobject src_bitmap, jobject dst_bitmap) {
AndroidBitmapInfo src_info;
uint8_t *src_buf;
int ret = AndroidBitmap_getInfo(env, src_bitmap, &src_info);
if (ret != 0) return -1;
AndroidBitmap_lockPixels(env, src_bitmap, (void **)&src_buf);
AndroidBitmapInfo dst_info;
uint8_t *dst_buf;
ret = AndroidBitmap_getInfo(env, dst_bitmap, &dst_info);
if (ret != 0) {
AndroidBitmap_unlockPixels(env, src_bitmap);
return -1;
}
AndroidBitmap_lockPixels(env, dst_bitmap, (void **)&dst_buf);
ret = RgaProcess::getInstance()->rgbaScaleToGray(src_buf, src_info.width, src_info.height,
dst_buf, dst_info.width, dst_info.height);
AndroidBitmap_unlockPixels(env, src_bitmap);
AndroidBitmap_unlockPixels(env, dst_bitmap);
return ret;
}
// 释放所有硬件资源
extern "C" JNIEXPORT void JNICALL
Java_com_heipiao_rk3576_envmonitor_MainActivity_hardwareRelease(JNIEnv *env, jobject thiz) {
Sht30Sensor::getInstance()->release();
GpioControl::getInstance()->release();
RgaProcess::getInstance()->release();
if (g_sensor_callback != nullptr) {
env->DeleteGlobalRef(g_sensor_callback);
g_sensor_callback = nullptr;
}
if (g_exception_callback != nullptr) {
env->DeleteGlobalRef(g_exception_callback);
g_exception_callback = nullptr;
}
LOGD("所有硬件资源已释放");
}
3. Java 层 UI 与业务逻辑实现
(1)回调接口定义
java
运行
// SensorCallback.java
package com.heipiao.rk3576.envmonitor;
public interface SensorCallback {
void onDataReceived(float temperature, float humidity);
}
java
运行
// HardwareExceptionCallback.java
package com.heipiao.rk3576.envmonitor;
public interface HardwareExceptionCallback {
void onException(String errorMsg);
}
(2)主界面实现(MainActivity.java)
java
运行
package com.heipiao.rk3576.envmonitor;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.jiangdg.uvccamera.UVCCameraHelper;
import com.jiangdg.uvccamera.callback.UVCCameraCallback;
public class MainActivity extends AppCompatActivity implements SensorCallback, HardwareExceptionCallback {
private TextView tvTemperature;
private TextView tvHumidity;
private TextView tvFanState;
private EditText etTempThreshold;
private ImageView ivCameraPreview;
// 温度阈值,默认30℃
private float tempThreshold = 30.0f;
private boolean isFanOn = false;
private boolean isHardwareInitSuccess = false;
// 数据刷新线程
private Handler mainHandler = new Handler(Looper.getMainLooper());
private Runnable sensorRefreshRunnable;
// 摄像头辅助类
private UVCCameraHelper uvcCameraHelper;
static {
System.loadLibrary("env_monitor");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 绑定UI控件
initView();
// 设置回调
setSensorCallback(this);
setExceptionCallback(this);
// 初始化硬件
initHardware();
// 初始化摄像头
initCamera();
// 启动传感器数据刷新
startSensorRefresh();
}
private void initView() {
tvTemperature = findViewById(R.id.tv_temperature);
tvHumidity = findViewById(R.id.tv_humidity);
tvFanState = findViewById(R.id.tv_fan_state);
etTempThreshold = findViewById(R.id.et_temp_threshold);
ivCameraPreview = findViewById(R.id.iv_camera_preview);
etTempThreshold.setText(String.valueOf(tempThreshold));
// 阈值修改监听
etTempThreshold.setOnEditorActionListener((v, actionId, event) -> {
try {
tempThreshold = Float.parseFloat(etTempThreshold.getText().toString());
Toast.makeText(this, "温度阈值已设置为:" + tempThreshold + "℃", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(this, "请输入有效的温度阈值", Toast.LENGTH_SHORT).show();
}
return false;
});
}
private void initHardware() {
new Thread(() -> {
int ret = hardwareInit();
runOnUiThread(() -> {
if (ret == 0) {
isHardwareInitSuccess = true;
Toast.makeText(this, "硬件初始化成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "硬件初始化失败,错误码:" + ret, Toast.LENGTH_LONG).show();
}
});
}).start();
}
private void initCamera() {
uvcCameraHelper = UVCCameraHelper.getInstance();
uvcCameraHelper.init(this);
uvcCameraHelper.setUVCCameraCallback(new UVCCameraCallback() {
@Override
public void onPreviewFrame(Bitmap bitmap) {
// 摄像头预览帧回调,通过RGA硬件加速处理
if (!isHardwareInitSuccess) return;
Bitmap dstBitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ALPHA_8);
rgaProcessImage(bitmap, dstBitmap);
runOnUiThread(() -> ivCameraPreview.setImageBitmap(dstBitmap));
}
@Override
public void onCameraAttached() {
runOnUiThread(() -> Toast.makeText(MainActivity.this, "摄像头已连接", Toast.LENGTH_SHORT).show());
}
@Override
public void onCameraDetached() {
runOnUiThread(() -> Toast.makeText(MainActivity.this, "摄像头已断开", Toast.LENGTH_SHORT).show());
}
@Override
public void onCameraError(String errorMsg) {
runOnUiThread(() -> Toast.makeText(MainActivity.this, "摄像头异常:" + errorMsg, Toast.LENGTH_LONG).show());
}
});
uvcCameraHelper.openCamera();
}
private void startSensorRefresh() {
sensorRefreshRunnable = new Runnable() {
@Override
public void run() {
if (isHardwareInitSuccess) {
// 读取传感器数据
readSensorData();
}
// 1秒后再次执行
mainHandler.postDelayed(this, 1000);
}
};
mainHandler.post(sensorRefreshRunnable);
}
// 传感器数据回调
@Override
public void onDataReceived(float temperature, float humidity) {
runOnUiThread(() -> {
tvTemperature.setText(String.format("%.2f ℃", temperature));
tvHumidity.setText(String.format("%.2f %%", humidity));
// 智能温控逻辑
if (temperature >= tempThreshold && !isFanOn) {
// 温度超限,开启风扇
new Thread(() -> setFanState(1)).start();
isFanOn = true;
tvFanState.setText("风扇状态:开启");
} else if (temperature < tempThreshold - 2 && isFanOn) {
// 温度回落2℃,关闭风扇,避免频繁启停
new Thread(() -> setFanState(0)).start();
isFanOn = false;
tvFanState.setText("风扇状态:关闭");
}
});
}
// 硬件异常回调
@Override
public void onException(String errorMsg) {
runOnUiThread(() -> Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show());
}
@Override
protected void onDestroy() {
super.onDestroy();
mainHandler.removeCallbacks(sensorRefreshRunnable);
uvcCameraHelper.closeCamera();
uvcCameraHelper.release();
// 释放硬件资源
hardwareRelease();
}
// JNI native方法定义
public native int hardwareInit();
public native void setSensorCallback(SensorCallback callback);
public native void setExceptionCallback(HardwareExceptionCallback callback);
public native int readSensorData();
public native int setFanState(int is_on);
public native int rgaProcessImage(Bitmap srcBitmap, Bitmap dstBitmap);
public native void hardwareRelease();
}
(3)布局文件(activity_main.xml)
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="RK3576智能环境监测终端"
android:textSize="24sp"
android:gravity="center"
android:layout_marginBottom="20dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:spacing="20dp"
android:layout_marginBottom="20dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:background="#E8F4F8"
android:padding="15dp"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="环境温度"
android:textSize="16sp"/>
<TextView
android:id="@+id/tv_temperature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-- ℃"
android:textSize="28sp"
android:textColor="#FF5722"
android:layout_marginTop="10dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:background="#E8F4F8"
android:padding="15dp"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="环境湿度"
android:textSize="16sp"/>
<TextView
android:id="@+id/tv_humidity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-- %"
android:textSize="28sp"
android:textColor="#2196F3"
android:layout_marginTop="10dp"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="温度阈值:"
android:textSize="16sp"
android:layout_gravity="center_vertical"/>
<EditText
android:id="@+id/et_temp_threshold"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:hint="阈值"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" ℃"
android:textSize="16sp"
android:layout_gravity="center_vertical"/>
<TextView
android:id="@+id/tv_fan_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="风扇状态:关闭"
android:textSize="16sp"
android:layout_marginStart="30dp"
android:layout_gravity="center_vertical"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RGA硬件加速灰度预览"
android:textSize="16sp"
android:layout_marginBottom="10dp"/>
<ImageView
android:id="@+id/iv_camera_preview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="fitCenter"
android:background="#000000"/>
</LinearLayout>
四、编译运行与调试
1. 前置权限配置
运行前执行 adb 命令,配置设备节点权限与 SELinux:
bash
运行
adb root
adb remount
# 配置GPIO、I2C、RGA设备节点权限
adb shell chmod 777 /dev/gpiochip*
adb shell chmod 777 /dev/i2c-*
adb shell chmod 777 /dev/rga
# 临时关闭SELinux
adb shell setenforce 0
2. 编译运行
- 确认所有模块代码、布局文件、配置文件均已正确添加
- Android Studio 中选择 RK3576 开发板,点击 Run 安装 APK
- 打开 APP,授予摄像头、存储权限,即可看到温湿度数据实时刷新、摄像头灰度预览画面
- 用手捂住 SHT30 传感器,温度升高超过阈值后,风扇自动开启,温度回落自动关闭
3. 常见问题排查
- 硬件初始化失败:检查设备节点权限是否配置、SELinux 是否关闭、硬件接线是否正确
- 传感器数据读取失败 :检查 I2C 地址是否正确、接线是否虚焊、
i2cdetect命令是否能扫描到设备 - 风扇不动作:检查 GPIO 编号是否正确、继电器模块供电是否正常、电平触发方式是否匹配
- 摄像头无画面:检查摄像头是否为 UVC 免驱、USB 权限是否授予、OTG 模式是否正确
五、系列整体总结
本系列从 0 到 1 完整讲解了 RK3576 平台安卓 JNI/NDK 底层开发的全流程,覆盖了从基础概念到工业级项目落地的所有核心知识点,整体知识体系总结如下:
1. 核心知识体系
- 基础概念层:搞懂了 JNI/NDK 的本质区别与核心作用,建立了安卓应用层到底层硬件的完整分层架构认知
- 环境搭建层:锁定了 RK3576 兼容性最优的开发环境版本,解决了新手最头疼的环境搭建、版本兼容问题
- JNI 语法层:掌握了 JNI 数据类型映射、方法签名、字符串 / 数组 / 对象操作、内存管理等核心语法,解决了类型转换、崩溃、内存泄漏等常见问题
- 构建系统层 :精通了 CMakeLists.txt 的完整配置,掌握了第三方库链接、编译优化、多文件管理等技能,解决了
undefined reference等编译报错 - 驱动访问层:理解了 Linux "一切皆文件" 的核心思想,掌握了设备节点操作、ioctl 系统调用,解决了权限不足、硬件访问失败的核心问题
- 硬件实战层:完成了 GPIO 点灯、I2C 传感器读取、RGA 硬件加速三大核心硬件操作,实现了安卓 APP 对底层硬件的完全控制
- 项目落地层:整合所有知识点,完成了工业级智能环境监测终端项目,实现了从理论到实战的完整闭环
2. 新手核心避坑总览
- 版本兼容坑:NDK、CMake、SDK 版本必须与 RK3576 原厂固件匹配,不可盲目使用最新版
- JNI 语法坑:严格遵守 JNI 命名规则、类型映射规则,有 Get 必有 Release,避免内存泄漏与崩溃
- 权限管控坑:安卓下操作硬件必须配置设备节点权限,同时关闭 SELinux,否则会出现权限拒绝
- 线程安全坑:所有硬件操作必须放在子线程执行,不可阻塞主线程,避免 ANR
- 硬件操作坑:严格按照芯片手册、传感器手册操作,GPIO 编号、I2C 地址、硬件接线不可出错
- 内存管理坑:全局引用必须手动释放,Bitmap 像素必须锁定与解锁,避免内存泄漏
六、后续进阶学习路线
完成本系列学习后,你已经掌握了 RK3576 安卓底层开发的核心能力,可根据需求选择以下进阶方向:
1. 安卓 HAL 层开发
深入学习安卓硬件抽象层(HAL)开发,实现自定义硬件的 HAL 模块,对接安卓 Framework 层,实现系统级硬件服务,摆脱应用层权限限制,是工业级安卓设备开发的核心技能。
2. RK3576 NPU 推理开发
学习瑞芯微 RKNN-Toolkit2 工具链,通过 JNI 调用 librknn_api 库,实现 AI 模型在 RK3576 NPU 上的端侧推理,完成人脸识别、物体检测、工业缺陷检测等 AIoT 项目。
3. Linux 内核驱动开发
深入学习 Linux 字符设备驱动开发,为自定义外设编写内核驱动,实现更复杂的硬件控制、中断处理、DMA 传输,真正做到从内核到应用的全栈开发。
4. 工业级项目优化
学习安卓系统裁剪、固件定制、实时性优化、功耗优化、稳定性保障等工业级项目必备技能,实现产品化落地。
5. 音视频流媒体开发
结合 RK3576 的硬件编解码能力(MPP 库),通过 JNI 调用 librockchip_mpp 库,实现视频编解码、RTSP/RTMP 流媒体推流,完成安防监控、视频会议等项目。
系列收尾
本系列到这里就全部结束了,从最基础的 JNI 概念,到最终的工业级项目落地,全程围绕 RK3576 平台的实际开发场景,所有代码均可直接复用。
我是黒漂技术佬,专注于嵌入式安卓、AIoT、底层驱动开发的保姆级教程输出。如果你在学习过程中遇到任何问题,或者有想学习的其他技术内容,都可以在评论区留言,我会一一回复。
感谢各位兄弟的一路陪伴,我们下个系列见!