1、确保在 AndroidManifest.xml 文件中添加以下权限:
<!-- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
2、创建一个串口通信的类,例如 SerialPortUtils.java:
import android.util.Log;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class SerialPortUtils {
private static final String TAG = "SerialPortUtils";
private FileDescriptor mFd;
private FileInputStream mInputStream;
private FileOutputStream mOutputStream;
// 设备路径
private static final String DEVICE_PATH = "/dev/ttyS0";
// 波特率
private static final int BAUD_RATE = 115200;
public void openSerialPort() {
try {
// 打开设备文件
mFd = new FileDescriptor();
mFd = open(DEVICE_PATH, BAUD_RATE);
mInputStream = new FileInputStream(mFd);
mOutputStream = new FileOutputStream(mFd);
Log.i(TAG, "串口打开成功");
} catch (Exception e) {
Log.e(TAG, "串口打开失败:" + e.getMessage());
}
}
public void closeSerialPort() {
try {
if (mInputStream != null) {
mInputStream.close();
mInputStream = null;
}
if (mOutputStream != null) {
mOutputStream.close();
mOutputStream = null;
}
close();
Log.i(TAG, "串口关闭成功");
} catch (IOException e) {
Log.e(TAG, "串口关闭失败:" + e.getMessage());
}
}
public void sendData(byte[] data) {
try {
if (mOutputStream != null) {
mOutputStream.write(data);
mOutputStream.flush();
Log.i(TAG, "发送数据成功");
} else {
Log.e(TAG, "串口未打开,发送数据失败");
}
} catch (IOException e) {
Log.e(TAG, "发送数据失败:" + e.getMessage());
}
}
public byte[] receiveData() {
byte[] buffer = new byte[1024];
try {
if (mInputStream != null) {
int size = mInputStream.read(buffer);
if (size > 0) {
byte[] data = new byte[size];
System.arraycopy(buffer, 0, data, 0, size);
Log.i(TAG, "接收到数据:" + new String(data));
return data;
}
} else {
Log.e(TAG, "串口未打开,接收数据失败");
}
} catch (IOException e) {
Log.e(TAG, "接收数据失败:" + e.getMessage());
}
return null;
}
// JNI方法,用于打开串口设备
private native static FileDescriptor open(String path, int baudRate);
// JNI方法,用于关闭串口设备
private native void close();
// 加载JNI库
static {
System.loadLibrary("serial_port");
}
}
上述代码中使用了 JNI(Java Native Interface)
来实现打开和关闭串口设备的方法。为了在 Java 代码中调用 JNI
方法,需要在项目的 src/main 目录下创建一个名为 jni 的文件夹,并在该文件夹下创建 serial_port.c
和 Android.mk
文件。其中serial_port.c
文件用于实现 JNI 方法,Android.mk
文件用于定义 JNI 模块。
下面是 serial_port.c
文件的示例代码:
#include "jni.h"
#include <fcntl.h>
#include <termios.h>
JNIEXPORT jobject JNICALL
Java_com_example_SerialPortUtils_open(JNIEnv *env, jclass type, jstring path, jint baudRate) {
const char *path_utf = (*env)->GetStringUTFChars(env, path, NULL);
int fd = open(path_utf, O_RDWR);
(*env)->ReleaseStringUTFChars(env, path, path_utf);
if (fd == -1) {
return NULL;
}
struct termios options;
tcgetattr(fd, &options);
speed_t speed;
switch (baudRate) {
case 9600:
speed = B9600;
break;
case 19200:
speed = B19200;
break;
case 38400:
speed = B38400;
break;
case 57600:
speed = B57600;
break;
case 115200:
speed = B115200;
break;
default:
speed = B9600;
}
cfsetispeed(&options, speed);
cfsetospeed(&options, speed);
// 8N1
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
// 无硬件流控制
options.c_cflag &= ~CRTSCTS;
// 可读可写
options.c_cflag |= CREAD | CLOCAL;
// 无软流控制
options.c_iflag &= ~(IXON | IXOFF | IXANY);
// 非规范模式
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// 设置等待时间和最小接收字符
options.c_cc[VTIME] = 1;
options.c_cc[VMIN] = 1;
tcflush(fd, TCIOFLUSH);
tcsetattr(fd, TCSANOW, &options);
jobject mFileDescriptor;
jclass mFileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");
jmethodID mFileDescriptorConstructor = (*env)->GetMethodID(env, mFileDescriptorClass,
"<init>", "()V");
jfieldID mFileDescriptorDescriptor = (*env)->GetFieldID(env, mFileDescriptorClass,
"descriptor", "I");
mFileDescriptor = (*env)->NewObject(env, mFileDescriptorClass, mFileDescriptorConstructor);
(*env)->SetIntField(env, mFileDescriptor, mFileDescriptorDescriptor, (jint) fd);
return mFileDescriptor;
}
JNIEXPORT void JNICALL
Java_com_example_SerialPortUtils_close(JNIEnv *env, jobject instance) {
jclass mFileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");
jfieldID mFileDescriptorDescriptor = (*env)->GetFieldID(env, mFileDescriptorClass,
"descriptor", "I");
jint descriptor = (*env)->GetIntField(env, instance, mFileDescriptorDescriptor);
close(descriptor);
}
Android.mk
文件的示例代码如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := serial_port
LOCAL_SRC_FILES := serial_port.c
include $(BUILD_SHARED_LIBRARY)
在编写 JNI 代码时,需要在项目的 src/main 目录下创建一个名为jniLibs
的文件夹,并在该文件夹下创建 armeabi
和 armeabi-v7a
两个文件夹,将编译生成的 .so
文件放置到对应文件夹中。注意,.so 文件的命名应与 LOCAL_MODULE
定义的模块名一致。
3、在你的活动(Activity)或服务(Service)中使用 SerialPortUtils 类来实现串口通信:
private SerialPortUtils serialPortUtils;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
// 打开串口
serialPortUtils.openSerialPort();
// 向串口写入数据
byte[] sendData = "Hello, Serial Port!".getBytes();
serialPortUtils.writeData(sendData);
// 读取串口数据
serialPortUtils.readData();
} catch (IOException | InvalidParameterException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 关闭串口
serialPortUtils.closeSerialPort();
}