Delphi 10.3 Firemonkey Android应用如何通过USB-C连接POS热敏打印机?
解决Delphi FireMonkey Android通过USB-C连接POS热敏打印机的问题
解决思路
Android USB主机模式下,需通过系统UsbManager完成设备识别、权限申请,再通过USB批量传输端点发送打印指令——打印指令逻辑和你蓝牙代码完全通用,仅数据传输通道不同。
步骤1:配置Android权限
修改项目中的AndroidManifest.template.xml,添加USB主机功能声明和设备过滤规则:
<!-- 声明支持USB主机模式 --> <uses-feature android:name="android.hardware.usb.host" android:required="false" /> <application ...> <activity android:name="com.embarcadero.firemonkey.FMXNativeActivity" ...> <!-- 监听USB设备连接事件 --> <intent-filter> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> </intent-filter> <!-- 绑定USB设备过滤规则 --> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/usb_device_filter" /> </activity> </application>
在项目res\xml目录下新建usb_device_filter.xml(无此目录则手动创建),添加打印机匹配规则:
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- 匹配通用热敏打印机:设备类=7(打印机)、子类=1(热敏)、协议=2(批量传输) --> <usb-device class="7" subclass="1" protocol="2" /> <!-- 若通用规则无效,可指定打印机的VID/PID(替换XXXX/YYYY为实际值): <usb-device vendor-id="XXXX" product-id="YYYY" /> --> </resources>
步骤2:Delphi USB打印实现代码
以下代码可直接替换蓝牙打印的连接逻辑,打印指令部分完全复用你现有代码:
uses Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.Usb, Androidapi.Helpers, Androidapi.JNI.JavaTypes, System.SysUtils, System.Classes; var UsbManager: JUsbManager; UsbConn: JUsbDeviceConnection; UsbInterface: JUsbInterface; OutEndpoint: JUsbEndpoint; const ACTION_USB_PERMISSION = 'com.your.app.USB_PERMISSION'; // 自定义权限动作标识 // 查找已连接的POS热敏打印机 function FindPOSPrinter: JUsbDevice; var DeviceList: JHashMap; Iterator: JIterator; DeviceName: JString; TempDevice: JUsbDevice; begin Result := nil; UsbManager := TJUsbManager.Wrap(TAndroidHelper.Context.getSystemService(TJContext.JavaClass.USB_SERVICE)); DeviceList := UsbManager.getDeviceList; if Assigned(DeviceList) then begin Iterator := DeviceList.keySet.iterator; while Iterator.hasNext do begin DeviceName := TJString.Wrap(Iterator.next); TempDevice := TJUsbDevice.Wrap(DeviceList.get(DeviceName)); // 按设备类/子类/协议筛选热敏打印机 if (TempDevice.getDeviceClass = 7) and (TempDevice.getDeviceSubclass = 1) and (TempDevice.getDeviceProtocol = 2) then begin Result := TempDevice; Break; end; end; end; end; // 请求USB设备访问权限 function RequestUsbPermission(ADevice: JUsbDevice): Boolean; var PermissionIntent: JPendingIntent; begin Result := False; // 已有权限直接返回 if UsbManager.hasPermission(ADevice) then begin Result := True; Exit; end; // 创建权限请求广播意图 PermissionIntent := TJPendingIntent.JavaClass.getBroadcast(TAndroidHelper.Context, 0, TJIntent.JavaClass.init(StringToJString(ACTION_USB_PERMISSION)), 0); // 注册广播接收器(正式版需自定义Receiver监听授权结果,此处简化处理) TAndroidHelper.Context.registerReceiver(nil, TJIntentFilter.JavaClass.init(StringToJString(ACTION_USB_PERMISSION))); // 弹出权限请求对话框 UsbManager.requestPermission(ADevice, PermissionIntent); // 等待用户授权(仅测试用,正式版需通过广播回调处理) Sleep(2000); Result := UsbManager.hasPermission(ADevice); end; // 发送打印指令到USB打印机 procedure SendPrintCommand(Command: TBytes); var BytesSent: Integer; begin if Assigned(UsbConn) and Assigned(OutEndpoint) then begin BytesSent := UsbConn.bulkTransfer(OutEndpoint, Command, Length(Command), 5000); if BytesSent < 0 then raise Exception.Create('打印指令发送失败'); end; end; // 完整USB打印流程 procedure PrintViaUSB; var PrinterDevice: JUsbDevice; I: Integer; CmdBytes: TBytes; begin // 1. 查找打印机设备 PrinterDevice := FindPOSPrinter; if not Assigned(PrinterDevice) then raise Exception.Create('未找到USB连接的POS打印机'); // 2. 请求USB权限 if not RequestUsbPermission(PrinterDevice) then raise Exception.Create('未获取USB设备访问权限'); // 3. 打开设备连接 UsbConn := UsbManager.openDevice(PrinterDevice); if not Assigned(UsbConn) then raise Exception.Create('无法建立USB设备连接'); try // 4. 获取打印接口和输出端点 UsbInterface := PrinterDevice.getInterface(0); // 打印机通常使用第一个接口 if UsbConn.claimInterface(UsbInterface, True) then begin try // 查找批量输出端点(方向为OUT、传输类型为BULK) for I := 0 to UsbInterface.getEndpointCount - 1 do begin OutEndpoint := UsbInterface.getEndpoint(I); if (OutEndpoint.getDirection = TJUsbEndpoint.JavaClass.DIRECTION_OUT) and (OutEndpoint.getType = TJUsbEndpoint.JavaClass.USB_ENDPOINT_XFER_BULK) then Break; end; if not Assigned(OutEndpoint) then raise Exception.Create('未找到打印输出端点'); // 5. 发送打印指令(和你蓝牙代码完全一致) // 重置打印机 CmdBytes := TEncoding.GetEncoding('iso8859-2').GetBytes(chr(27) + chr(64)); SendPrintCommand(CmdBytes); // 开启加粗 CmdBytes := TEncoding.GetEncoding('iso8859-2').GetBytes(escBoldOn); SendPrintCommand(CmdBytes); // 空行 CmdBytes := TEncoding.GetEncoding('iso8859-2').GetBytes(escNewLine + escNewLine + escNewLine + escNewLine); SendPrintCommand(CmdBytes); Sleep(250); finally UsbConn.releaseInterface(UsbInterface); end; end else raise Exception.Create('无法获取USB接口控制权'); finally UsbConn.close; end; end;
关键注意事项
- 权限异步处理:示例用
Sleep简化了权限请求逻辑,正式开发必须实现BroadcastReceiver监听授权结果,避免阻塞主线程。 - 设备匹配:若通用规则无法识别你的打印机,需查询打印机的VID和PID,在
usb_device_filter.xml中指定具体值。 - 资源释放:必须确保关闭
UsbDeviceConnection并释放UsbInterface,否则会导致设备被占用无法再次连接。
内容的提问来源于stack exchange,提问作者sale2211
相关产品推荐
相关产品推荐

