在工业控制、智能硬件、物联网等场景中,Android 设备常需与外部硬件(如传感器、PLC、读卡器)通过串口通信。串口作为一种传统的有线通信方式,以其简单可靠的特性,至今仍是设备间数据交互的重要选择。本文将从硬件基础到代码实现,全面讲解 Android 串口通讯的开发流程,帮助开发者快速搭建串口交互功能。
串口通讯(Serial Communication)是指通过串行端口(如 RS-232、RS-485)实现设备间的数据传输。与 USB、蓝牙等方式相比,串口通讯具有硬件简单、协议灵活、抗干扰能力强的特点,适合短距离、低速率的数据交换(如工业设备状态监控、传感器数据采集)。
串口通讯的核心参数(需与外部设备一致):
Android 设备本身通常不自带物理串口(手机、平板以 USB、蓝牙为主),需通过以下方式实现串口通讯:
1.USB 转串口
通过 “USB 转 TTL/RS232” 模块(如 CH340、PL2303 芯片),将 Android 设备的 USB 接口转换为串口,这是最常用的方案。
2.内置串口的定制设备
工业级 Android 设备(如工控平板、物联网网关)通常自带硬件串口(如 UART 接口),可直接使用。
3.蓝牙转串口
通过蓝牙模块(如 HC-05)将蓝牙信号转为串口数据,适合无线串口场景(但本质是蓝牙通讯,本文以有线串口为例)。
在 Android(基于 Linux)中,所有硬件设备都以 “设备节点” 形式存在于文件系统中。串口设备节点通常路径为:
串口通讯的本质是对设备节点进行文件读写操作:向节点写入数据(发送),从节点读取数据(接收)。
硬件连接示例:
Android 设备 USB 口 → USB 转串口模块(CH340) → 外部串口设备(如传感器),确保模块电源正常(部分模块需外接 5V 电源)。
USB 转串口模块需要 Android 设备支持对应的驱动:
可通过adb shell ls /dev/ttyUSB*验证:插入模块后若能看到ttyUSB0节点,说明驱动已识别。
在AndroidManifest.xml中添加 USB 相关权限:
对于 Android 6.0+,需动态申请 USB 设备访问权限(后续代码会涉及)。
Android 原生 SDK 未提供串口操作 API,需使用第三方库封装串口读写逻辑。推荐使用android-serialport-api(GitHub 开源项目),核心功能是封装JNI层对串口设备节点的操作。
集成方式:
dependencies {
implementation 'com.github.licheedev:Android-SerialPort:2.1.1'
}
串口通讯的开发流程可概括为 “打开串口→发送数据→接收数据→关闭串口”,核心类为SerialPort(封装节点操作)和InputStream/OutputStream(数据读写流)。
打开串口需指定设备节点路径和串口参数(波特率、校验位等),并申请设备访问权限。
import android.hardware.usb.UsbManager;
import com.licheedev.hwutils.HwUtils;
import com.licheedev.serialport.SerialPort;
import java.io.File;
import java.io.IOException;
public class SerialHelper {
private SerialPort mSerialPort;
private OutputStream mOutputStream;
private InputStream mInputStream;
private ReadThread mReadThread; // 接收数据的线程
// 打开串口
public boolean open(String devicePath, int baudRate) {
// 1. 检查设备节点是否存在
File device = new File(devicePath);
if (!device.exists()) {
Log.e("Serial", "设备节点不存在:" + devicePath);
return false;
}
// 2. 申请USB设备权限(Android 6.0+)
if (HwUtils.hasUsbPermission(device)) {
try {
// 3. 打开串口(参数:设备、波特率、校验位等)
// 第三个参数:0表示无校验,1表示奇校验,2表示偶校验
mSerialPort = new SerialPort(device, baudRate, 0);
// 获取读写流
mOutputStream = mSerialPort.getOutputStream();
mInputStream = mSerialPort.getInputStream();
// 4. 启动接收线程(持续读取数据)
mReadThread = new ReadThread();
mReadThread.start();
return true;
} catch (IOException e) {
Log.e("Serial", "打开串口失败:" + e.getMessage());
return false;
}
} else {
// 申请权限(需在Activity中处理权限回调)
HwUtils.requestUsbPermission(device);
return false;
}
}
// 接收数据的线程(必须在子线程中读取,避免阻塞主线程)
private class ReadThread extends Thread {
@Override
public void run() {
super.run();
byte[] buffer = new byte[1024]; // 缓冲区
int bytes;
while (!isInterrupted() && mInputStream != null) {
try {
// 读取数据(阻塞操作,若无数据会等待)
bytes = mInputStream.read(buffer);
if (bytes > 0) {
// 拷贝有效数据(避免缓冲区多余数据)
byte[] data = new byte[bytes];
System.arraycopy(buffer, 0, data, 0, bytes);
// 回调给UI层处理(需切换到主线程)
onDataReceived(data);
}
} catch (IOException e) {
Log.e("Serial", "读取数据失败:" + e.getMessage());
break;
}
}
}
}
// 数据接收回调(需在主线程处理)
private void onDataReceived(byte[] data) {
// 示例:转换为十六进制字符串
String hexData = bytesToHex(data);
Log.d("Serial", "收到数据:" + hexData);
// 通知UI更新(可通过Handler或接口回调)
}
// 字节数组转十六进制字符串(便于调试)
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
String hex = String.format("%02X ", b);
sb.append(hex);
}
return sb.toString();
}
}
关键说明:
通过OutputStream向串口写入字节数组,需注意数据格式(与外部设备约定,如固定长度、带校验位)。
// 发送数据(字节数组)
public void send(byte[] data) {
if (mOutputStream == null) {
Log.e("Serial", "串口未打开");
return;
}
try {
mOutputStream.write(data);
mOutputStream.flush(); // 立即发送
Log.d("Serial", "发送数据:" + bytesToHex(data));
} catch (IOException e) {
Log.e("Serial", "发送失败:" + e.getMessage());
}
}
// 发送字符串(需转为字节数组,指定编码)
public void send(String text) {
send(text.getBytes(StandardCharsets.UTF_8));
}
// 发送十六进制字符串(如"AA BB CC")
public void sendHex(String hex) {
byte[] data = hexToBytes(hex);
if (data != null) {
send(data);
}
}
// 十六进制字符串转字节数组(工具方法)
private byte[] hexToBytes(String hex) {
hex = hex.replaceAll(" ", ""); // 去除空格
int len = hex.length();
if (len % 2 != 0) {
Log.e("Serial", "十六进制字符串长度应为偶数");
return null;
}
byte[] bytes = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
bytes[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16);
}
return bytes;
}
使用完毕后需关闭串口,停止接收线程,避免资源泄漏。
// 关闭串口
public void close() {
// 停止接收线程
if (mReadThread != null) {
mReadThread.interrupt();
mReadThread = null;
}
// 关闭流
try {
if (mInputStream != null) {
mInputStream.close();
mInputStream = null;
}
if (mOutputStream != null) {
mOutputStream.close();
mOutputStream = null;
}
} catch (IOException e) {
Log.e("Serial", "关闭流失败:" + e.getMessage());
}
// 关闭串口
if (mSerialPort != null) {
mSerialPort.close();
mSerialPort = null;
}
}
在 Activity 中处理 USB 权限申请回调,确保能访问串口设备:
public class SerialActivity extends AppCompatActivity {
private SerialHelper mSerialHelper;
private static final String ACTION_USB_PERMISSION = "com.example.serial.USB_PERMISSION";
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
synchronized (this) {
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if (device != null) {
// 权限已获取,打开串口
mSerialHelper.open("/dev/ttyUSB0", 9600);
}
} else {
Log.e("Serial", "用户拒绝了USB权限");
}
}
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_serial);
mSerialHelper = new SerialHelper();
// 注册USB权限广播
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);
// 初始化时尝试打开串口
findViewById(R.id.btn_open).setOnClickListener(v -> {
// 检查是否有USB权限
File device = new File("/dev/ttyUSB0");
if (!HwUtils.hasUsbPermission(device)) {
// 申请权限
PendingIntent permissionIntent = PendingIntent.getBroadcast(
this, 0, new Intent(ACTION_USB_PERMISSION),
PendingIntent.FLAG_IMMUTABLE
);
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
usbManager.requestPermission(HwUtils.getUsbDevice(device), permissionIntent);
} else {
// 已有权限,直接打开
mSerialHelper.open("/dev/ttyUSB0", 9600);
}
});
// 发送数据按钮
findViewById(R.id.btn_send).setOnClickListener(v -> {
String text = ((EditText) findViewById(R.id.et_send)).getText().toString();
mSerialHelper.send(text);
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// 注销广播,关闭串口
unregisterReceiver(mUsbReceiver);
mSerialHelper.close();
}
}
以 “Android 设备与温湿度传感器通过串口通信” 为例,说明完整流程。
// 在SerialHelper的onDataReceived中解析数据
private void onDataReceived(byte[] data) {
String hexData = bytesToHex(data);
Log.d("Serial", "收到原始数据:" + hexData);
// 按协议解析(示例:AA 01 02 03 BB)
if (data.length == 5
&& data[0] == (byte) 0xAA
&& data[4] == (byte) 0xBB) {
int temp = data[2] & 0xFF; // 温度值(转为无符号整数)
int humi = data[3] & 0xFF; // 湿度值
// 切换到主线程更新UI
runOnUiThread(() -> {
tvTemp.setText("温度:" + temp + "℃");
tvHumi.setText("湿度:" + humi + "%");
});
} else {
Log.w("Serial", "数据格式不符");
}
}
// 发送请求指令(在Activity中调用)
findViewById(R.id.btn_request).setOnClickListener(v -> {
// 发送指令:AA 00 BB
mSerialHelper.sendHex("AA 00 BB");
});
为确保数据可靠性,可在协议中添加校验位(如 CRC 校验):
// 计算CRC16校验(示例)
public static int crc16(byte[] data) {
int crc = 0xFFFF;
for (byte b : data) {
crc ^= (int) b & 0xFF;
for (int i = 0; i < 8; i++) {
if ((crc & 1) != 0) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
// 发送数据时添加CRC
byte[] data = "Hello".getBytes();
int crc = crc16(data);
byte[] crcBytes = new byte[2];
crcBytes[0] = (byte) (crc & 0xFF);
crcBytes[1] = (byte) (crc >> 8);
// 拼接数据和CRC后发送
byte[] sendData = ArrayUtils.addAll(data, crcBytes);
监测串口连接状态,断开时自动重连:
// 在SerialHelper中添加状态监测
private void checkConnectStatus() {
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (mSerialPort == null || !isReading()) { // 未打开或读取中断
Log.w("Serial", "尝试重连串口");
open("/dev/ttyUSB0", 9600);
}
checkConnectStatus(); // 循环监测
}, 3000); // 每3秒检查一次
}
对于需要连接多个串口设备的场景(如工业控制),可封装SerialManager管理多个SerialHelper实例:
public class SerialManager {
private Map mSerialHelpers = new HashMap<>();
// 获取指定串口的助手
public SerialHelper getSerial(String devicePath) {
if (!mSerialHelpers.containsKey(devicePath)) {
mSerialHelpers.put(devicePath, new SerialHelper());
}
return mSerialHelpers.get(devicePath);
}
// 关闭所有串口
public void closeAll() {
for (SerialHelper helper : mSerialHelpers.values()) {
helper.close();
}
mSerialHelpers.clear();
}
}
Android 串口通讯的核心是 “通过设备节点进行文件读写”,开发流程遵循 “打开→发送→接收→关闭” 的逻辑。关键在于:
实际开发中,需根据外部设备的通讯协议(数据格式、指令集)定制解析逻辑,并做好异常处理(断线重连、数据校验)。通过本文的步骤,可快速搭建稳定的串口通讯功能,满足工业控制、智能硬件等场景的需求。