【RK3576 安卓 JNI/NDK 系列 10】综合实战:RK3576 智能环境监测系统全实现 + 系列总结

目录

本章核心说明

一、项目整体规划

[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)回调接口定义

(2)主界面实现(MainActivity.java)

(3)布局文件(activity_main.xml)

四、编译运行与调试

[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. 核心功能需求

本项目整合系列所有核心知识点,实现以下工业级常用功能:

  1. 环境数据采集:通过 I2C 总线读取 SHT30 温湿度传感器数据,1 秒刷新一次,安卓 UI 实时显示
  2. 智能温控:可配置温度阈值,超过阈值自动通过 GPIO 开启散热风扇,低于阈值自动关闭
  3. 实时图像预处理:外接 USB 摄像头采集画面,通过 RGA 硬件加速完成图像缩放、格式转换,在 UI 上实时预览
  4. 异常状态回调:传感器读取失败、硬件异常时,通过 JNI 回调 Java 层弹窗提示
  5. 全链路线程安全:所有硬件操作均在子线程执行,不阻塞主线程,无 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. 编译运行

  1. 确认所有模块代码、布局文件、配置文件均已正确添加
  2. Android Studio 中选择 RK3576 开发板,点击 Run 安装 APK
  3. 打开 APP,授予摄像头、存储权限,即可看到温湿度数据实时刷新、摄像头灰度预览画面
  4. 用手捂住 SHT30 传感器,温度升高超过阈值后,风扇自动开启,温度回落自动关闭

3. 常见问题排查

  1. 硬件初始化失败:检查设备节点权限是否配置、SELinux 是否关闭、硬件接线是否正确
  2. 传感器数据读取失败 :检查 I2C 地址是否正确、接线是否虚焊、i2cdetect命令是否能扫描到设备
  3. 风扇不动作:检查 GPIO 编号是否正确、继电器模块供电是否正常、电平触发方式是否匹配
  4. 摄像头无画面:检查摄像头是否为 UVC 免驱、USB 权限是否授予、OTG 模式是否正确

五、系列整体总结

本系列从 0 到 1 完整讲解了 RK3576 平台安卓 JNI/NDK 底层开发的全流程,覆盖了从基础概念到工业级项目落地的所有核心知识点,整体知识体系总结如下:

1. 核心知识体系

  1. 基础概念层:搞懂了 JNI/NDK 的本质区别与核心作用,建立了安卓应用层到底层硬件的完整分层架构认知
  2. 环境搭建层:锁定了 RK3576 兼容性最优的开发环境版本,解决了新手最头疼的环境搭建、版本兼容问题
  3. JNI 语法层:掌握了 JNI 数据类型映射、方法签名、字符串 / 数组 / 对象操作、内存管理等核心语法,解决了类型转换、崩溃、内存泄漏等常见问题
  4. 构建系统层 :精通了 CMakeLists.txt 的完整配置,掌握了第三方库链接、编译优化、多文件管理等技能,解决了undefined reference等编译报错
  5. 驱动访问层:理解了 Linux "一切皆文件" 的核心思想,掌握了设备节点操作、ioctl 系统调用,解决了权限不足、硬件访问失败的核心问题
  6. 硬件实战层:完成了 GPIO 点灯、I2C 传感器读取、RGA 硬件加速三大核心硬件操作,实现了安卓 APP 对底层硬件的完全控制
  7. 项目落地层:整合所有知识点,完成了工业级智能环境监测终端项目,实现了从理论到实战的完整闭环

2. 新手核心避坑总览

  1. 版本兼容坑:NDK、CMake、SDK 版本必须与 RK3576 原厂固件匹配,不可盲目使用最新版
  2. JNI 语法坑:严格遵守 JNI 命名规则、类型映射规则,有 Get 必有 Release,避免内存泄漏与崩溃
  3. 权限管控坑:安卓下操作硬件必须配置设备节点权限,同时关闭 SELinux,否则会出现权限拒绝
  4. 线程安全坑:所有硬件操作必须放在子线程执行,不可阻塞主线程,避免 ANR
  5. 硬件操作坑:严格按照芯片手册、传感器手册操作,GPIO 编号、I2C 地址、硬件接线不可出错
  6. 内存管理坑:全局引用必须手动释放,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、底层驱动开发的保姆级教程输出。如果你在学习过程中遇到任何问题,或者有想学习的其他技术内容,都可以在评论区留言,我会一一回复。

感谢各位兄弟的一路陪伴,我们下个系列见!

相关推荐
阿拉斯攀登8 小时前
【RK3576 安卓 JNI/NDK 系列 09】RK3576 实战(三):JNI 调用 librga 实现 2D 硬件加速图像处理
android·驱动开发·rk3568·瑞芯微·rk安卓驱动·rk3576 rga加速
阿拉斯攀登21 小时前
第 19 篇 驱动性能优化与功耗优化实战
android·驱动开发·瑞芯微·嵌入式驱动·安卓驱动
阿拉斯攀登1 天前
第 20 篇 RK 平台 NPU / 硬件编解码驱动适配与安卓调用
android·驱动开发·瑞芯微·rk安卓驱动
阿拉斯攀登1 天前
第 12 篇 RK 平台安卓驱动实战 5:SPI 设备驱动开发,以 SPI 屏 / Flash 为例
android·驱动开发·rk3568·瑞芯微·嵌入式驱动·安卓驱动·spi 设备驱动
阿拉斯攀登1 天前
【RK3576 安卓 JNI/NDK 系列 02】保姆级环境搭建,从 0 到跑通第一个 JNI 程序
android studio·瑞芯微·嵌入式驱动·安卓驱动·安卓ndk环境搭建 jni入门
阿拉斯攀登2 天前
第 13 篇 输入设备驱动(触摸屏 / 按键)开发详解,Linux input 子系统全解析
android·linux·运维·驱动开发·rk3568·瑞芯微·rk安卓驱动
阿拉斯攀登2 天前
第 11 篇 RK 平台安卓驱动实战 4:I2C 设备驱动开发,以 OLED 屏为例
android·驱动开发·i2c·瑞芯微·嵌入式驱动·rk3576·嵌入式安卓
阿拉斯攀登2 天前
第 9 篇 RK 平台安卓驱动实战 2:中断驱动开发,按键中断的完整实现
驱动开发·嵌入式硬件·rk3568·中断·瑞芯微·rk3576·rk安卓驱动
阿拉斯攀登2 天前
第 14 篇 显示驱动(MIPI/LVDS 屏)适配与调试,DRM 框架详解
android·驱动开发·rk3568·瑞芯微·rk安卓驱动