You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 17:54:59