news 2026/5/20 12:39:04

基于RV1126开发板的嵌入式AI算法全流程开发实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于RV1126开发板的嵌入式AI算法全流程开发实战指南

1. 项目概述:从一块开发板到AI应用落地的全链路实践

最近几年,嵌入式AI的火热程度有目共睹,从智能门锁的人脸识别到工业质检的缺陷检测,AI算法正以前所未有的速度“下沉”到各种终端设备中。而在这个浪潮里,瑞芯微的RV1126芯片凭借其出色的性价比和AI算力,成为了众多开发者进行边缘AI应用开发的首选平台之一。我手头正好有一块基于RV1126的开发板,最近用它完整地走了一遍从算法选型、模型训练、转换部署到性能优化的全流程。这个过程远比单纯调用一个现成的API复杂,但也正是这种“从零到一”的构建,让我对嵌入式AI开发的细节和挑战有了更深的体会。今天,我就把这套基于RV1126开发板的AI算法开发流程拆解开来,分享给同样想踏入这个领域或者正在实践中遇到瓶颈的朋友们。无论你是刚接触嵌入式的新手,还是有一定经验的开发者,希望这篇从实战中总结出来的流程指南,能帮你少走些弯路,更快地把你的AI想法变成在开发板上稳定运行的现实。

RV1126是一颗集成了双核Cortex-A7 CPU和2.0 TOPS NPU(神经网络处理单元)的SoC。它的核心价值在于,为成本敏感、功耗受限的嵌入式场景提供了一个能本地运行中等复杂度神经网络模型的解决方案。我们这套开发流程,就是围绕如何高效利用这颗芯片的CPU和NPU资源,将一个在PC端训练好的AI模型,经过一系列“加工”和“适配”,最终部署到RV1126开发板上并稳定执行的全过程。这中间涉及到工具链的搭建、模型的转换与量化、驱动的集成、应用的编写以及最终的性能调优,每一个环节都有其门道。

2. 开发环境搭建与工具链解析

工欲善其事,必先利其器。在开始具体的算法开发之前,一个稳定、高效的开发环境是基石。对于RV1126开发,环境搭建主要分为两大部分:一是用于模型训练和前期验证的PC端环境;二是针对RV1126板卡本身的交叉编译与烧录环境。

2.1 PC端模型开发环境配置

在PC上,我们主要进行算法的原型设计、模型训练和初步验证。这里我强烈推荐使用Anaconda来管理Python环境,它能很好地解决不同项目间依赖包版本冲突的问题。

首先,创建一个独立的conda环境,比如命名为rv1126_ai,并指定Python版本(建议3.8,兼容性较好):

conda create -n rv1126_ai python=3.8 conda activate rv1126_ai

接下来是深度学习框架的选择。虽然RV1126的NPU官方对PyTorch和TensorFlow都有相应的模型转换工具支持,但从社区生态和模型转换的成熟度来看,PyTorch是目前更主流、也更推荐的选择。许多最新的模型和研究工作都首发于PyTorch,其动态图特性也更利于研究和调试。

# 安装PyTorch (以CUDA 11.3为例,请根据你的显卡驱动选择对应版本) pip install torch==1.10.0+cu113 torchvision==0.11.1+cu113 torchaudio==0.10.0+cu113 -f https://download.pytorch.org/whl/cu113/torch_stable.html # 安装常用的数据处理和可视化库 pip install numpy opencv-python pillow matplotlib scikit-learn pandas

注意:PyTorch的版本并非越新越好。务必后续与瑞芯微提供的模型转换工具(RKNN-Toolkit2)的版本要求进行核对。RKNN-Toolkit2对PyTorch的版本有明确的兼容性列表,安装前最好先查阅官方文档,避免后续转换时出现无法识别的算子或接口错误。

除了深度学习框架,一个关键的PC端工具是RKNN-Toolkit2。这是瑞芯微官方提供的模型转换、量化和推理工具套件。它运行在PC上,负责将训练好的PyTorch(.pt)或TensorFlow(.pb)模型,转换成RV1126 NPU能够高效执行的专用格式——RKNN模型。你需要从瑞芯微的开发者网站下载对应版本,通常也是一个Python包,通过pip安装即可。安装后,可以通过python -c "import rknn; print(rknn.__version__)"来验证。

2.2 RV1126交叉编译与系统烧录环境

我们的最终目标是让程序在ARM架构的RV1126开发板上跑起来,因此需要在x86_64的PC上搭建交叉编译环境。瑞芯微会为每一款芯片提供完整的SDK(软件开发工具包),里面包含了交叉编译工具链、内核源码、根文件系统等。

  1. 获取SDK:从开发板供应商或瑞芯微官方渠道获取RV1126的Linux SDK。这个SDK包通常很大,包含了构建整个系统所需的一切。
  2. 安装依赖:根据SDK中的文档,安装必要的系统依赖包,如build-essential,libssl-dev,bison,flex等。
  3. 设置环境变量:SDK中会有一个build.sh或类似的脚本,以及一个envsetup.sh脚本。执行source envsetup.sh会设置好交叉编译工具链(如arm-rockchip830-linux-uclibcgnueabihf-)的路径和其他必要的环境变量。之后,你使用make等命令编译出的就是ARM架构的可执行文件了。
  4. 系统镜像构建与烧录:SDK支持全自动构建整个Linux系统镜像(包括U-Boot、Kernel、Rootfs)。对于初学者,可以直接使用供应商提供的预编译镜像进行烧录。烧录工具一般使用瑞芯微的RKDevTool,通过USB连接开发板并使其进入Loader模式,即可选择镜像文件进行烧写。

实操心得:搭建交叉编译环境时,最容易出错的是库的依赖。如果你的应用程序需要链接第三方动态库(如OpenCV),你必须使用同样由该交叉编译工具链编译出来的库版本,直接使用PC版的库或者从其他途径获取的ARM库很可能导致运行时链接失败。一个稳妥的做法是,在SDK的buildrootyocto配置中,添加你需要的软件包,然后整体重新编译根文件系统,这样能保证所有库的兼容性。

3. AI模型从训练到转换的完整路径

有了环境,接下来就是核心的算法部分。我们以一个经典的“人脸检测”任务为例,来阐述从模型训练到转换为RV1126可用格式的完整路径。

3.1 模型选择、训练与优化策略

对于嵌入式设备,模型的选择首要考虑的是大小速度,其次才是精度。一个参数量巨大的SOTA(最先进)模型可能在数据集上能达到99%的准确率,但根本无法在RV1126上实时运行(如>30 FPS)。因此,我们需要寻找轻量级网络。

对于人脸检测,Mobilenet-SSD、YOLO-fastest、或者基于Anchor-free的NanoDet都是不错的选择。这里假设我们选择了一个轻量化的YOLOv5-nano版本进行训练。

  1. 数据准备:收集并标注好人脸数据集(如Wider Face)。使用LabelImg等工具标注为YOLO格式(每个图像对应一个.txt文件,包含类别和归一化的边界框坐标)。
  2. 模型训练:在PC端的conda环境下,使用YOLOv5官方代码进行训练。关键训练参数需要调整:
    python train.py --data face.yaml --cfg yolov5n.yaml --weights '' --batch-size 32 --epochs 100 --img 640
    • --img 640:将输入图像统一缩放到640x640。这个尺寸直接影响模型速度和内存占用,需要根据开发板算力和应用场景权衡。
    • --batch-size:在显存允许范围内尽可能设大,有利于训练稳定。
  3. 模型优化(针对嵌入式)
    • 通道剪枝:使用一些剪枝工具(如Torch-Pruning),去除网络中不重要的通道,大幅减少参数量和计算量。
    • 知识蒸馏:用一个大的、精度高的教师模型来指导我们这个小模型训练,让小模型在保持轻量的同时获得更高的精度。
    • 训练后量化:在PyTorch中,可以使用torch.quantization进行动态或静态量化,将FP32的权重转换为INT8,模型大小可缩减至近1/4。但请注意:这一步的量化与后续RKNN的量化目的不同。PyTorch量化是为了验证INT8模型在PC上的精度损失,而RKNN量化才是生成最终NPU模型的关键。

训练完成后,我们会得到一个最好的权重文件best.pt。在转换为RKNN格式前,必须先将PyTorch模型转换为ONNX格式。ONNX是一种开放的模型交换格式,是RKNN-Toolkit2识别模型的桥梁。

import torch model = torch.load('best.pt', map_location='cpu')['model'].float() # 设置为评估模式 model.eval() # 提供一个示例输入 dummy_input = torch.randn(1, 3, 640, 640) # 导出为ONNX torch.onnx.export(model, dummy_input, "face_detection.onnx", input_names=['images'], output_names=['output'], dynamic_axes={'images': {0: 'batch'}, 'output': {0: 'batch'}})

3.2 模型转换与量化:RKNN-Toolkit2的核心操作

得到ONNX模型后,就进入了RV1126开发最特有的环节——使用RKNN-Toolkit2进行模型转换和量化。这个过程的目标是生成一个.rknn文件。

  1. 创建RKNN对象与配置

    from rknn.api import RKNN rknn = RKNN() # 配置模型参数,这些参数必须与训练时一致 rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform='rv1126')
    • mean_valuesstd_values:用于输入图像的归一化,需要与模型训练时的预处理方式对齐。如果训练时用了(img - mean)/std,这里就要对应设置。
    • target_platform:指定为rv1126,工具链会进行针对该NPU架构的优化。
  2. 加载与转换模型

    # 加载ONNX模型 ret = rknn.load_onnx(model='face_detection.onnx') # 构建模型,这一步会进行初步的图优化和算子转换 ret = rknn.build(do_quantization=True, dataset='./dataset.txt')
    • do_quantization=True:开启量化,这是提升NPU推理速度的关键。NPU对INT8计算有硬件加速。
    • dataset.txt:量化所需的校准数据集文件。里面是几百张训练图片的路径列表。量化过程会通过这些图片来统计激活值的分布,确定浮点数到整数的缩放比例。
  3. 导出与精度验证

    # 导出RKNN模型 ret = rknn.export_rknn('face_detection.rknn') # 在PC上模拟推理,验证转换后模型的精度(可选,但强烈推荐) rknn.init_runtime(target='rk1808', device_id='xxx') # 注意:PC模拟通常用RK1808或RK3399Pro作为target outputs = rknn.inference(inputs=[input_data])
    • 重要提示:PC模拟推理(target='rk1808')只能用于验证功能正确性和排查明显的转换错误,其性能(速度)和精度与在真实的RV1126上运行有差异,最终性能必须以真机测试为准。

踩坑实录:量化环节最容易出问题。如果dataset.txt中的图片不具有代表性(例如,全是白天图片,但实际应用有夜间场景),会导致量化参数不准确,在真实场景下出现严重的精度下降甚至误检。我的经验是,校准数据集最好从训练集中随机抽取,并且能覆盖各种可能的场景变化。此外,如果模型中有RKNN不支持的算子,会在build阶段报错。常见的解决方法是修改模型结构,用支持的算子组合替代,或者联系芯片原厂寻求支持。

4. 嵌入式端应用开发与集成实战

拿到.rknn模型文件后,接下来的任务就是让它“活”在RV1126开发板上。这涉及到驱动、运行时库和应用程序的集成。

4.1 NPU驱动与运行时库部署

RV1126的Linux系统镜像中需要包含NPU内核驱动(galcore.ko)和用户态运行时库(librknnrt.so)。通常,供应商提供的镜像已经包含了这些。你需要确认以下两点:

  1. 驱动加载:通过lsmod | grep galcore命令检查NPU驱动是否已加载。
  2. 库文件位置:确认librknnrt.so库文件存在于系统的库路径(如/usr/lib)下,或者在你的应用程序中指定其路径。

如果镜像中没有,你需要从SDK中找到这些预编译好的文件,手动将其放入开发板根文件系统的对应位置。

4.2 编写C/C++推理应用程序

在嵌入式端,我们通常使用C或C++来编写高性能的推理应用。瑞芯微提供了RKNN的C API,封装在librknnrt.so中。

一个典型的推理流程的C++代码骨架如下:

#include <rknn/rknn_runtime.h> #include <opencv2/opencv.hpp> int main() { // 1. 初始化RKNN上下文 rknn_context ctx; int ret = rknn_init(&ctx, "face_detection.rknn", 0, 0, nullptr); // 2. 获取模型输入输出信息 rknn_input_output_num io_num; rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num)); // 3. 设置输入 rknn_input inputs[io_num.n_input]; // ... 使用OpenCV读取图像,并进行预处理(缩放、归一化等) cv::Mat img = cv::imread("test.jpg"); cv::resize(img, img, cv::Size(640, 640)); // 将图像数据填充到inputs[0].buf,注意内存布局(例如NHWC格式) inputs[0].index = 0; inputs[0].type = RKNN_TENSOR_UINT8; // 根据量化类型设定 inputs[0].fmt = RKNN_TENSOR_NHWC; inputs[0].buf = img.data; inputs[0].size = img.total() * img.elemSize(); rknn_inputs_set(ctx, io_num.n_input, inputs); // 4. 执行推理 ret = rknn_run(ctx, nullptr); // 5. 获取输出 rknn_output outputs[io_num.n_output]; // ... 为outputs分配内存 rknn_outputs_get(ctx, io_num.n_output, outputs, nullptr); // 6. 后处理 // 解析outputs.buf中的数据(例如,YOLO的检测框、置信度、类别) // 应用非极大值抑制(NMS)等算法 // 在图像上绘制检测框 // 7. 释放资源 rknn_outputs_release(ctx, io_num.n_output, outputs); rknn_destroy(ctx); return 0; }

编写完代码后,使用RV1126的交叉编译工具链进行编译:

arm-rockchip830-linux-uclibcgnueabihf-g++ -o face_detection_app main.cpp \ -I/path/to/rknn_api/include \ -L/path/to/rknn_api/lib -lrknnrt \ `pkg-config --cflags --libs opencv4` -lopencv_core -lopencv_imgproc -lopencv_highgui

将编译好的可执行文件face_detection_app和模型文件face_detection.rknn一起通过scp或ADB推送到开发板上。

4.3 系统集成与自启动

对于一个产品化的应用,我们通常希望它能在开发板上电后自动运行。

  1. 编写启动脚本:创建一个Shell脚本(如start_app.sh),在里面设置必要的环境变量(如LD_LIBRARY_PATH指向RKNN库路径),然后启动你的应用程序。
  2. 配置系统服务:在Linux系统中,更规范的做法是创建一个systemd服务单元文件(.service文件)。将其放在/etc/systemd/system/目录下,通过systemctl enable your-service命令设置开机自启。这种方式可以管理应用的启动、停止、重启以及日志收集。
  3. 资源管理:在服务脚本或应用程序初始化部分,可以加入对CPU频率、NPU频率的设置,以在性能和功耗间取得平衡。RV1126的SDK通常提供/sys/class下的节点或专用工具(如rknn_server)来进行相关控制。

5. 性能调优与问题深度排查

将应用跑起来只是第一步,让它跑得又快又稳才是挑战。下面是一些关键的调优点和排查方法。

5.1 性能瓶颈分析与优化手段

当发现推理速度不达标时,需要系统性地分析瓶颈所在。

  1. 时间剖析:在代码中关键位置(如图像读取、预处理、推理、后处理)加入高精度计时(如gettimeofdaystd::chrono),精确测量每个阶段耗时。

    • 常见情况:往往发现预处理(如cv::resize,cv::cvtColor)或后处理(NMS)的CPU计算时间,甚至超过了NPU推理本身。这是因为NPU的算力很强,而RV1126的A7 CPU主频相对较低。
  2. 优化策略

    • 预处理优化
      • 使用NPU进行预处理:一些简单的预处理(如减均值、乘系数)可以在模型转换前合并到网络结构中,让NPU来完成。
      • 使用硬件加速:调查RV1126的ISP(图像信号处理器)或RGA(2D图形加速器)是否支持所需的色彩空间转换和缩放。通过libdrmlibrga库调用硬件加速,效率远高于OpenCV的纯CPU操作。
    • 后处理优化
      • 简化算法:检查NMS的实现是否高效,尝试使用更轻量级的版本。
      • 并行化:如果有多帧缓冲,可以考虑将后处理与下一帧的推理重叠(流水线并行)。
    • 推理本身优化
      • 调整NPU频率:在散热允许的情况下,适当提高NPU工作频率。
      • 模型层面:回顾第3.1节,是否还有模型剪枝、蒸馏的空间?能否将输入尺寸从640降到416或320?精度下降是否在可接受范围内?
      • Batch推理:如果应用场景允许处理多张图片,尝试使用Batch推理。RKNN API支持设置多个输入,一次性推理多张图片的吞吐量通常会高于逐张推理。

5.2 常见问题与稳定性排查清单

在实际部署中,除了性能,还会遇到各种稳定性问题。

问题现象可能原因排查思路与解决方案
推理结果完全错误,框乱飞1. 预处理不一致
2. 量化失败
3. 模型转换出错
1.核对预处理:确保PC训练/验证、RKNN转换配置、嵌入式端代码三处的图像缩放、裁剪、归一化(均值/方差)完全一致。建议将嵌入式端预处理后的图像数据保存下来,传回PC用Python脚本模拟预处理并输入原始模型,对比结果。
2.检查量化数据集:确认dataset.txt中的图片是训练集的一部分,且覆盖了真实场景的多样性。
3.关闭量化测试:在rknn.build()时设置do_quantization=False,生成一个FP16/FP32的RKNN模型。在板端运行,如果结果正确,问题一定出在量化环节。
程序运行一段时间后崩溃或NPU无响应1. 内存泄漏
2. NPU驱动或固件问题
3. 散热导致降频或死机
1.检查代码:确保每次推理后,都正确调用了rknn_outputs_releaserknn_destroy(在程序最终退出时)。使用topfree命令监控内存使用情况。
2.查看内核日志:使用dmesg命令查看是否有NPU驱动相关的错误信息(如galcore报错)。尝试更新NPU驱动和固件到最新版本。
3.监控温度:使用cat /sys/class/thermal/thermal_zone*/temp查看温度。加强散热或添加温度监控逻辑,在过热时主动降频或暂停任务。
推理速度波动大,时快时慢1. 系统后台任务干扰
2. CPU/NPU频率缩放
3. 内存带宽竞争
1.隔离CPU核心:使用taskset命令将你的推理进程绑定到特定的CPU核心上,避免被其他进程调度影响。
2.设置性能模式:将CPU和NPU的调速器(governor)设置为performance模式,防止其自动降频。echo performance > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor
3.减少并发:检查系统是否还有其他高带宽应用在运行。
模型加载失败 (rknn_init返回错误)1. 模型文件路径错误或损坏
2.librknnrt.so版本不匹配
3. 内存不足
1.检查文件:使用md5sum核对板端模型文件与PC导出的是否一致。
2.核对版本:确认PC上导出RKNN模型的RKNN-Toolkit2版本,与板端librknnrt.so的版本兼容。通常大版本号需要一致。
3.检查内存free -m查看剩余内存。RV1126内存有限,大模型加载可能需要较多内存。

个人经验:稳定性问题中,温度是一个极其隐蔽但关键的因素。我曾遇到一个案子,在实验室空调房运行24小时无恙,但在现场机柜中运行1小时就死机。最后发现是外壳散热设计不足,NPU持续高负载下结温过高,触发了硬件保护。加装散热片并在软件中加入“推理-休眠”的间歇工作模式后问题解决。因此,压力测试(Burn-in Test)必须在模拟真实环境温度下进行。

6. 进阶:模型部署的工程化考量

当单个模型应用稳定后,如果要走向产品化,还需要考虑更多工程化问题。

6.1 多模型管理与调度

一个复杂的AI应用(如智能摄像头)可能需要同时运行人脸检测、人脸识别、车辆检测等多个模型。这就需要一套模型管理和调度机制。

  1. 模型加载策略
    • 预加载:在系统启动时,将所有模型加载到内存中。优点是推理时延低,缺点是启动慢、内存占用高。
    • 动态加载:按需加载和卸载模型。优点是内存利用率高,缺点是推理时有加载开销。可以设计一个简单的模型缓存池来平衡。
  2. NPU资源复用与竞争:RV1126的NPU是单个计算核心。如果多个进程或线程同时调用RKNN API去执行推理,底层驱动会进行串行化调度,这可能引发不可预知的延迟甚至错误。最佳实践是设计一个单进程、多线程的AI服务。在这个主进程中,由一个专门的线程或线程池来管理所有RKNN上下文并执行推理任务,其他业务线程通过进程内通信(如队列)提交推理请求和获取结果。这样可以避免对NPU资源的直接竞争。

6.2 功耗、散热与可靠性设计

对于电池供电或严苛环境下的设备,功耗和可靠性至关重要。

  1. 功耗模型建立:测量不同工作状态(待机、低频推理、高频推理)下的整板电流。为应用程序设计状态机,在无任务时让NPU和CPU进入低功耗状态,有任务时再快速唤醒。
  2. 温度闭环控制:如5.2节所述,实时读取温度传感器数据。可以设计一个梯度降频策略:当温度超过阈值T1时,将NPU频率降低一档;超过更严重的T2时,暂停推理任务,直到温度回落。这比简单的“过热关机”用户体验更好。
  3. 看门狗与异常恢复:为AI推理进程添加看门狗(Watchdog)。如果主进程由于未知原因挂掉,看门狗会重启它。同时,在代码中捕获所有可能的异常(如rknn_run返回错误),并进行日志记录和优雅的重试,避免整个应用崩溃。

从选择模型、训练优化,到转换量化、嵌入式集成,再到最后的深度调优和工程化打磨,基于RV1126的AI算法开发是一条环环相扣的链路。每一个环节的细节都影响着最终的成效。这套流程不仅仅是技术的堆砌,更是一种在资源、功耗、精度和速度之间反复权衡的工程思维。我个人的体会是,嵌入式AI开发没有“银弹”,最大的技巧就是“大胆假设,小心求证”——用实验数据(速度、精度、温度)来驱动每一个优化决策,并且永远对生产环境保持敬畏,实验室的稳定只是起点。最后分享一个小技巧:建立一个详细的实验日志,记录每一次模型改动、参数调整、转换配置对应的性能数据,时间长了,这会成为你应对各种需求时最宝贵的经验库。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/20 12:38:57

3分钟掌握OBS多平台直播:obs-multi-rtmp插件终极配置指南

3分钟掌握OBS多平台直播&#xff1a;obs-multi-rtmp插件终极配置指南 【免费下载链接】obs-multi-rtmp OBS複数サイト同時配信プラグイン 项目地址: https://gitcode.com/gh_mirrors/ob/obs-multi-rtmp 你是否正在寻找一款能够让你在多个直播平台同步推流的解决方案&…

作者头像 李华
网站建设 2026/5/20 12:36:52

WzComparerR2完整指南:三步掌握冒险岛游戏数据提取终极工具

WzComparerR2完整指南&#xff1a;三步掌握冒险岛游戏数据提取终极工具 【免费下载链接】WzComparerR2 Maplestory online Extractor 项目地址: https://gitcode.com/gh_mirrors/wz/WzComparerR2 WzComparerR2是一款功能强大的开源工具&#xff0c;专门用于解析和提取《…

作者头像 李华
网站建设 2026/5/20 12:32:15

CANN/asc-devkit Cube资源组假消息发送API

PostFakeMsg 【免费下载链接】asc-devkit 本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言&#xff0c;原生支持C和C标准规范&#xff0c;主要由类库和语言扩展层构成&#xff0c;提供多层级API&#xff0c;满足多维场景算子开发诉求。 项目地址: https://gitcode.co…

作者头像 李华
网站建设 2026/5/20 12:30:04

JiYuTrainer:智能破解极域电子教室控制的高效开源解决方案

JiYuTrainer&#xff1a;智能破解极域电子教室控制的高效开源解决方案 【免费下载链接】JiYuTrainer 极域电子教室防控制软件, StudenMain.exe 破解 项目地址: https://gitcode.com/gh_mirrors/ji/JiYuTrainer JiYuTrainer是一款专门针对极域电子教室系统控制限制的开源…

作者头像 李华
网站建设 2026/5/20 12:28:27

QUIK消息备份与恢复教程:保护你的重要对话

QUIK消息备份与恢复教程&#xff1a;保护你的重要对话 【免费下载链接】quik The most beautiful SMS messenger for Android - Revived 项目地址: https://gitcode.com/gh_mirrors/qui/quik QUIK作为Android平台上备受赞誉的开源短信应用&#xff0c;不仅以优雅的界面设…

作者头像 李华