蓝牙传文件底层协议与开发实践:从OPP到BLE的完整指南 在日常使用中“蓝牙给其他设备传输文件”这个操作看起来非常简单设备 A 打开蓝牙选择文件点击分享选一个蓝牙设备设备 B 确认接收进度条走完文件就到了。这件事确实可以这么完成但它只覆盖了最表面的那条路径。一旦把视角切到开发者一侧问题会立刻变多为什么有些设备只能传小文件传大文件总在 30% 左右断开为什么手机传图片走的是蓝牙而蓝牙耳机播放音乐走的却是另一套逻辑为什么用 ESP32 或 HC-05 模块时手机 App 扫描不到为什么同样是蓝牙有的设备之间能互相发现有的设备却像隐形一样这些问题背后并不是某一个“发送文件”按钮而是一整条蓝牙协议栈链路。本文会从蓝牙文件传输的底层协议讲起分别覆盖普通用户与开发者两条视角先说明系统层面怎么操作最稳妥再给出 Android、Python、BLE 三种常见开发路径最后整理一套排查链路和生产环境选型建议。读完以后至少能回答“这个文件能不能走蓝牙传、应该走哪条协议、断了之后从哪一层开始查”这三个问题。1. 先弄懂蓝牙传文件走的是哪条协议链路1.1 第一次点击“蓝牙发送”时系统做了什么蓝牙文件传输并不是一个单独的大协议而是由多层协议协作完成。以最常见的 Android 手机互传文件为例用户在文件管理器里点击“分享 - 蓝牙”后系统并不是直接把文件字节流塞进蓝牙无线电而是调用了蓝牙协议栈上层的一个应用规范Profile尤其是 Object Push Profile简称 OPP。OPP 的作用是定义“如何把一个可识别的对象比如图片、文档、联系人 vCard、音频文件从一台设备推到另一台设备”。它依赖底层几条链路RFCOMM一种串口仿真协议负责建立一条可靠的数据传输通道。因为经典蓝牙支持 RFCOMM很多文件传输逻辑都建立在它之上。L2CAP位于基带之上负责把上层数据分组成 L2CAP 包并提供逻辑通道管理。RFCOMM 本身通过 L2CAP 承载。基带和射频层负责真正的无线数据收发。也就是说蓝牙传文件的核心链条是应用层使用 OPP 规范 - 把文件转换成可推送对象 - 通过 RFCOMM 通道 - 由 L2CAP 封装成蓝牙逻辑通道包 - 经链路管理和基带发送到对端。这里有一个容易被忽略的点OPP 主要面向“小对象推送”它并不擅长超大数据流的断点续传。系统自带的蓝牙传输功能在手机上通常对单个文件大小有限制或表现不稳定原因不在“蓝牙硬件速度不够”而在上层 OPP 协议的会话控制、超时和接收方存储路径设计都比较简单。1.2 为什么传文件不用 HFP 或 A2DP蓝牙协议栈里有很多 Profile比如HFPHands-Free Profile用于蓝牙耳机和车载免提通话。A2DPAdvanced Audio Distribution Profile用于高质量音频流传输比如手机向蓝牙音箱推音乐。HIDHuman Interface Device用于键盘、鼠标、手柄。OPP用于对象推送。FTPFile Transfer Profile用于浏览和传输远端文件能力比 OPP 更强但普通手机系统很少完整暴露给用户。很多初学者会混淆既然 A2DP 也在传输“数据”为什么不能直接拿它传文件A2DP 设计目标是单向、实时、容忍部分丢包的音频流它没有面向文件的可靠传输语义。即使 A2DP 在链路上使用了 ACLAsynchronous Connection-Oriented链路应用层也不会去关心某个音频帧是否完整送达。而文件传输要求的是“一个字节都不能错”所以必须走 OPP/FTP 这类面向对象的可靠传输并使用 RFCOMM 通道来保证流式数据的顺序和完整。这也是为什么耳机听歌可以用 A2DP但耳机本身并不能直接帮你传文件。1.3 经典蓝牙与 BLE传文件场景下怎么选蓝牙 4.0 之后行业把蓝牙分成三种模式经典蓝牙BR/EDR、低功耗蓝牙BLE、双模蓝牙同时支持前两者。它们在传文件场景里的角色差异非常关键。维度经典蓝牙 BR/EDR低功耗蓝牙 BLE连接建立速度相对较慢需要多次握手更快适合快速连接传输速率经典模式理论速率更高适合持续流传输单次连接速率相对有限吞吐依赖 MTU 和连接间隔典型 ProfileOPP、FTP、SPP、A2DP、HFPGATT、HID-over-GATT、ANCS 等文件传输适合度更适合系统级传文件可以传但要自己设计分包、确认、重传常见设备手机、老式蓝牙音箱、HC-05 模块手环、传感器、ESP32 BLE、手机周边外设常见误区是“设备是蓝牙 4.0/5.0所以一定能互传文件”。蓝牙版本号解决的是物理层、链路层能力能不能传文件取决于上层协议栈是否实现了 OPP/FTP以及设备是否启用了 RFCOMM 服务。很多 BLE 外设只实现了 GATT 服务它根本没有“接收文件”的业务逻辑。所以开发者在设计“蓝牙传文件”功能时第一件事不是选什么文件库而是确认目标设备支持哪条协议栈。2. 准备环境版本、权限、可发现性先对齐2.1 蓝牙版本和能力的基本判断蓝牙主版本号并不直接决定“能不能传文件”但它会影响速率上限、连接稳定性和功耗。以常见设备为例蓝牙 2.1 EDR经典蓝牙支持 OPP速率约 2 到 3 Mbps 的实际吞吐已经能覆盖小文件传输。蓝牙 4.0/4.2引入 BLE双模设备同时支持 BR/EDR 和 BLE。蓝牙 5.0/5.1/5.2/5.3主要增强 BLE 速率、广播能力和定位能力经典蓝牙的 OPP 文件传输能力没有本质变化。学习环境中不需要刻意追求最新版本常见 Android 手机和 PC 自带的蓝牙都能完成小文件传输。如果要把传输链路做到生产级别才需要针对蓝牙 SOC 模组的规格、天线布局、协议栈实现和系统 API 做更细的确认。2.2 Android 与 Windows 的系统权限准备Android 使用蓝牙涉及多个权限不同 Android 版本要求不同Android 6 到 11需要BLUETOOTH、BLUETOOTH_ADMIN同时扫描附近蓝牙设备通常需要位置权限因为系统把蓝牙扫描结果视为近似位置信息。Android 12 及以上需要BLUETOOTH_SCAN、BLUETOOTH_CONNECT、BLUETOOTH_ADVERTISE等新运行时权限。Android 13 及以上定位相关限制继续变化实际还要结合目标 SdkVersion 处理。简单判断如果写了一个 Android 蓝牙 App在低版本上运行正常在 Android 12 以上的设备上闪退或扫描不到设备优先检查运行时权限是否申请完整。Windows 侧常见的现象是“设置里面没有蓝牙开关”或“设备管理器里找不到蓝牙适配器”。这类问题通常不是代码问题而是蓝牙驱动被禁用或未安装。BIOS 中的蓝牙被关闭。Windows 蓝牙服务未启动服务名通常是Bluetooth Support Service。电脑本身没有蓝牙模块比如部分台式机。检查时可以用devmgmt.msc打开设备管理器看是否有蓝牙节点用services.msc检查蓝牙服务状态。若确定硬件存在但驱动异常需要重新安装 OEM 驱动而不是在设置面板里反复重启开关。2.3 可发现模式不是“开了蓝牙”就能被搜到很多用户误以为“我开了蓝牙别人就能搜到我”实际上蓝牙有三种状态关闭不可见不能连接。开启但不可发现可以连接已知设备但广播里看不到自己的名字。开启且可发现其他设备可以扫描到通常只在设置页停留一段时间比如 120 秒。做两台手机互传时接收方必须停留在蓝牙设置页并保持“可被发现”状态否则发送方会一直搜不到目标设备。开发调试也一样设备之间能不能互相扫描到和是不是已经配过对、是否开启可见性、是否有系统限制都有关系。3. 手机、PC 与模块之间传文件的标准操作路径3.1 Android 手机向另一台 Android 手机发送文件这是最典型的日常场景操作路径如下两台手机都打开设置中的蓝牙。接收方进入蓝牙设置页保持当前页面使设备处于可被发现状态。发送方在文件管理器中选择要发送的文件点击“分享”。在分享面板中选择“蓝牙”。发送方会搜索附近设备点击接收方名称。接收方弹出接收确认对话框点击“接受”。等待进度条完成文件默认保存在“蓝牙接收”目录中路径通常为/storage/emulated/0/Bluetooth/或系统下载目录。验证方式很简单接收完成后打开文件管理器查看文件大小是否与源文件一致再尝试打开该文件。如果文件打不开优先怀疑传输不完整。3.2 Windows 与手机互传文件Windows 10/11 自带蓝牙文件传输入口但位置比较隐蔽。打开“设置 - 蓝牙和其他设备”确保蓝牙开启。点击“添加蓝牙或其他设备”把手机设为可被发现。完成配对。在系统搜索栏输入“蓝牙”找到“通过蓝牙发送或接收文件”程序。选择“发送文件”挑选目标设备和本地文件。接收方手机确认后开始传输。Windows 向手机发送小文件时比较方便但向手机发送大文件并不稳定原因和 OPP 的会话管理有关。反过来手机向 Windows 发送文件往往受 Windows 侧默认接收目录和系统安全提示限制。3.3 开发板与蓝牙模块场景HC-05、ESP32 怎么传数据单片机开发中HC-05 模块是典型的经典蓝牙串口透传模块。它本身不实现 OPP 文件推送而是把蓝牙链路映射成串口数据流。手机端需要安装支持 SPP 的蓝牙串口调试工具连接后向串口写入文件内容对端通过 UART 读取数据。流程上更像“数据流传输”而不是“文件推送到系统相册”因为 HC-05 不知道什么是文件它只负责把字节流传给单片机。ESP32 则比较灵活使用经典蓝牙时可以启用 SPPAndroid 侧使用BluetoothSocket连接走类似串口的方式传数据。使用 BLE 时需要自定义 GATT Service把文件内容切片写入 Characteristic同时设计对方确认机制。开发中经常遇到“手机扫描不到 HC-05”的问题常见原因是手机 App 只扫描 BLE 设备而 HC-05 是经典蓝牙扫描时使用的 API 不同。Android 中扫描经典蓝牙使用startDiscovery()扫描 BLE 使用startScan()两者不能混为一谈。4. 开发者视角用代码实现一次蓝牙文件传输4.1 先明确这次要实现的目标和边界下面给出的示例适合学习环境目标是Android 手机通过经典蓝牙连接另一台支持 SPP 的设备。客户端读取本地文件把字节流写入BluetoothSocket。服务端接收字节流并写入本地文件。这个示例不处理断点续传、多文件队列、加密传输和 UI 状态机。真要用于生产还要补充线程管理、超时、异常分类和文件覆盖策略。4.2 Android 经典蓝牙 Socket 传输示例先看服务端核心代码它负责监听来自客户端的文件字节流。为了简化这里直接在后台线程中监听并使用一个固定的 UUID。UUID 是蓝牙服务发现的关键客户端必须使用和服务端相同的 UUID 才能找到对应服务。经典蓝牙 SPP 的常见 UUID 是00001101-0000-1000-8000-00805F9B34FB但不同设备实现不一样自己做双端程序时可以自定义一个 UUID。服务端监听示例private BluetoothAdapter bluetoothAdapter BluetoothAdapter.getDefaultAdapter(); private AcceptThread acceptThread; public void startServer() { BluetoothServerSocket serverSocket bluetoothAdapter.listenUsingRfcommWithServiceRecord(FileTransfer, UUID_SPP); acceptThread new AcceptThread(serverSocket); acceptThread.start(); } private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; public AcceptThread(BluetoothServerSocket socket) { mmServerSocket socket; } Override public void run() { BluetoothSocket socket null; while (true) { try { socket mmServerSocket.accept(); if (socket ! null) { receiveFileFromSocket(socket); break; } } catch (IOException e) { e.printStackTrace(); break; } } } } private void receiveFileFromSocket(BluetoothSocket socket) { FileOutputStream fos null; try { InputStream inputStream socket.getInputStream(); File file new File(getExternalFilesDir(null), received.bin); fos new FileOutputStream(file); byte[] buffer new byte[4096]; int bytesRead; while ((bytesRead inputStream.read(buffer)) ! -1) { fos.write(buffer, 0, bytesRead); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fos ! null) { fos.close(); } socket.close(); } catch (IOException e) { e.printStackTrace(); } } }客户端发送核心代码private void sendFileViaBluetooth(String filePath, BluetoothDevice device) { try { BluetoothSocket socket device.createRfcommSocketToServiceRecord(UUID_SPP); socket.connect(); BufferedInputStream bis new BufferedInputStream(new FileInputStream(filePath)); OutputStream outputStream socket.getOutputStream(); byte[] buffer new byte[4096]; int bytesRead; while ((bytesRead bis.read(buffer)) ! -1) { outputStream.write(buffer, 0, bytesRead); } bis.close(); outputStream.flush(); socket.close(); } catch (IOException e) { e.printStackTrace(); } }这段代码有两个关键点createRfcommSocketToServiceRecord(UUID_SPP)会自动把 UUID 映射到一个可用的 RFCOMM 通道不要在两侧硬编码通道号因为通道是动态分配的。文件流结束后要调用socket.close()否则对端read()会一直阻塞以为数据还没传完。常见坑是发起连接前没有先cancelDiscovery()。如果设备正在执行蓝牙扫描连接可能会失败或耗时异常建议连接前调用bluetoothAdapter.cancelDiscovery()。4.3 Python 端使用 PyBluez 读取文件在 Python 侧接收 Android 客户端传来的文件可以借助 PyBluez。PyBluez 在 Windows 和 Linux 上的支持情况不同Linux 下依赖 BlueZ 协议栈Windows 下依赖系统蓝牙栈安装前需要先确认目标环境。以下示例用于说明大致结构落地时要根据本机蓝牙地址和系统环境调整。import bluetooth uuid 00001101-0000-1000-8000-00805F9B34FB server_sock bluetooth.BluetoothSocket(bluetooth.RFCOMM) server_sock.bind((, bluetooth.PORT_ANY)) server_sock.listen(1) port server_sock.getsockname()[1] bluetooth.advertise_service(server_sock, FileTransfer, service_iduuid, service_classes[uuid, bluetooth.SERIAL_PORT_CLASS], profiles[bluetooth.SERIAL_PORT_PROFILE]) print(listening on port, port) client_sock, client_info server_sock.accept() print(accepted from, client_info) with open(received_from_android.bin, wb) as f: while True: data client_sock.recv(4096) if not data: break f.write(data) client_sock.close() server_sock.close()运行这个脚本前要确保机器蓝牙已开启并且有权限监听端口。脚本中advertise_service的作用是广播一个 RFCOMM 服务让 Android 端的createRfcommSocketToServiceRecord能发现它。4.4 BLE 传文件不是不行但要自己处理分包与确认BLE 传文件和经典蓝牙最大的区别在于经典蓝牙的 RFCOMM 天然把链路包装成类似 TCP 的流你只管读写BLE 是面向 GATT 的请求/响应模型一次写入一个 Characteristic 能承载的数据量有限默认 MTU 只有 23 字节减去 ATT 头后有效载荷更小。因此在 BLE 上传输一个文件至少要设计这几层逻辑协商 MTU连接后通过requestMtu或底层协商把 MTU 调大比如 185 或 512减少分包数量。自定义 GATT Service 和 Characteristic一个 Characteristic 用于数据写入一个用于对端 ACK。分包把文件切成多个 chunk每个 chunk 带上序号。重传对端收到后回 ACK没回就超时重发。示例思路如下private fun sendChunks(characteristic: BluetoothGattCharacteristic, fileBytes: ByteArray, chunkSize: Int) { var offset 0 var seq 0 while (offset fileBytes.size) { val end minOf(offset chunkSize, fileBytes.size) val chunk fileBytes.copyOfRange(offset, end) val packet ByteArray(chunk.size 1) packet[0] seq.toByte() System.arraycopy(chunk, 0, packet, 1, chunk.size) characteristic.value packet // 这里需要等待对端 ACK 后再发送下一包否则会丢包 offset end seq } }实际工程中不能只做“发完就结束”因为 GATT 写入可能失败对端处理速度也可能跟不上。需要把发送窗口、ACK 超时、报文序号和文件头字节数都纳入设计。5. 关键参数RFCOMM、UUID 与 MTU 到底影响什么5.1 UUID 的作用与常见取值UUID 在蓝牙服务发现里相当于“服务名称”。客户端不知道服务端在哪个 RFCOMM 通道上监听必须通过 UUID 向对端查询对端协议栈返回一个可用通道客户端再用这个通道建立连接。常见 UUID 如下服务类型UUID说明SPP00001101-0000-1000-8000-00805F9B34FB串口仿真很多蓝牙模块使用OPP00001105-0000-1000-8000-00805F9B34FB对象推送服务FTP00001106-0000-1000-8000-00805F9B34FB文件传输服务A2DP Source0000110A-0000-1000-8000-00805F9B34FB音频流HID00001124-0000-1000-8000-00805F9B34FB人机交互设备在两台手机之间传文件时不需要开发者自己指定 UUID系统 OPP 服务会处理。但在自研 App 连接模块时务必保证两端 UUID 一致。最典型的坑是服务端广播了自定义 UUID客户端却使用 SPP 通用 UUID导致服务发现超时。5.2 RFCOMM 通道是自动映射不要硬写通道号RFCOMM 通道号在服务端绑定端口时可以指定但协议栈可能动态调整。客户端如果硬编码一个已知的通道号在当前会话里可能能用下次重启后对端通道变了就会失败。正确方式是通过createRfcommSocketToServiceRecord(UUID)或Service Discovery ProtocolSDP查询获得真实通道。自己写示例程序时不要图省事直接传一个固定 int 通道号。5.3 BLE 的 MTU不是越大越好MTU 在 BLE 中指的是链路层单包最大传输单元。它对 BLE 传文件影响很大参数值范围影响默认 MTU23 字节单个 ATT 包有效载荷约 20 字节传大文件分包极多吞吐很低协商后的 MTU185、247、512 等分包数量减少单次写入更大吞吐提升连接间隔7.5ms 到 4s间隔越短单位时间可传包数越多但更耗电写入超时由系统决定超时太短会误判写入失败太长会影响重传效率注意MTU 不是“设置得越大越好”。如果对端设备内存小或协议栈实现不完整超过其能力的 MTU 会导致写入失败。实践中要先通过 API 查询协商结果再根据结果决定每包大小而不是盲目设置成 512。6. 常见问题排查从“搜不到设备”到“传一半断开”6.1 搜不到身边设备现象可能原因检查方式处理建议手机扫描不到另一台手机对端没有开启“可被发现”让对方停留在蓝牙设置页重新开启可被发现模式扫描不到蓝牙耳机/音箱设备已连接其他主机断开其他连接从旧设备取消配对后重试扫描不到 HC-05 模块手机 App 只扫 BLEHC-05 是经典蓝牙确认 App 扫描 API 类型使用支持 SPP 的调试工具Android 12 以上扫描不到设备缺少运行时权限检查日志中的 SecurityException申请BLUETOOTH_SCAN和位置权限Windows 搜不到手机蓝牙驱动或服务异常打开设备管理器、检查服务重新安装驱动并启动 Bluetooth Support Service6.2 配对不上或 PIN 码错误配对失败通常表现为弹窗要求输入 PIN但输入正确后仍提示失败或者设备一直显示“配对失败”。排错顺序删除旧配对记录让两侧重新搜索并配对。确认 PIN 码是否匹配。部分设备是“0000”或“1234”但有些自定义蓝牙模块会使用固定 PIN 或动态 PIN。检查设备类型。某些只支持输入但不需要确认的设备与需要确认输入的设备之间会出现 PIN 流程不一致。检查是否同时被其他服务占用比如耳机已连一台手机时无法再配对第二台。6.3 传输速度慢、传输中断、文件损坏速度慢和中断在蓝牙传文件中非常常见尤其是中等以上文件。可能原因包括文件过大OPP 会话在系统层面没有断点续传能力。收发双方距离过远或中间有遮挡。接收方系统在锁屏或后台时暂停了蓝牙接收进程。发送方在传输过程中被电话、通知或系统节能策略打断。文件本身包含特殊字符路径导致接收端无法正常落盘。检查方式把两台设备放在同一桌面排除距离和遮挡。保持接收方 App 在前台或系统设置页。查看接收方日志中是否出现 Bluetooth 相关异常Android 可以使用adb logcat | grep -i bluetooth抓取。如果文件较小但很快失败先确认文件路径和文件名是否合法。一个常见判断小文件正常大文件总失败优先怀疑 OPP 会话超时或接收端存储路径异常而不是底层蓝牙信号问题。6.4 系统蓝牙开关消失或驱动异常Windows 和部分 Linux 桌面环境中“蓝牙开关消失”是一个高频问题。排查步骤打开设备管理器查看是否有带感叹号的蓝牙设备。检查 BIOS 设置中蓝牙是否被禁用台式机尤其需要注意。打开服务管理器确认Bluetooth Support Service处于“正在运行”状态。如果使用的 USB 蓝牙适配器重新插拔一次并尝试更换 USB 口。使用驱动软件前先到 OEM 官网下载对应驱动不要随意安装第三方驱动。6.5 可复用的蓝牙排查顺序清单遇到蓝牙传文件或连不上问题按以下顺序排查可以减少无效操作蓝牙开关是否真的开启。目标设备是否可见。两台设备距离是否在几米内。是否已配对配对记录是否过期。权限是否齐全包括位置权限和附近设备权限。驱动、服务、BIOS 是否正常。抓取日志确认是 SDP 失败、RFCOMM 连接失败还是传输中断。换一种文件类型和更小文件排除文件本身问题。7. 最佳实践与更适合实现文件传输的方案选择7.1 学习环境建议先跑通“小文件一条龙”初学者不要一开始就奔着“传 1GB 视频”的目标去。先用一个 1KB 的文本文件在 Android 和电脑之间传一遍确认连接、配对、发送、接收四个环节都正常。再逐步增加文件大小。调试时可以这样做手机端用系统“分享 - 蓝牙”发送小文本文件到电脑。电脑端用 Python 脚本监听 RFCOMM保存接收到的文本内容。确认内容一致后再改成图片或二进制文件。加入文件大小校验比如计算 MD5判断传输是否完整。使用 MD5 校验是一个简单而有效的验证手段。传完文件后发送端和接收端都计算一遍哈希值一致则说明数据没有损坏。7.2 生产环境不要把系统 OPP 当成万能方案只要做产品就不能假设目标用户都会打开系统蓝牙设置自己点击确认接收。生产环境要考虑自动接收策略是否允许对端直接推文件或在用户授权后自动接收。存储路径与文件类型安全不能把接收文件直接覆盖到应用私有目录以外避免路径穿越和恶意文件写入。传输进度OPP 的系统进度条通常由系统界面控制App 层难以完全接管。失败重试需要在应用层增加超时和重试机制。权限合规调用蓝牙权限时要向用户说明用途并处理拒绝授权的场景。经典蓝牙 OPP 更适合“体验型功能”比如小尺寸联系人卡片、链接、短文本。它确实简单但如果要传较大文件体验和生产稳定性都很难达到要求。7.3 大文件传输更优的路径是什么当一个业务场景真正需要稳定传输几十 MB 甚至更大的文件时蓝牙通常不是最优选项。更常见的选择是Wi-Fi Direct / P2P吞吐更高适合同一房间内的设备直连。局域网共享两端在同一网络时使用 HTTP 或自定义 TCP 协议。云中转 / 局域网文件服务先把文件上传到临时服务再推送下载链接。二维码或短链对文件本身不需要二进制实时传输时直接引导用户下载。蓝牙的价值在于“无网络环境下的短距低功耗连接”适合小包数据、指令控制、传感器采集和身份配对不适合做高吞吐文件通道。如果你正在设计一个“蓝牙传文件”功能建议先评估文件大小和时延要求再决定是不是要引入额外的 Wi-Fi P2P 通道做辅助传输。7.4 新手最该记住的三件事第一蓝牙传文件不是单一接口而是一条协议链路。遇到问题先分层定位是权限层、服务发现层、RFCOMM 连接层还是应用层文件逻辑。第二经典蓝牙和 BLE 不能混为一谈。HC-05、老式蓝牙模块走 SPP手机 App 扫描时要用经典蓝牙 APIESP32 或传感器走 BLE扫描时要用 GATT 相关 API。第三生产场景优先考虑可靠传输。如果只是学习系统自带分享和几行 Socket 示例就足够如果是产品必须补充进度、确认、超时、重试、校验和日志或者直接换用 Wi-Fi P2P 等更具吞吐能力的方案。把握住这三条主线再遇到蓝牙文件传输需求就不会只停留在“发不出去”的表面猜测上。