Delphi经典蓝牙控件Android版Demo实战与避坑指南

发布时间:2026/10/15 14:00:34
Delphi经典蓝牙控件Android版Demo实战与避坑指南
简介这份资源面向使用 Delphi 开发 Android 移动应用的开发者尤其是需要在设备间实现无线通信、快速集成经典蓝牙功能的工程人员。包内提供经典蓝牙控件源码与配套演示工程涵盖蓝牙管理、设备信息、服务搜索绑定以及低层数据收发等核心模块帮助开发者理解控件在实际项目中的调用方式与集成路径。资源共160个文件以 pas、fmx、dproj、dpr、dpk、dcu 等 Delphi 工程与编译文件为主另含 res、xml、keystore、json 等配置与资源文件压缩包约1.03MB目录结构便于按控件与 Demo 两条线检索。已有1192人学习下载。通过研读源码与演示工程读者可掌握设备扫描、连接、断开、数据传输及错误处理等环节的实现思路并了解 Android 权限声明、设备兼容性、用户交互反馈与安全隐私等注意事项为自定义扩展蓝牙服务组件、优化传输效率提供可复用的参考。1. Delphi 经典蓝牙控件及DemoAndroid 版本到底能不能直接跑如果你手上有一个用 Delphi 写的 Android 应用需要跟单片机、打印机、扫码枪或者另一台手机做短距离数据交换那大概率绕不开经典蓝牙Bluetooth Classic也就是常说的 SPP 串口透传。很多人第一次搜「Delphi 经典蓝牙控件及Demo (android版本)」心里想的其实是一件事有没有一个能直接拖到窗体上、点几下就能连上设备的控件最好还带一份能跑的 Demo。答案是有的但坑比想象中多。Delphi 从 XE5 开始支持 AndroidFireMonkey 框架里自带一套跨平台蓝牙 API经典蓝牙在 Android 上走的是 RFCOMM 通道对应 SPP 协议。这套东西能解决「手机和串口设备通信」这个具体问题适合做工业手持机、蓝牙打印、医疗采集这类场景的开发者。但它不是拖控件就完事权限、配对、UUID、线程、编码每一环都能让你卡半天。下面我按自己踩过的路把选型、实现、参数和翻车点讲清楚。2. Delphi Android 经典蓝牙的控件选型与底层机制2.1 官方自带 API 和第三方控件的取舍Delphi 在 Android 上做经典蓝牙主流有三条路。第一条是官方System.Bluetooth单元这是 FireMonkey 自带的跨平台封装TBluetoothManager、TBluetoothAdapter、TBluetoothDevice、TBluetoothSocket这几个类就够用了。第二条是第三方商业控件比如某些串口通信套件它们把配对、连接、收发、断线重连都包好了代价是授权费和版本绑定。第三条是直接调 Android 的 JNI用Androidapi.JNI.Bluetooth那套接口灵活但代码量大。我一般推荐先用官方System.Bluetooth。原因很实际它跟着 Delphi 版本走不需要额外授权社区里能搜到的示例也大多基于它。第三方控件的优势在于断线重连和粘包处理做得细但如果你只是做点对点透传官方 API 完全够。要注意的是官方 API 在经典蓝牙这块Android 平台底层就是 RFCOMMiOS 平台根本不支持经典蓝牙只支持 BLE所以这份代码天生是 Android 专属别指望一套代码两端跑。选型时还要看你的 Delphi 版本。较新的版本对 Android 权限模型适配更好尤其是 Android 12 之后蓝牙权限拆成了BLUETOOTH_CONNECT和BLUETOOTH_SCAN老版本 IDE 生成的清单文件不会自动带上需要手动改。这一点后面避坑章节会细说。2.2 RFCOMM、UUID 和 SPP 到底怎么对应经典蓝牙通信的核心是 RFCOMM它模拟串口在两条蓝牙设备之间建立一条可靠的字节流通道。每个服务用一个 UUID 标识。串口透传设备SPP用的标准 UUID 是00001101-0000-1000-8000-00805F9B34FB这个值几乎所有蓝牙串口模块都认。你在代码里创建 socket 时如果对方是标准 SPP 设备直接用这个 UUID如果对方是自定义服务就得问硬件方要他们广播的 UUID。连接流程是这样的先拿到本地适配器TBluetoothManager.Current再取CurrentAdapter然后要么从已配对设备列表里找要么发起发现。找到目标TBluetoothDevice后调CreateClientSocket拿到TBluetoothSocket再Connect。这里有个关键点Connect是阻塞的放在主线程会卡界面必须丢到后台线程。官方 Demo 里经常直接在主线程调真机上界面会假死几秒用户以为崩了。数据收发用的是TBluetoothSocket.Input和Output两个流。读的时候要注意蓝牙是流式协议没有消息边界你发 10 个字节对方可能一次收到 6 个、下一次收到 4 个。所以应用层必须自己定协议比如固定长度、或者用结束符、或者长度前缀。这是新手最容易翻车的地方以为SendData一次对方就ReceiveData一次实际根本不是。2.3 最小可跑通的连接代码下面这段是连接和发送的最小骨架放在一个后台线程里执行。注意System.Bluetooth和System.Classes的引用。uses System.Bluetooth, System.Classes, System.SysUtils; const SPP_UUID {00001101-0000-1000-8000-00805F9B34FB}; procedure TFormBT.ConnectAndSend(const ADevice: TBluetoothDevice; const AText: string); var Socket: TBluetoothSocket; Data: TBytes; begin // 用标准 SPP UUID 创建客户端 socket Socket : ADevice.CreateClientSocket(StringToGUID(SPP_UUID), False); if Socket nil then begin // 设备不支持该 UUID 的服务直接退出 Exit; end; try Socket.Connect; // 阻塞务必在后台线程 Data : TEncoding.UTF8.GetBytes(AText); Socket.Output.Write(Data, Length(Data)); // 写入字节流 Socket.Output.Flush; // 确保立即发出 finally Socket.Close; Socket.Free; end; end;逻辑说明CreateClientSocket第二个参数False表示不加密SPP 设备一般用不加密通道如果对方要求安全连接就传True。Connect没有超时参数这是官方 API 的一个缺陷连不上会一直等实际项目里要自己用线程加超时控制。Output.Write写入的是字节数组Flush很重要不调的话数据可能压在缓冲区里迟迟不发。发送完立刻Close在短连接场景没问题但如果是长连接socket 要保存成成员变量不能每次发完就关。参数上UUID 字符串转TGUID时格式必须带花括号大小写不敏感。TEncoding.UTF8是文本编码如果对方是 GBK 设备很多国产串口模块默认 GBK这里要换成对应的编码否则中文全是乱码。这个编码问题我在现场被坑过整整一个下午。3. 从配对到收发的完整落地步骤3.1 权限声明和运行时申请Android 6.0 之后蓝牙扫描需要定位权限因为扫描结果能推断位置。Android 12 之后又拆出BLUETOOTH_CONNECT、BLUETOOTH_SCAN。Delphi 里要在工程选项的 Uses Permissions 里勾选或者手动改AndroidManifest.template.xml。清单里至少要包含uses-permission android:nameandroid.permission.BLUETOOTH/ uses-permission android:nameandroid.permission.BLUETOOTH_ADMIN/ uses-permission android:nameandroid.permission.BLUETOOTH_CONNECT/ uses-permission android:nameandroid.permission.BLUETOOTH_SCAN/ uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION/注意BLUETOOTH_CONNECT和BLUETOOTH_SCAN是运行时权限光声明不够代码里要用PermissionsService.RequestPermissions动态申请。很多人只改了清单结果在 Android 12 真机上CurrentAdapter直接返回 nil就是因为没申请运行时权限。申请时机建议放在窗体OnCreate之后别放在启动瞬间否则弹窗还没出来用户就点了别处。3.2 枚举已配对设备和发现新设备已配对设备列表拿起来很简单CurrentAdapter.PairedDevices返回一个数组遍历就行。发现新设备要调StartDiscovery然后监听OnDiscoveryEnd事件。发现过程耗电且慢一般 10 到 15 秒别频繁调。procedure TFormBT.ListPaired; var Adapter: TBluetoothAdapter; Dev: TBluetoothDevice; begin Adapter : TBluetoothManager.Current.CurrentAdapter; if Adapter nil then begin Memo1.Lines.Add(适配器不可用检查权限或蓝牙开关); Exit; end; for Dev in Adapter.PairedDevices do Memo1.Lines.Add(Dev.DeviceName | Dev.Address); end;DeviceName可能为空尤其是某些模块没广播名称这时候只能靠Address识别。Address是 MAC 格式字符串比较时注意大小写统一。已配对列表里可能混着耳机、手环实际项目里通常按名称前缀过滤或者让用户手动选。3.3 后台线程收数据与粘包处理收数据必须开线程因为Input.Read也是阻塞的。下面是一个接收线程的核心逻辑用长度前缀协议举例前两字节是后续数据长度。type TRecvThread class(TThread) private FSocket: TBluetoothSocket; FOnPacket: TProcTBytes; protected procedure Execute; override; public constructor Create(ASocket: TBluetoothSocket; AOnPacket: TProcTBytes); end; procedure TRecvThread.Execute; var Buf: array[0..255] of Byte; Got, Need, Len: Integer; Packet: TBytes; begin while not Terminated do begin Got : FSocket.Input.Read(Buf, SizeOf(Buf)); // 阻塞读 if Got 0 then begin // 返回 0 或负值通常意味着对端断开 Break; end; // 这里把 Buf[0..Got-1] 追加到自己的缓冲区 // 再按长度前缀切包切出完整包后回调 FOnPacket // 具体缓冲逻辑略关键是不要假设一次读到一个完整包 end; end;逻辑说明Input.Read返回实际读到的字节数可能小于请求的 256。返回 0 或抛异常表示连接断开要跳出循环并触发重连。粘包处理的核心是维护一个动态缓冲区每次读到的数据追加进去然后循环检查是否够一个完整包够就切出来回调不够就等下次。这个缓冲区必须是线程私有的回调到主线程更新 UI 要用TThread.Queue不能直接操作控件。参数上Read的缓冲区大小设 256 到 1024 都行太小会增加循环次数太大浪费内存。真正决定性能的是你的协议设计长度前缀比结束符可靠因为二进制数据里可能恰好出现结束符字节。4. 避坑与常见问题排查4.1 连接一直卡住不返回现象调Connect之后界面卡死或者后台线程一直不往下走。原因Connect没有超时机制对端不在范围内或服务不存在时会长时间阻塞。解决把连接放在独立线程主线程用定时器监控超过 8 到 10 秒没成功就强制Close并提示用户。别指望 API 自己超时。4.2 Android 12 上适配器返回 nil现象CurrentAdapter为 nil或者PairedDevices空数组。原因BLUETOOTH_CONNECT运行时权限没申请或者清单里没声明。解决确认清单包含新权限并在代码里用PermissionsService申请申请结果回调里再初始化适配器。老版本 IDE 生成的清单模板默认没有这两个权限必须手动补。4.3 中文乱码现象发出去的中文对方收到是乱码或者收到的中文显示问号。原因编码不一致Delphi 默认 UTF-8很多串口设备默认 GBK。解决确认对方编码用对应的TEncoding转换。如果对方是 GBKDelphi 里可以用TEncoding.GetEncoding(936)。发送前转一次接收后转回来。4.4 数据发出去对方收不全现象调用Write返回成功但对方只收到一部分。原因没调Flush或者 socket 被提前Close。解决每次写完调Flush长连接场景不要发完就关 socket保持连接直到业务结束。另外确认写入的字节数正确Write的第二个参数是字节数不是字符数。4.5 断线后重连失败现象设备断开后重新Connect报错。原因旧 socket 没释放或者系统缓存了旧连接状态。解决断开时确保Close并Free重连前 sleep 几百毫秒必要时先调CancelDiscovery。有些模块需要重新配对才能连这种情况只能引导用户去系统设置里取消配对再重来。5. 进阶把经典蓝牙封装成可复用组件的几个技巧做到能收发只是及格线真正让代码能上生产还得解决几个工程问题。第一个是状态机。连接、已连接、断开、重连中这几个状态用枚举管理所有操作先判断当前状态避免重复连接或者对已关闭的 socket 写数据。我一般写一个TBtClient类把适配器、socket、接收线程、状态、重连计数全包进去窗体只调Connect、Send、OnData三个接口。第二个是心跳和超时。蓝牙链路在设备休眠、距离变远时会假死socket 不报错但数据不通。做法是每隔几秒发一个心跳包连续几次没回应就主动断开重连。心跳包内容自定义别和业务数据冲突。重连要有退避策略第一次隔 1 秒第二次 2 秒最多退到 10 秒避免疯狂重连耗电。第三个是日志。蓝牙问题现场排查极难因为你看不到空口数据。我的习惯是在TBtClient里加一个日志回调把每次连接、发送字节数、接收字节数、异常信息都记下来出问题时让现场人员导出日志。这个习惯帮我省了无数次跑现场的时间。第四个是协议层和传输层分离。传输层只管字节流收发协议层负责组包解包。这样换设备、换协议时只改协议层传输层不动。协议层建议用「魔数 长度 载荷 校验」的结构魔数用来快速同步校验用来发现传输错误。验证方法上别只用一台真机测。准备两台 Android 手机一台装你的 Delphi 应用另一台装一个通用的蓝牙串口调试工具互相收发对比。再准备一个真实的串口模块用 USB 转 TTL 接到电脑电脑端开串口助手看数据。三个方向都通了才算真正跑通。我见过太多人只在自己应用里自收自发一接真实设备就露馅。最后说个血泪经验Delphi 的System.Bluetooth在不同 Android 版本上行为有差异尤其是 Android 13、14 对后台蓝牙限制更严。如果你的应用需要在后台保持连接得考虑前台服务否则系统会掐断。这个不是 Delphi 的问题是 Android 的省电策略提前想清楚你的场景是否允许后台运行。希望帮到你。本文还有配套的精品资源点击获取