# android 串口通信实现
**Repository Path**: nnddkj/android_SerialPortDemo
## Basic Information
- **Project Name**: android 串口通信实现
- **Description**: android 串口通信实现demo
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-08-14
- **Last Updated**: 2026-08-14
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# SerialPortLib 串口通信库使用说明
> 串口(UART) + RS485 物联网通信 Android Library
> 开发者:宁工 联系电话:17776688224 在找工作,有内推最好了
---
## 目录
1. [库简介](#1-库简介)
2. [串口与 485 的区别(重要)](#2-串口与-485-的区别重要)
3. [项目结构](#3-项目结构)
4. [快速接入(3 步)](#4-快速接入3-步)
5. [调用示例](#5-调用示例)
6. [配置项详解](#6-配置项详解)
7. [权限与系统版本兼容性(Android 5 ~ 17)](#7-权限与系统版本兼容性android-5--17)
8. [测试工具 App 使用说明](#8-测试工具-app-使用说明)
9. [MVVM 项目集成建议](#9-mvvm-项目集成建议)
10. [常见问题 FAQ](#10-常见问题-faq)
11. [版本记录](#11-版本记录)
---
## 1. 库简介
`SerialPortLib`(模块名 `:serialportlib`)是一个纯 Java 实现的 Android 串口通信库,
包含两大独立功能模块:
| 功能模块 | 类 | 说明 |
| -------- | -- | ---- |
| 普通串口 | `SerialPortManager` | TTL / RS232 全双工串口通信 |
| RS485 | `Serial485Manager` | RS485 半双工总线通信(自动方向切换) |
**库的核心特点:**
- **纯 Java API**:Java / Kotlin 项目均可直接调用,无任何 API 差异。
- **零第三方依赖**:库内部仅依赖 `appcompat`,集成到商业项目时无依赖冲突风险。
- **架构无关**:支持普通 Android 项目(MVC)与 MVVM 项目(把管理器封装进 Repository 即可)。
- **全版本兼容**:适配 Android 5.0(API21) ~ Android 17(API37)。
- **回调自动切主线程**:数据回调已统一切换到主线程,可直接更新 UI,无需手动 `runOnUiThread`。
- **配置对外开放**:所有参数通过 Builder 模式对外暴露,业务项目可自由设置。
---
## 2. 串口与 485 的区别(重要)
**串口通信和 485 通信是两个不同的功能,本库将它们完全独立封装,互不影响。**
| 对比项 | 普通串口(UART / RS232 / TTL) | RS485 |
| ------ | ------------------------------ | ----- |
| 工作模式 | 全双工(可同时收发) | 半双工(同一时刻只能发或收) |
| 总线形态 | 点对点 | 多点总线(一条总线可挂多个设备) |
| 电平标准 | TTL / RS232 电平 | 差分信号(A/B 两线) |
| 通信距离 | 较短(约 15 米内) | 较长(约 1200 米) |
| 方向控制 | 无需,天然可双向同时通信 | 需要切换发送/接收方向(GPIO 或自动方向) |
| 常用场景 | 板载调试串口、传感器、GPS 模块 | 工业仪表、电表、Modbus 物联网总线 |
**本库中的体现:**
- 串口通道:`SerialPortManager`,独立配置(设备路径 / 波特率 / 参数),打开后即收发。
- 485 通道:`Serial485Manager`,在串口配置基础上**额外独立配置**方向控制
(GPIO 控制或自动方向 + 发送前后延时),发送时自动切换方向。
- **两个通道互不影响**:可以同时打开串口和 485,各自独立收发;关闭其中一个不影响另一个。
- 初始化时分别通过 `SerialLib.get().serialManager(...)` 和 `SerialLib.get().rs485Manager(...)` 获取,
两者内部各自持有独立的文件句柄、线程与监听器集合。
---
## 3. 项目结构
```
SerialPortDemo/
├── serialportlib/ # ★ 串口通信 Library(可直接移植到商业项目)
│ └── src/main/
│ ├── java/com/nyw/seriallib/
│ │ ├── SerialLib.java # 库门面(统一入口,初始化/获取管理器/释放)
│ │ ├── core/
│ │ │ ├── SerialPort.java # JNI 串口底层(termios 操作)
│ │ │ └── SerialPortFinder.java # 自动扫描系统串口设备
│ │ ├── config/
│ │ │ ├── SerialConfig.java # 串口配置(设备/波特率/数据位/停止位/校验/流控)
│ │ │ ├── Rs485Config.java # 485 方向控制配置(GPIO/自动方向/延时)
│ │ │ ├── BaudRate.java # 波特率常量
│ │ │ ├── Parity.java # 校验位常量
│ │ │ └── SerialParams.java # 数据位/停止位/流控常量
│ │ ├── manager/
│ │ │ ├── SerialPortManager.java # ★ 串口管理器
│ │ │ ├── Serial485Manager.java # ★ 485 管理器
│ │ │ └── SerialChannel.java # 通道抽象基类(统一 API)
│ │ ├── listener/
│ │ │ └── OnSerialDataListener.java # 数据回调监听器
│ │ ├── util/
│ │ │ ├── HexUtil.java # HEX 转换工具
│ │ │ ├── GpioUtil.java # GPIO 方向控制工具(485 用)
│ │ │ └── Logger.java # 调试日志工具
│ │ └── cpp/ # JNI 原生代码(CMake)
│ └── AndroidManifest.xml # 库清单(权限说明见第 7 节)
├── app/ # ★ 测试工具 App(演示库的全部用法)
│ └── src/main/java/com/nyw/serialportdemo/
│ ├── DemoApp.kt # Application:SerialLib.init + setDebug
│ ├── MainActivity.kt # 底部导航主界面(4 个功能页)
│ ├── SerialTestFragment.kt # 串口测试页(打开/关闭/收发/HEX)
│ ├── Rs485TestFragment.kt # 485 测试页(含 Modbus 快捷指令)
│ ├── BaseChannelFragment.kt # 通道测试页基类(串口/485 共用逻辑)
│ ├── SettingsFragment.kt # 设置页(双通道独立配置)
│ ├── DemoConfigStore.kt # 配置持久化(SharedPreferences)
│ └── AboutFragment.kt # 关于页(版本 + 开发者信息)
└── README.md # 本使用说明
```
**核心 API 一览(全部对外开放):**
| 类 | 主要方法 | 功能 |
| -- | -------- | ---- |
| `SerialLib` | `init(context)` | 初始化库(Application 中调用一次) |
| | `setDebug(boolean)` | 开关调试日志 |
| | `get()` | 获取单例门面 |
| | `serialManager(SerialConfig)` | 获取串口管理器(全局单例) |
| | `rs485Manager(SerialConfig, Rs485Config)` | 获取 485 管理器(全局单例) |
| | `openSerial(config, listener)` | 【一步式】打开串口 + 注册监听 |
| | `closeSerial(listener)` | 【一步式】关闭串口 |
| | `isSerialOpen()` | 串口是否已打开 |
| | `sendSerialHex / sendSerialText / sendSerial` | 【一步式】串口发送数据 |
| | `openRs485(config, rs485Config, listener)` | 【一步式】打开 485 + 注册监听 |
| | `closeRs485(listener)` | 【一步式】关闭 485 |
| | `isRs485Open()` | 485 是否已打开 |
| | `sendRs485Hex / sendRs485Text / sendRs485` | 【一步式】485 发送数据 |
| | `findDevices()` | 扫描设备全部串口 |
| | `releaseAll()` | 释放全部通道 |
| `SerialPortManager` / `Serial485Manager` | `open()` / `close()` | 打开 / 关闭通道 |
| | `isOpen()` | 是否已打开 |
| | `send(byte[])` | 发送原始字节 |
| | `sendHex(String)` | 发送 HEX 字符串(如 `"AA 55 01 FF"`) |
| | `sendText(String)` | 发送文本 |
| | `addListener(OnSerialDataListener)` | 注册数据回调 |
| | `removeListener(...)` | 移除数据回调 |
| | `getBaudRates()` | 获取支持的波特率列表 |
| `SimpleSerialDataListener` | `onDataReceived` | 简易监听器:只需重写数据回调 |
| `SerialConfig` | `Builder` | 串口全参数配置(见第 6 节) |
| `Rs485Config` | `Builder` | 485 方向控制配置(见第 6 节) |
| `HexUtil` | `toHex / fromHex / isHex` | HEX 与字节互转、格式校验 |
### ★ 一步式调用(新项目推荐,最快接入方式)
无需关心管理器创建细节,三条语句即可完成串口收发:
```kotlin
// 1. 打开串口(自动创建管理器 + 注册监听 + 打开,一条语句完成)
SerialLib.get().openSerial(
SerialConfig.Builder().setDevicePath("/dev/ttyS1").setBaudRate(BaudRate.B9600).build()
) { data, length ->
tvLog.append("收到: " + HexUtil.bytesToHex(data, length) + "\n")
}
// 2. 发送数据(未打开时返回 false,无需判空管理器)
SerialLib.get().sendSerialHex("AA 55 01 02 FF")
SerialLib.get().sendSerialText("AT+RESET\r\n")
// 3. 关闭串口
SerialLib.get().closeSerial(null)
```
485 用法完全一致:`openRs485(...)` / `sendRs485Hex(...)` / `closeRs485(null)`。
---
## 4. 快速接入(3 步)
### 第 1 步:复制模块
将项目中的 `serialportlib/` 文件夹整体复制到你的工程根目录,
并在根目录 `settings.gradle.kts` 中加入:
```kotlin
include(":serialportlib")
```
### 第 2 步:添加依赖
在 App 模块的 `build.gradle.kts`(或 Groovy 的 `build.gradle`)中添加:
```kotlin
dependencies {
implementation(project(":serialportlib"))
}
```
### 第 3 步:初始化 + 使用
在你的 `Application` 中初始化(仅一次):
```kotlin
// Kotlin 写法
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
SerialLib.init(this) // 初始化库
SerialLib.setDebug(BuildConfig.DEBUG) // 调试期开日志,上线建议关闭
}
}
```
```java
// Java 写法
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
SerialLib.init(this);
SerialLib.setDebug(BuildConfig.DEBUG);
}
}
```
> **提示**:`init()` 内部仅保存上下文,无耗时操作,可放心在主线程调用。
---
## 5. 调用示例
### 5.1 Kotlin —— 串口通信
```kotlin
// 1. 配置串口(设备路径 / 波特率 / 参数)
val config = SerialConfig.Builder()
.setDevicePath("/dev/ttyS1") // 串口设备路径
.setBaudRate(BaudRate.B9600) // 波特率 9600
.setDataBits(SerialParams.DATA_BITS_8)
.setStopBits(SerialParams.STOP_BITS_1)
.setParity(Parity.NONE)
.setFlowControl(SerialParams.FLOW_NONE)
.build()
// 2. 获取管理器(全局单例)
val manager = SerialLib.get().serialManager(config)
// 3. 注册数据回调(回调已在主线程,可直接更新 UI)
manager.addListener(object : OnSerialDataListener {
override fun onDataReceived(data: ByteArray, length: Int) {
val hex = HexUtil.bytesToHex(data, length) // 转 HEX 显示
tvLog.append("收到: $hex\n")
}
override fun onStateChanged(isOpen: Boolean) {
tvStatus.text = if (isOpen) "已打开" else "已关闭"
}
override fun onError(e: Exception) {
Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_SHORT).show()
}
})
// 4. 打开串口
manager.open()
// 5. 发送数据(三种方式任选)
manager.sendText("AT+RESET\r\n") // 发送文本
manager.sendHex("AA 55 01 02 FF") // 发送 HEX
manager.send(byteArrayOf(0x01, 0x03)) // 发送原始字节
// 6. 关闭串口
manager.close()
```
### 5.2 Kotlin —— 485 通信(Modbus 为例)
```kotlin
// 1. 串口基础配置
val serial = SerialConfig.Builder()
.setDevicePath("/dev/ttyS2")
.setBaudRate(BaudRate.B9600)
.build()
// 2. 485 方向控制配置(自动方向,发送前 0ms / 发送后 10ms 延时)
val rs485 = Rs485Config.Builder()
.setBeforeTxDelay(0)
.setAfterTxDelay(10)
.build()
// 若板卡有 GPIO 方向控制引脚,也可:
// val rs485 = Rs485Config.Builder()
// .enableGpioControl(84, true) // GPIO84,发送时输出高电平
// .build()
// 3. 获取 485 管理器(全局单例)
val manager = SerialLib.get().rs485Manager(serial, rs485)
// 4. 注册回调 + 打开 + 发送 Modbus 读保持寄存器指令
manager.addListener(object : OnSerialDataListener { /* 同上 */ })
manager.open()
manager.sendHex("01 03 00 00 00 01 84 0A") // 站号01 功能码03 读1个保持寄存器
```
### 5.3 Java —— 完整示例
```java
// 配置串口
SerialConfig config = new SerialConfig.Builder()
.setDevicePath("/dev/ttyS1")
.setBaudRate(BaudRate.B9600)
.setDataBits(SerialParams.DATA_BITS_8)
.setStopBits(SerialParams.STOP_BITS_1)
.setParity(Parity.NONE)
.build();
// 获取串口管理器
SerialPortManager manager = SerialLib.get().serialManager(config);
// 注册回调
manager.addListener(new OnSerialDataListener() {
@Override
public void onDataReceived(byte[] data, int length) {
Log.i("Serial", "收到: " + HexUtil.bytesToHex(data, length));
}
@Override
public void onStateChanged(boolean isOpen) {
Log.i("Serial", "状态: " + (isOpen ? "已打开" : "已关闭"));
}
@Override
public void onError(Exception e) {
Log.e("Serial", "错误: " + e.getMessage());
}
});
// 打开并发送
manager.open();
manager.sendHex("AA 55 01 02 FF");
```
---
## 6. 配置项详解
### 6.1 SerialConfig(串口配置)
| Builder 方法 | 类型 | 说明 | 默认值 |
| ------------ | ---- | ---- | ------ |
| `setDevicePath(String)` | String | 串口设备路径(如 `/dev/ttyS1`) | `/dev/ttyS1` |
| `setBaudRate(int)` | int | 波特率(可用 `BaudRate` 常量) | 9600 |
| `setDataBits(int)` | int | 数据位 5/6/7/8 | 8 |
| `setStopBits(int)` | int | 停止位 1/2 | 1 |
| `setParity(Parity)` | Parity | 校验位(NONE/ODD/EVEN/MARK/SPACE) | NONE |
| `setFlowControl(int)` | int | 流控(NONE/RTS_CTS/XON_XOFF) | NONE |
常用波特率常量(`BaudRate`):`B4800` `B9600` `B19200` `B38400` `B57600` `B115200` `B230400` `B460800` `B921600`,也支持任意非标准波特率。
常用参数常量(`SerialParams`):`DATA_BITS_5/6/7/8`、`STOP_BITS_1/2`、`FLOW_NONE/RTS_CTS/XON_XOFF`。
校验位(`Parity`):`NONE`、`ODD`(奇校验)、`EVEN`(偶校验)、`MARK`、`SPACE`。
### 6.2 Rs485Config(485 方向控制配置)
| Builder 方法 | 类型 | 说明 | 默认值 |
| ------------ | ---- | ---- | ------ |
| `enableGpioControl(int gpio, boolean txHigh)` | void | 使用 GPIO 方向控制(gpio 引脚号;txHigh=true 表示发送时输出高电平) | 关闭 |
| `setBeforeTxDelay(long)` | long | 发送前延时(毫秒),等待方向切换稳定 | 0 |
| `setAfterTxDelay(long)` | long | 发送后延时(毫秒),确保数据发送完再切回接收 | 10 |
| `setAutoDirection(boolean)` | void | 自动方向控制(无 GPIO 时发送后自动切回接收) | 开启 |
> **说明**:若板卡硬件上有 RE/DE 方向控制引脚接到 GPIO,推荐 `enableGpioControl`;
> 若没有 GPIO 或驱动已自动处理方向,保持自动方向即可。
---
## 7. 权限与系统版本兼容性(Android 5 ~ 17)
**核心结论:串口 / 485 通信本身不需要任何 Android 运行时权限。**
串口通过 Linux 设备节点(`/dev/ttyS*`、`/dev/ttyMT*`、`/dev/ttyUSB*` 等)通信,
属于系统底层能力,无需在清单中声明权限。库的清单文件已做好版本兼容处理:
- `minSdk = 21`(Android 5.0)~ `compileSdk = 37`(Android 17),全版本可编译可运行。
- 库内部无任何 `@RequiresApi` 门槛代码,所有底层调用均做了版本判断与降级处理。
### 各版本兼容说明
| Android 版本 | API | 兼容性处理 |
| ------------ | --- | ---------- |
| 5.0 ~ 5.1 | 21~22 | 基础支持,串口 JNI 使用标准 termios 接口,无兼容问题 |
| 6.0 ~ 7.x | 23~24 | 无需动态权限(串口不走危险权限);设备节点权限不足时库自动尝试 chmod |
| 8.0 ~ 9.0 | 26~28 | 同上;测试 App 使用边缘到边缘显示时做了 inset 适配 |
| 10 ~ 11 | 29~30 | 同上;无分区存储影响(库不读写公共存储) |
| 12 ~ 13 | 31~33 | 兼容验证;库不使用蓝牙/定位等受限权限 |
| 14 ~ 17 | 34~37 | compileSdk 37 编译,API 变更点均已适配(如前台服务/通知等未涉及) |
### 设备节点权限不足的处理
部分设备(尤其工控主板)的串口节点默认权限不足,库底层已内置处理逻辑:
1. 打开设备失败时,自动尝试 `chmod 666 设备路径` 重新打开;
2. 仍失败时抛出带中文说明的异常,测试工具中会弹出提示;
3. 若系统为封闭签名固件,需在系统定制时预置权限(`file_contexts` 或 init.rc 设置节点权限)。
### USB 转串口设备(可选)
若使用 USB 转串口(CH340/CP2102 等),在**宿主 App** 的清单中自行添加:
```xml
```
并在代码中处理 USB 设备授权(参考 Android USB Host API,本库暂不内置 USB 授权流程)。
---
## 8. 测试工具 App 使用说明
`app/` 模块是一个完整的串口测试工具,用于连接硬件后收发数据、验证功能:
### 页面功能
| 页面 | 功能 |
| ---- | ---- |
| **串口** | 打开/关闭串口、文本/HEX 收发、换行追加、收发字节统计、HEX 显示切换 |
| **485** | 485 收发(自动方向切换)、Modbus 读寄存器快捷指令、广播帧测试 |
| **设置** | 串口通道与 485 通道**独立**配置(设备路径/波特率/数据位/停止位/校验/流控、485 GPIO/延时),自动扫描系统串口设备,配置自动持久化 |
| **关于** | 库版本信息、功能特性列表、开发者信息(宁工 17776688224) |
### 使用流程
1. 安装 App,进入**设置**页,点击"扫描设备"查看板卡串口列表;
2. 分别配置串口通道与 485 通道参数(两通道独立保存);
3. 回到**串口**页打开串口,输入指令(如 `AT`)点击发送,观察接收区;
4. 切到 **485** 页打开 485,使用 Modbus 快捷指令测试总线设备。
> **提示**:真实调试串口时建议使用串口助手工具对比验证(如 SecureCRT、Putty 等)。
---
## 9. MVVM 项目集成建议
本库为纯 Java 管理类 API,MVVM 项目中可轻松封装进数据层:
### Kotlin + MVVM(ViewModel + Repository)
```kotlin
// Repository:封装串口数据流
class SerialRepository(private val config: SerialConfig) {
private val manager = SerialLib.get().serialManager(config)
private val _data = MutableStateFlow("")
init {
manager.addListener(object : OnSerialDataListener {
override fun onDataReceived(data: ByteArray, length: Int) {
_data.value = HexUtil.bytesToHex(data, length)
}
override fun onStateChanged(isOpen: Boolean) { _isOpen.value = isOpen }
override fun onError(e: Exception) { _error.value = e.message }
})
}
val data: StateFlow = _data.asStateFlow()
fun open() = manager.open()
fun sendHex(hex: String) = manager.sendHex(hex)
fun close() = manager.close()
}
// ViewModel:通过 StateFlow 驱动 UI
class SerialViewModel(private val repo: SerialRepository) : ViewModel() {
val data = repo.data
fun onSend(hex: String) = repo.sendHex(hex)
}
```
### 注意点
- 管理器是全局单例,若页面配置不同,可在打开前 `setConfig(...)` 动态切换;
- 建议在 `ViewModel.onCleared()` 中移除监听器(`removeListener`),避免内存泄漏;
- 回调已在主线程,LiveData/Flow 赋值可直接进行。
---
## 10. 常见问题 FAQ
**Q1:打开串口提示"设备节点权限不足 / 无法打开"怎么办?**
A:库已自动尝试 chmod 提权。若仍失败,检查设备路径是否正确(用设置页"扫描设备"确认)、
系统是否封闭签名(需系统定制预置权限)、是否已被其它进程占用。
**Q2:485 只发不收,或收发错乱?**
A:检查方向控制配置:确认发送前后延时是否足够(一般发送后延时 5~20ms);
若用 GPIO 方向控制,确认方向电平极性是否反了(发送时高/低电平)。
**Q3:收不到数据,但发送正常?**
A:先确认硬件 TX/RX 是否交叉连接;再确认波特率、校验位等参数与对端一致;
用串口助手验证硬件链路本身是否正常。
**Q4:串口和 485 能同时使用吗?**
A:可以。两个通道完全独立(独立文件句柄、独立线程、独立监听器),互不影响。
**Q5:支持哪些设备路径?**
A:常见为 `/dev/ttyS0~3`、`/dev/ttyMT*`(联发科)、`/dev/ttyHSL*`(高通)、
`/dev/ttyUSB*`(USB 转串口)等。设置页"扫描设备"可自动列出可用串口。
**Q6:会与其它三方库冲突吗?**
A:库零第三方依赖(仅 appcompat),无冲突风险。
**Q7:测试工具 App 的 minSdk 为什么是 24?**
A:测试工具为演示 UI 用了较新 AndroidX API,故 minSdk=24;
**Library 本身 minSdk=21(Android 5.0)**,商业项目按需调整 App 即可。
---
## 11. 版本记录
| 版本 | 日期 | 说明 |
| ---- | ---- | ---- |
| 1.0.0 | 2026-08 | 首版:串口 + 485 双通道独立封装、测试工具 App、本说明文档 |
---
*本库及测试工具由 宁工(17776688224)开发维护,欢迎商业项目集成使用。*