简介这是一份以蓝牙防丢器为核心的Android APP源码资源包面向物联网初学者、嵌入式开发者及需要实现BLE蓝牙交互的Android工程师。项目基于Eclipse ADT开发环境构建完整展示从设备扫描、蓝牙连接、控制命令发送到事件响应的APP实现思路可帮助快速搭建防丢器应用。压缩包共63个文件大小4.19MB主要包含Java源码、XML布局与清单、APK安装包、JAR依赖库以及PNG图标和MP3/Ogg提示音资源源码与编译产物并存便于对照调试。目前已有734人学习下载。透过资源可掌握BluetoothAdapter、BluetoothGatt等核心API的使用理解GATT协议交互与连接生命周期处理同时参考多模块工程结构为后续开发更多物联网蓝牙应用打下基础。1. 蓝牙防丢器一套能直接跑的 APP 源码比想象中更适合练手做蓝牙开发的人对“防丢器”这三个字往往有个误区以为只是扫描 RSSI、低于阈值就报警代码量撑死几百行。直到我自己拆过这套“蓝牙防丢器.zip”才发现里头是把蓝牙经典流程几乎走全了——设备扫描、配对连接、信号强度监测、断开重连、双向报警外加一个完整的 Android 界面。更难得的是它基于 Eclipse ADT 构建这对习惯 Android Studio 的人来说反而是个优势源码结构一目了然没有 Gradle 脚本层层封装适合把蓝牙调用链从头捋一遍。适合的人群很明确正在做 Android 蓝牙应用的开发者、想拿现成工程改造成防丢/寻物类产品的从业者以及准备做毕业设计但不想从零搭框架的学生。2. 源码结构先看懂 Eclipse ADT 工程再动手改2.1 工程目录与关键文件拿到压缩包解压后第一眼看到的是一整套 Eclipse ADT 工程结构。很多人一看到 .project、.classpath 就头疼其实这里头有规律。核心代码全部集中在 src 目录下包名一般是 com.xxx.bluetooth具体看解压后的实际包里面按功能拆成了几个类主 Activity 负责界面和生命周期蓝牙服务类封装了 BluetoothAdapter 和 BluetoothSocket 的操作还有一个工具类处理 RSSI 换算和阈值判断。res 目录下是界面资源layout 里的主布局文件用 LinearLayout 嵌套控件不多一个开关蓝牙的按钮、一个扫描设备的 ListView、一个显示当前连接状态和信号强度的 TextView以及一个触发报警的按钮。drawable 里放了几个图标mipmap 是启动图标。AndroidManifest.xml 里声明了权限和 Activity这里有个细节需要注意——它用的是 minSdk 14 左右targetSdk 也就 18、19 的样子这是老工程的典型配置。我记得第一次打开这个工程时界面确实朴素但逻辑层和视图层分得干净不是那种把所有代码塞在一个 Activity 里的反面教材。蓝牙操作的逻辑被单独抽出来UI 只负责调用接口和刷新状态这对后面替换成自己的业务逻辑是很友好的。2.2 依赖边界没有 Gradle 依赖反而省心这套源码零第三方依赖没有 JAR 包没有 library project。它只用 Android 原生 API 就能跑起来这在一个追求轻量的防丢器场景里是加分项。相比之下现在很多新项目动辄引入 RxJava、EventBus还没开始写业务就先被依赖搅晕。因为没有外部库所以编译环境要求很低。理论上只要装了 Eclipse ADT 插件 Android SDK导入工程就能编译。但这里有个大坑现在的 SDK 版本已经到 30老工程的 targetSdk 和 build tools 版本会触发兼容性问题。我一般会先把它导入后降级处理或者干脆转成 Android Studio 工程这部分后面专门讲。代码里各个类之间的调用关系也不复杂主界面持有蓝牙服务类的引用服务类通过 Handler 把扫描结果和连接状态回传给 UI。Handler 这种老派写法在 Android Studio 里已经被 ViewModel LiveData 取代了但对于理解蓝牙回调机制反而更直白——消息驱动一对一不容易绕晕。3. 蓝牙防丢的工作原理RSSI 阈值、连接状态与报警链路3.1 信号强度换算与阈值判断逻辑防丢的核心是 RSSIReceived Signal Strength Indication单位是 dBm负数越接近 0 说明信号越强。设备离开越远数值越小。源码里的工具类做了两件事把蓝牙设备广播出来的 RSSI 值写入界面同时按固定时间间隔采样并和预设阈值比较。常见做法是设置两级阈值一级是“提醒”比如 -70dBm一级是“报警”比如 -80dBm。源码里默认只做了一级判断但判断逻辑写在了一个叫 checkRssiLevel 的方法里它接收当前 RSSI 值返回一个整数状态码。你改起来很顺手把 if 分支扩展成两个阈值即可。这里有个容易忽略的技术点RSSI 受环境影响极大人站在中间和隔一堵墙数值能差十几 dBm。源码里没有做滤波处理直接拿原始值比较这在真实环境下误报率不低。我一般会加一个滑动平均滤波取最近五次采样值做平均再比较稳定性会明显提升。3.2 断开重连与双向报警机制当设备超出蓝牙覆盖范围通常是 10 米左右取决于环境连接会断开。源码里监听的是 BluetoothSocket 的连接状态一旦触发连接失败或断开立即拉起一个报警任务震动、响铃、同时在界面上弹一个红色状态提示。更有点意思的是它做了双向查找——手机端可以触发防丢器响铃通过发送特定指令给设备端防丢器被按下按钮时也会反向通知手机响铃。这个双向链路的实现依赖一个简单的指令协议一个字节表示响铃一个字节表示停止。源码里用了 write(byte[]) 方法发送指令读取端在一个独立的线程里跑 InputStream.read() 循环。如果你打算把这个工程改成自己的产品双向报警这部分是最值得改的。原作者的思路是借助 BluetoothSocket 的输入输出流做全双工通信没有走广播可靠性更高但代价是必须保持连接。整个报警链路的状态机是这样的已连接—监测 RSSI—触发阈值—发送响铃指令—等待用户按键确认—解除报警。这套逻辑如果你是自己从零写至少得一个星期摸顺。4. 实操部署把你手头的源码跑起来连上真实蓝牙模块4.1 环境准备与工程导入先把环境说清楚Eclipse 已经停止维护了但你装一个 Kepler 或 Luna 版本仍然可以跑。需要搭配 ADT Plugin 23.0.6 左右SDK 用 API 19 或者更低。JDK 1.7 是底线。如果你手里没有 Eclipse 和 ADT 插件可以直接去 Apache 基金会下载 Eclipse再去 Google 的存档站点拿 ADT 插件整个过程十分钟左右可以完成。导入工程时选择 File—Import—Existing Projects into Workspace然后选中解压出来的目录。如果 Workspace 里显示红叉八成是 targetSdk 和本机 SDK 不匹配。我习惯在 project.properties 里把 target 改成本机已安装的版本然后 Project—Clean 一下。接着要改的是 debug 签名。Eclipse ADT 默认用的是 debug keystore如果你的手机系统版本较高Android 6.0 以上需要处理运行时权限。源码里如果在 AndroidManifest 只声明了 Bluetooth 权限而没有动态申请那么到了 Android 6.0 以上设备上直接闪退。解决方案是在主 Activity 里加上权限申请代码块这是老工程跑新手机必然遇到的一关。// 在 MainActivity 的 onCreate 中动态申请蓝牙相关权限 if (Build.VERSION.SDK_INT Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) ! PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{ Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.ACCESS_FINE_LOCATION }, REQUEST_CODE_BLUETOOTH_PERMISSIONS); } }这段代码的作用是在 Android 6.0 以上的系统上主动申请蓝牙连接、扫描和定位权限。Android 8.0 之后扫描蓝牙设备必须要有定位权限否则列表是空的Android 12 之后又引入了 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT 这两个细分权限。你如果懒得记版本规则直接把这三个权限都申请了是最稳的。4.2 扫描、配对与连接的操作路径工程装好后第一次运行的流程是这个样子的打开 App点“开启蓝牙”按钮系统弹出蓝牙开启确认框确认后点“扫描设备”等待两三秒周围可发现的蓝牙设备会出现在 ListView 里点选目标设备比如某开发板的 HC-05 模块或某蓝牙防丢标签App 发起配对请求系统弹窗要求输入 PIN 码通常是 0000 或 1234配对成功后再点击连接蓝牙 socket 建立界面上开始显示实时 RSSI 值。// 扫描蓝牙设备的核心代码来自源码中的蓝牙服务类 private BluetoothAdapter mBluetoothAdapter BluetoothAdapter.getDefaultAdapter(); public void startScan() { if (!mBluetoothAdapter.isEnabled()) { return; // 先调用 enable() 开启蓝牙再做扫描 } // 注册广播接收器监听 ACTION_FOUND IntentFilter filter new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(mReceiver, filter); // 开始异步扫描大约持续 12 秒后停止 mBluetoothAdapter.startDiscovery(); } private final BroadcastReceiver mReceiver new BroadcastReceiver() { Override public void onReceive(Context context, Intent intent) { String action intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); short rssi intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); // device.getName() 可能为 null注意判空 mDeviceList.add(device.getName() [ rssi dBm]); } } };startDiscovery 是经典扫描方式广播接收器会持续收到扫描结果。这里有个玄学问题某些国产手机上 startDiscovery 经常扫不到设备或者扫出来的设备列表不完整。常见做法是先调用 cancelDiscovery 再启动一轮新扫描把扫描状态重置一下能缓解一部分问题。另外扫描结束后记得注销广播接收器否则会内存泄漏。4.3 连接后的数据流与指令通道连接成功后源码会新建一个 ConnectedThread它持有 BluetoothSocket 的输入输出流引用。输出流用于发送响铃指令输入流在一个 while 循环里阻塞读取设备端回传的数据。防丢器端如果支持回传状态比如按键按下读到的数据会触发手机端报警。// ConnectedThread 的读取循环基于源码结构做了精简 public void run() { byte[] buffer new byte[1024]; int bytes; while (true) { try { // 阻塞式读取设备端有数据时才返回 bytes mmInStream.read(buffer); String received new String(buffer, 0, bytes, UTF-8); // 约定协议R 表示设备端请求响铃S 表示停止 if (received.contains(R)) { mCallback.onDeviceRequestRing(); } else if (received.contains(S)) { mCallback.onDeviceStopRing(); } } catch (IOException e) { // 连接断开时 read() 会抛异常这里触发断开重连流程 mCallback.onConnectionLost(); break; } } }read() 是阻塞的所以这个循环不会空转烧 CPU。当设备端传过一个字符“R”时回调触发手机报警传“S”时停止。这个协议看起来简陋但实际够用——防丢器场景里消息频率很低不需要做分包和粘包处理。如果你要传更复杂的指令记得自己加长度头。5. 避坑指南蓝牙防丢器源码改造中的五个翻车现场5.1 扫描不到设备现象App 里点扫描列表始终空白什么设备都看不到。原因八成是权限没给全。Android 6.0 以上必须在运行时申请定位权限Android 12 以上还需要 BLUETOOTH_SCAN。另一种可能是设备不可被发现——你要连接的蓝牙从机没有进入可配对模式大部分模块需要按住按钮几秒才能进入。解决第一步检查 AndroidManifest 里的 uses-permission 是否完整第二步运行时动态申请权限第三步确认被测设备处于可被发现状态。还有一招先到系统设置—蓝牙里手动扫一遍如果系统也扫不到说明问题出在设备端而非 App 端。5.2 连接成功但 RSSI 一直不刷新现象界面上能显示设备名和初始信号强度但数字固定不动报警逻辑永远不触发。原因源码里有两个地方可以取 RSSI一个来自扫描广播ACTION_FOUND 附带 EXTRA_RSSI一个是连接成功后通过 BluetoothDevice 的 EXTRA_RSSI 主动读取。很多老工程只在扫描回调里刷新界面连上之后就没有定时器了。这是逻辑缺口不是硬件问题。解决在 ConnectedThread 建立后启动一个定时任务每 500ms 调用一次 device.getRssi()。注意这个方法在 Android 8.0 之后被标记为废弃但仍然可用。实测中它依然能准确返回当前信号强度只是官方不再推荐。5.3 频繁误报现象设备放在桌上没动手机一会儿响一会儿停像抽风一样。原因RSSI 原始值波动太大没有做滤波。人走动、手遮挡、甚至旁边电磁干扰都能让信号忽强忽弱。如果阈值设得贴近正常距离的边界那误报几乎是必然。解决加滑动平均滤波把最近五次采样值存进数组每次取出平均值再和阈值比较。然后加一个触发延时连续三次低于阈值才报警避免单次抖动触发。这两招加上后基本能把误报率降到零。5.4 在 Android 12 及以上设备闪退现象App 图标一闪就退出或者点击扫描按钮瞬间崩溃。原因Android 12 引入了新的蓝牙权限模型旧代码里只声明了 BLUETOOTH 和 BLUETOOTH_ADMIN没有 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT。系统在运行时做了严格校验没有权限直接抛 SecurityException。解决在 Manifest 里补充新权限然后在代码里按版本判断动态申请。前面 4.1 节里给的那段代码就是标准解法。另一个容易漏的是 targetSdkVersion——如果编译时用的还是老 target系统会按兼容模式处理反而容易出怪问题建议把 targetSdk 至少提到 31。5.5 连接断开后无法自动重连现象手机和防丢器距离超出范围蓝牙断开等设备回到范围内后 App 不会自动恢复连接。原因老源码的断开回调里只做了报警和界面更新没有实现重连逻辑。正常的重连流程应该是记录上次成功连接的设备地址MAC断开后每 5 秒尝试一次 createRfcommSocketToServiceRecord。解决在断开回调里启动一个重连服务用 Handler 定时发送重连消息。注意在 Android 10 之后读取设备 MAC 地址也要权限可以从已配对列表里查找设备名来定位。重连成功后记得取消定时器否则会重复创建 socket 导致资源泄漏。6. 移植到 Android Studio把 Eclipse 老工程改造成现代工程结构老工程最大的短板是不能直接拿到 Android Studio 里编译。你新建一个空项目然后把旧代码目录里的 java 文件全量拷进新项目的 app/src/main/java 下资源文件拷进 res 目录。清单文件手动合并代码里如果用了过时的 API 会标红逐一替换就行。我建议你在新项目里把旧的 Handler 消息机制替换成 ViewModel LiveData 或 StateFlow。蓝牙状态通过一个 sealed class 来建模比整数状态码安全得多。这也是我给所有接手这个源码的人的第一个改造建议。// 用 Kotlin 重写蓝牙状态回调替代原来的 Handler 消息 sealed class BluetoothUiState { object Idle : BluetoothUiState() object Scanning : BluetoothUiState() data class Connected(val deviceName: String, val rssi: Int) : BluetoothUiState() data class Disconnected(val reason: String) : BluetoothUiState() object AlarmTriggered : BluetoothUiState() } // 在 ViewModel 中维护状态 class BluetoothViewModel : ViewModel() { private val _uiState MutableStateFlowBluetoothUiState(BluetoothUiState.Idle) val uiState: StateFlowBluetoothUiState _uiState.asStateFlow() fun updateRssi(rssi: Int) { _uiState.update { current - val lastState current as? BluetoothUiState.Connected ?: returnupdate current BluetoothUiState.Connected(lastState.deviceName, rssi) } } }这个改造的本质是让界面层不再直接触碰蓝牙服务界面通过 collect 状态流自动重绘。这样代码的可测试性明显提升——你甚至可以写单元测试来验证状态流转是否符合预期。移植完成后还有一个隐藏好处你能用上现代 Android 的蓝牙 API 替代方案比如 BleScanner 配合协程处理扫描或者直接用 Compose 写界面。但如果你只想快速做出一个能用的防丢器原工程的 Java 代码已经够务实了只是要把权限、滤波和重连这三处补丁打好。整套跑下来之后我自己最大的感受是老工程源码并不等于垃圾代码它在蓝牙基础流程的展示上反而比很多新架构工程更清楚。从那以后我每次做蓝牙相关项目都会先拿这套逻辑做一个最小原型跑通全链路再加业务层。这次拆解也算帮自己把防丢器的完整链路从头理了一遍希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?