前言
最近在学习《移动软件开发》课程时,我接到了一个任务:开发一个安卓App,实现两台手机通过蓝牙互传图片。听起来很简单?我一开始也这么认为。然而,随着安卓系统的飞速迭代,曾经简单的几行代码,如今需要面对权限申请、后台限制、分区存储、版本适配等一系列“现代化”的挑战。

这篇博客,既是我的学习成果总结,也是一份详尽的“踩坑避坑”指南。希望能帮助正在或将要探索安卓蓝牙开发的你,少走一些弯路。

一、 最终成果展示

  • 发送方:选择图片后,连接设备,显示发送成功。
  • 接收方:接收成功后,提示文件保存路径,并在系统相册中可见。

二、 核心原理与项目搭建

1. 蓝牙通信原理

安卓蓝牙通信是典型的 客户端-服务器 (C/S) 模型:

  • 服务端(Server): 创建一个 BluetoothServerSocket,在一个唯一的 UUID 上进行监听 (listen),然后调用 accept() 进入阻塞状态,等待客户端连接。
  • 客户端(Client): 通过服务端的MAC地址和同一个 UUID 创建 BluetoothSocket,然后调用 connect() 发起连接。
  • 数据交换: 连接成功后,双方通过 InputStream 和 OutputStream 进行数据的读写,完成文件传输。

2. 项目基础搭建

UI布局 (activity_main.xml): 界面很简单,包含几个核心功能的按钮和一个用于显示状态的 TextView。

三、 Android权限

这是整个项目中最具挑战性的部分。如果你的App还在使用旧的权限申请方式,那么在Android 12以上的设备上几乎寸步难行。

1. AndroidMainfest.xml 中的权限申请

我们需要一个能兼容新旧所有版本的权限声明清单。关键在于使用 maxSdkVersion 属性。

2. 运行时权限请求

告别繁琐的 onRequestPermissionsResult ,使用ActivityResultLauncher可以让权限处理逻辑更清晰、更解耦。我们需要为“请求蓝牙权限”、“请求存储权限”和“打开文件选择器”分别创建Launcher。

四、 核心代码实现

这里我们直接贴出经过所有调试和优化后的最终核心方法。

发送文件 (sendFile)

接收文件 (receiveFile) – 适配分区存储与异常处理

这是整个项目的精华所在,它解决了分区存储和我们后面会提到的“假失败”问题。

五、 踩坑实录:我的调试之旅

天坑一:小米(MIUI)的“权限墙”

  • 现象:应用在其他手机上正常,在小米上安装失败,提示 INSTALL_FAILED_USER_RESTRICTED,或者选择文件时提示“没有权限”。
  • 原因:MIUI拥有独特的、更严格的权限管理机制。
  • 解法:必须手动进入 手机管家 -> 应用管理 -> 你的App -> 权限管理,要打开“文件和媒体”权限和一个隐藏的“后台弹出界面”权限。否则,系统级的权限请求对话框根本无法弹出。

天坑二:文件接收的“假失败”

  • 现象:这是最诡异的问题。发送方显示成功,接收方的相册里也确实出现了图片,但我的App却提示“接收文件失败”。
  • 探究:通过查看Logcat,我发现每当接收失败时,总会捕获到一个特定的异常:java.io.IOException: bt socket closed, read return: -1。
  • 顿悟:read return: -1 在Java I/O中本是数据流正常结束的标志。但安卓蓝牙的底层实现,在连接被对方迅速关闭时,会将这个“正常结束”信号包装成一个IOException抛出!所以,我的程序成功接收了所有数据,却在最后一步将“成功”误判为了“失败”。
  • 解法:如上面的receiveFile代码所示,在catch块里对这个特定的异常信息进行判断。如果是它,就执行成功的逻辑;如果是其他IOException,才认为是真正的失败。

[完整的项目代码已上传至GitHub:collapsar-git/BluetoothFiletransfer: 安卓手机间通过蓝牙传输文件]

Categories:

Tags:

No responses yet

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注