目录
1. 实现Java层的USB串口通信
1.1 初始化与权限管理
1.2 获取设备列表并请求权限
1.3 打开串口并启动I/O管理器
2. 接收串口数据并通过JNI传递到C++
2.1 数据接收与打印
2.2 通过JNI传递数据到C++
3. 关闭串口
2. 实现C++层的JNI交互与Qt UI集成
2.1 创建 PortWidgets 类
2.2 初始化与刷新串口列表
PortWidgets.cpp 文件中构造函数的实现
2.3 使用JNI与Java层交互
刷新串口设备列表
连接并打开串口
2.4 处理接收到的串口数据
JNI 回调函数与数据处理
2.5 关闭窗口
本文将介绍如何在Android平台上通过Java代码实现USB串口通信,并通过JNI(Java Native Interface)将数据传递到C++层进行处理。这个过程涵盖了从权限管理、串口数据收发、到C++信号与槽的实现。
完整代码:GitHub - TryTryTL/serialport
首先,我们需要创建一个 UsbController
类来管理USB设备。这个类负责初始化 UsbManager
,请求用户权限,并监听设备权限的授予情况。
package org.qtproject.example;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.util.Log;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import com.hoho.android.usbserial.util.SerialInputOutputManager;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class UsbController implements SerialInputOutputManager.Listener {
private Context context;
private UsbManager usbManager;
private PendingIntent permissionIntent;
private UsbSerialPort pendingUsbSerialPort;
private SerialInputOutputManager mSerialIoManager;
private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
在构造函数中,我们初始化了 UsbManager
并注册了一个 BroadcastReceiver
,以便监听用户是否授予了对设备的访问权限。
public UsbController(Context context) {
this.context = context;
this.usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
this.permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent("org.qtproject.example.USB_PERMISSION"), 0);
IntentFilter filter = new IntentFilter("org.qtproject.example.USB_PERMISSION");
context.registerReceiver(usbPermissionReceiver, filter);
}
我们提供了一个 getDeviceList
方法,返回当前连接的USB设备列表,并请求用户权限。
public String[] getDeviceList() {
HashMap usbDevices = usbManager.getDeviceList();
String[] deviceNames = new String[usbDevices.size()];
int index = 0;
for (UsbDevice device : usbDevices.values()) {
deviceNames[index++] = device.getDeviceName();
usbManager.requestPermission(device, permissionIntent);
}
return deviceNames;
}
当用户授予权限后,我们会打开USB串口,并启动 SerialInputOutputManager
以处理串口数据的收发。
public void requestPermissionAndOpenPort(UsbSerialPort usbSerialPort, int baudRate, int dataBits, int stopBits, int parity) {
UsbDevice device = usbSerialPort.getDriver().getDevice();
if (usbManager.hasPermission(device)) {
openSerialPort(usbSerialPort, baudRate, dataBits, stopBits, parity);
} else {
pendingUsbSerialPort = usbSerialPort;
usbManager.requestPermission(device, permissionIntent);
}
}
private void openSerialPort(UsbSerialPort usbSerialPort, int baudRate, int dataBits, int stopBits, int parity) {
UsbDeviceConnection connection = usbManager.openDevice(usbSerialPort.getDriver().getDevice());
if (connection != null) {
try {
usbSerialPort.open(connection);
usbSerialPort.setParameters(baudRate, dataBits, stopBits, parity);
Log.d("USB", "Serial port opened successfully.");
startIoManager(usbSerialPort);
} catch (IOException e) {
Log.e("USB", "Error opening serial port: " + e.getMessage());
}
} else {
Log.e("USB", "Could not open connection - permission might be required.");
}
}
private void startIoManager(UsbSerialPort usbSerialPort) {
if (usbSerialPort != null) {
mSerialIoManager = new SerialInputOutputManager(usbSerialPort, this);
mExecutor.submit(mSerialIoManager);
}
}
当串口接收到数据时,onNewData
方法会被回调。我们可以在此将数据转换为字符串并打印出来,确认接收是否成功。
@Override
public void onNewData(final byte[] data) {
try {
String receivedData = new String(data, "UTF-8");
Log.d("USB", "Received data: " + receivedData);
} catch (UnsupportedEncodingException e) {
Log.e("USB", "UTF-8 encoding is not supported", e);
}
onDataReceivedFromJava(data);
if (onDataReceivedListener != null) {
onDataReceivedListener.onDataReceived(data);
}
}
onDataReceivedFromJava
是一个本地方法,负责将接收到的数据传递到C++层。
public native void onDataReceivedFromJava(byte[] data);
在不再需要使用串口时,我们提供了一个 closeSerialPort
方法来停止I/O管理器并关闭串口。
public void closeSerialPort() {
stopIoManager();
if (pendingUsbSerialPort != null) {
try {
pendingUsbSerialPort.close();
Log.d("USB", "Serial port closed.");
} catch (IOException e) {
Log.e("USB", "Error closing serial port: " + e.getMessage());
}
pendingUsbSerialPort = null;
}
}
现在我们将进一步探讨如何在C++中处理这些数据,并将其与Qt的UI集成。
PortWidgets
类PortWidgets
类是Qt中的一个自定义控件,它负责管理串口通信,并通过JNI与Java层进行交互。我们首先在构造函数中初始化UI并设置相关的串口参数。
PortWidgets.h
文件结构
#ifndef PORTWIDGETS_H
#define PORTWIDGETS_H
#include
#include
#include
#include
namespace Ui {
class PortWidgets;
}
class PortWidgets : public QWidget
{
Q_OBJECT
public:
explicit PortWidgets(QWidget *parent = nullptr);
~PortWidgets();
// JNI 方法,用于从 Java 接收数据
void onDataReceivedFromJava(const QByteArray &data);
signals:
void receivedata(QByteArray data);
private slots:
void on_ptn_refresh_clicked();
void receive_data();
void on_ptn_conn_clicked();
void on_pushButton_clicked();
public:
void write_data(QByteArray writedata);
private:
Ui::PortWidgets *ui;
QSerialPort *myPort; // 串口指针
QAndroidJniObject javaUsbController;
static PortWidgets *instance;
std::unordered_map baudRateMap = {
{ 0, QSerialPort::Baud1200 },{ 1, QSerialPort::Baud2400 },{ 2, QSerialPort::Baud4800 },
{ 3, QSerialPort::Baud9600 },{ 4, QSerialPort::Baud19200 },{ 5, QSerialPort::Baud38400 },
{ 6, QSerialPort::Baud57600 },{ 7, QSerialPort::Baud115200 }
};
std::unordered_mapdataMap = {
{ 0, QSerialPort::Data5 },{ 1, QSerialPort::Data6 },{ 2, QSerialPort::Data7 },{ 3, QSerialPort::Data8 }
};
std::unordered_mapparityMap = {
{ 0, QSerialPort::NoParity },{ 1, QSerialPort::OddParity },{ 2, QSerialPort::EvenParity }
};
std::unordered_map stopMap = {
{0, QSerialPort::OneStop}, {1, QSerialPort::OneAndHalfStop}, {2, QSerialPort::TwoStop}
};
};
#endif // PORTWIDGETS_H
在 PortWidgets
的构造函数中,我们初始化了UI组件,并为不同的串口参数(波特率、数据位、校验位、停止位)设置了选项。此外,我们还调用了 on_ptn_refresh_clicked()
函数来刷新并显示当前可用的串口列表。
PortWidgets.cpp
文件中构造函数的实现PortWidgets* PortWidgets::instance = nullptr;
PortWidgets::PortWidgets(QWidget *parent)
: QWidget(parent)
, ui(new Ui::PortWidgets)
{
// 在构造函数中初始化静态实例指针
instance = this;
ui->setupUi(this);
this->setWindowTitle("PortWidgets");
this->setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
// 设置串口参数
QStringList baudratelist = {"1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"};
ui->CmbBaud->addItems(baudratelist);
ui->CmbBaud->setCurrentIndex(3);
QStringList datalist = {"5", "6", "7", "8"};
ui->CmbDataBits->addItems(datalist);
ui->CmbDataBits->setCurrentIndex(3);
QStringList checklist = {"No", "Even", "Odd"};
ui->CmbParity->addItems(checklist);
ui->CmbParity->setCurrentIndex(0);
QStringList stoplist = {"1", "1.5", "2"};
ui->CmbStopBits->addItems(stoplist);
ui->CmbStopBits->setCurrentIndex(0);
// 刷新串口列表
on_ptn_refresh_clicked();
myPort = new QSerialPort(this);
connect(myPort, &QSerialPort::readyRead, this, &PortWidgets::receive_data);
}
PortWidgets::~PortWidgets()
{
// 在析构函数中清除静态实例指针
instance = nullptr;
delete ui;
}
我们通过JNI与Java层进行交互,获取串口设备列表并打开串口。在 on_ptn_refresh_clicked()
和 on_ptn_conn_clicked()
方法中,我们使用 QAndroidJniObject
来调用Java方法。
void PortWidgets::on_ptn_refresh_clicked() {
QAndroidJniObject javaUsbController = QAndroidJniObject("org/qtproject/example/UsbController",
"(Landroid/content/Context;)V",
QtAndroid::androidContext().object());
QAndroidJniObject result = javaUsbController.callObjectMethod("getAllSerialPort", "()Ljava/util/List;");
QAndroidJniEnvironment env;
jobject listObject = result.object();
jclass listClass = env->FindClass("java/util/List");
jmethodID sizeMethod = env->GetMethodID(listClass, "size", "()I");
jmethodID getMethod = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;");
int size = env->CallIntMethod(listObject, sizeMethod);
QStringList deviceList;
for (int i = 0; i < size; i++) {
jobject serialPortObject = env->CallObjectMethod(listObject, getMethod, i);
QAndroidJniObject usbDevice = QAndroidJniObject::fromLocalRef(serialPortObject).callObjectMethod("getDevice", "()Landroid/hardware/usb/UsbDevice;");
QString deviceName = usbDevice.callObjectMethod("getDeviceName", "()Ljava/lang/String;").toString();
deviceList << deviceName;
qDebug() << "Serial Port found: " << deviceName;
}
ui->CmPortlist->clear(); // 清除旧的列表项
ui->CmPortlist->addItems(deviceList); // 添加新的端口列表
if (!deviceList.isEmpty()) {
ui->CmPortlist->setCurrentIndex(0); // 默认选择第一个端口
}
}
void PortWidgets::on_ptn_conn_clicked()
{
int portIndex = 0;
int baudRate = 9600; // 示例波特率
int dataBits = 8; // 示例数据位
int stopBits = 1; // 示例停止位
int parity = 0; // 示例校验位,无校验
QAndroidJniObject javaUsbController = QAndroidJniObject("org/qtproject/example/UsbController",
"(Landroid/content/Context;)V",
QtAndroid::androidContext().object());
QAndroidJniObject serialPorts = javaUsbController.callObjectMethod("getAllSerialPort", "()Ljava/util/List;");
QAndroidJniEnvironment env;
jobject listObject = serialPorts.object();
jobject serialPortObject = env->CallObjectMethod(listObject, env->GetMethodID(env->GetObjectClass(listObject), "get", "(I)Ljava/lang/Object;"), portIndex);
javaUsbController.callMethod("requestPermissionAndOpenPort",
"(Lcom/hoho/android/usbserial/driver/UsbSerialPort;IIII)V",
serialPortObject,
baudRate,
dataBits,
stopBits,
parity);
}
当从Java层接收到串口数据时,通过JNI回调函数将数据传递到C++层,并通过Qt的信号槽机制将数据传递到UI或其他需要处理的地方。
extern "C" JNIEXPORT void JNICALL
Java_org_qtproject_example_UsbController_onDataReceivedFromJava(JNIEnv *env, jobject, jbyteArray data) {
jsize length = env->GetArrayLength(data);
jbyte* byteArray = env->GetByteArrayElements(data, nullptr);
QByteArray receivedData((const char*)byteArray, length);
if (PortWidgets::instance) {
PortWidgets::instance->onDataReceivedFromJava(receivedData);
}
env->ReleaseByteArrayElements(data, byteArray, 0);
}
void PortWidgets::onDataReceivedFromJava(const QByteArray &data)
{
emit receivedata(data);
}
为了确保应用程序的UI响应,我们在关闭按钮的点击事件中调用了 close()
方法,以关闭当前窗口。
void PortWidgets::on_pushButton_clicked()
{
this->close();
}