一、项目概述

BluetoothSpecialSPP 是一款基于 Android 原生蓝牙 SPP(Serial Port Profile)协议的通信监控应用。它通过经典的 RFCOMM 通道与蓝牙串口设备建立连接,实现数据的双向收发。

本项目最初作为华东交通大学电子信息工程专业的毕业设计项目开发,因此具备两重身份:

项目规格

参数
开发平台Android Studio + Java
目标平台Android 7.0 (API 24) 及以上
编译环境Gradle 8.11.1 + AGP 8.9.0
蓝牙协议经典蓝牙 SPP(UUID: 00001101-0000-1000-8000-00805f9b34fb)
UI 框架Android XML Layout + Material Design
源码包名com.jay.bluetooth
APK 名称BlueToothSpecialSPP-1.4.21.apk
项目作者张杰(华东交通大学 电子信息工程)
• • •

二、技术栈与环境

2.1 开发环境

说明
操作系统Windows / macOS
开发工具Android Studio
编程语言Java 11
构建工具Gradle 8.11.1 + AGP 8.9.0
compileSdk / targetSdk35
minSdk24(Android 7.0)

2.2 核心依赖

库名版本用途
appcompat1.6.1Android 兼容库
material1.10.0Material Design 组件
activity1.8.0Activity API 封装
constraintlayout2.1.4约束布局(主界面核心)

所有依赖通过 Gradle Version Cataloglibs.versions.toml)统一管理,这是官方推荐的最佳实践。

• • •

三、模块详解与代码分析

3.1 MainActivity —— 主界面与蓝牙通信核心

文件:MainActivity.java(约 500 行),负责蓝牙连接管理、数据收发、解析与 UI 更新。

蓝牙连接流程:

private void connectToDevice(String deviceAddress) {
    new Thread(() -> {
        BluetoothDevice device = bluetoothAdapter.getRemoteDevice(deviceAddress);
        BluetoothSocket socket = device.createRfcommSocketToServiceRecord(MY_UUID);
        socket.connect();
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
        startReceiveThread();
        runOnUiThread(() -> updateConnectionStatus(device.getName()));
    }).start();
}Java

数据接收与解析:

private void startReceiveThread() {
    receiveThread = new Thread(() -> {
        byte[] buffer = new byte[1024];
        int bytes;
        while ((bytes = inputStream.read(buffer)) != -1) {
            String data = new String(buffer, 0, bytes, "UTF-8");
            if (data.startsWith(">")) {
                // 终端显示(原始数据)
                tvTerminal.append("\n" + data);
            } else if (data.startsWith("#") && data.length() == 21) {
                // 结构化数据解析
                String temp = data.substring(1, 4);
                String humi = data.substring(5, 8);
                String sound = data.substring(9, 12);
                String level = data.substring(13, 15);
                String dust = data.substring(16, 19);
                String ctrl = data.substring(20, 21);
                // 告警判断
                if (level.equals("I") || Double.parseDouble(dust) > 150) {
                    setBackgroundColor(红色);
                } else {
                    setBackgroundColor(绿色);
                }
            }
        }
    });
    receiveThread.start();
}Java

数据发送:

private void sendBluetoothCommand(String command) {
    try {
        outputStream.write((command + "\r\n").getBytes("UTF-8"));
        outputStream.flush();
    } catch (IOException e) {
        Toast.makeText(this, "发送失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
    }
}Java

3.2 SecondActivity —— 蓝牙设备搜索与选择

文件:SecondActivity.java(约 320 行),负责显示已配对设备和扫描附近设备。

3.3 BtnActivity —— 自定义按钮编辑

文件:BtnActivity.java(约 80 行),提供编辑界面修改按钮名称和发送内容。

• • •

四、数据通信协议

4.1 发送协议

指令含义
on\r\n开启加湿器
off\r\n关闭加湿器
自定义消息用户预设的任意指令

4.2 接收协议 —— 结构化数据帧

# 开头,固定 21 字节:

#  T1 T2 T3 T4  H1 H2 H3 H4  S1 S2 S3 S4  N1 N2 N3  D1 D2 D3 D4  C
│  ───温度───  ───湿度───  ───音量───  ──等级──  ───粉尘───  开关
0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20格式
字段长度位置说明
温度4索引 1-4单位:℃
湿度4索引 5-8单位:%
音量4索引 9-12单位:DB
噪音等级3索引 13-15I / II / III
粉尘浓度4索引 16-19单位:μg/m³
开关状态1索引 20'1'=开,其他=关

4.3 告警逻辑

• • •

五、权限与适配

5.1 权限声明

应用在 AndroidManifest.xml 中声明了 9 项权限:

权限用途
BLUETOOTH_CONNECT连接蓝牙设备(Android 12+ 运行时权限)
BLUETOOTH基础蓝牙操作(兼容旧版本)
BLUETOOTH_ADMIN蓝牙管理(扫描等)
ACCESS_FINE_LOCATION精确定位(蓝牙扫描需要)
BLUETOOTH_SCAN蓝牙扫描(neverForLocation 标志)
INTERNET网络访问(预留)
VIBRATE振动

注意:BLUETOOTH_SCAN 使用了 neverForLocation 权限标志,表明不会将扫描结果用于定位,这是 Android 12+ 的最佳实践。

5.2 分层适配策略

private void checkBluetoothPermissions() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        // Android 12+: 运行时申请 BLUETOOTH_CONNECT + BLUETOOTH_SCAN
        requestPermissions(new String[]{
            BLUETOOTH_CONNECT, BLUETOOTH_SCAN, ACCESS_FINE_LOCATION
        }, REQUEST_BLUETOOTH_PERMISSIONS);
    } else {
        // 旧版本: 仅需 ACCESS_FINE_LOCATION
        requestPermissions(new String[]{
            ACCESS_FINE_LOCATION
        }, REQUEST_LOCATION_PERMISSION);
    }
}Java
• • •

六、常见问题排查

问题解决方案
提示"设备不支持蓝牙"确认手机硬件支持经典蓝牙(非 BLE),虚拟机通常不支持
扫描不到设备检查目标设备是否开启且可发现;确认已授予位置权限;点击刷新重新扫描
连接失败系统设置中先配对;确认设备未与其他设备连接;距离保持在 10 米内
数据显示乱码确认硬件编码格式为 UTF-8,否则修改 new String(buffer, 0, bytes, "编码")
中文发送无反应部分蓝牙串口模块仅支持 ASCII,建议使用英文或数字
红色背景如何恢复红色是告警状态,数据恢复正常后自动变回绿色