当前位置: 首页 > news >正文

安卓开发---BLE通信

概念:为了让支持 BLE 的设备能够在彼此之间传输数据,它们必须先形成通信通道。若要使用 Bluetooth LE API,需要在清单文件中声明多项权限。应用获得使用蓝牙的权限后,需要访问 BluetoothAdapter 并确定设备上是否支持蓝牙。如果支持蓝牙,设备将扫描附近的BLE设备。找到设备后,通过连接到BLE设备上的GATT服务器来发现 BLE 设备的功能。建立连接后,可以根据可用服务和特性与已连接的设备传输数据。

关键术语:

1.BLE (Bluetooth Low Energy) - 蓝牙低功耗

蓝牙4.0引入的低功耗技术,专为间歇性数据传输设计,比经典蓝牙更省电。适用于耳机、手环等设备。

2.GATT (Generic Attribute Profile) - 通用属性协议

BLE通信的核心协议,定义了数据组织和交换方式。采用客户端-服务器架构,设备作为服务器提供数据,手机作为客户端访问数据。

3.UUID (Universally Unique Identifier) - 通用唯一标识符

128位唯一标识符,用于识别蓝牙服务、特征和描述符。

4.Service - 服务

完成特定功能的数据集合,包含多个特征(Characteristics)。例如电池服务、设备信息服务等。

5.Characteristic - 特征

服务中的数据点,包含一个值和多个描述符。具有读、写、通知等属性。

txUUID: 发送特征(手机→设备)

rxUUID: 接收特征(设备→手机)

6.Descriptor - 描述符

特征的附加信息,最常用的是CCCD(客户端特征配置描述符),用于启用或禁用通知。

7.CCCD (Client Characteristic Configuration Descriptor) - 客户端特征配置描述符

特殊的描述符,用于配置设备如何向客户端发送数据:

ENABLE_NOTIFICATION_VALUE: 发送通知(无确认)

ENABLE_INDICATION_VALUE: 发送指示(需要确认)

8.MTU (Maximum Transmission Unit) - 最大传输单元

单次数据传输的最大字节数。默认23字节,请求更大MTU可以提高传输效率

9.ScanCallback - 扫描回调

接收BLE设备扫描结果的接口,当发现设备时系统会自动调用。

10.BluetoothGattCallback - GATT回调

处理所有GATT操作结果的接口,包括连接状态改变、服务发现、数据接收等。

11.序列号管理 (seq)

用于数据包排序和去重,确保数据顺序正确,防止重复处理。

通信过程的详细讲解

1.权限:要在应用中使用蓝牙功能,必须声明 BLUETOOTH 蓝牙相关权限。需要权限才能执行任何蓝牙通信,例如请求连接、接受连接和传输数据等。同时,还需要位置权限。因为蓝牙 LE 信标通常与位置相关联。

    <uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.BLUETOOTH_SCAN" /><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

2.定义UUID,根据自己UUID进行更改,服务UUID是为了让设备发现该服务,特征UUID是为了数据读写操作,描述符UUID是为了通知配置。

//UUIDprivate final static UUID serviceUUID = UUID.fromString("0000fdb1-0000-1000-8000-00805f9b34fb");//服务特征UUIDprivate final static UUID txUUID = UUID.fromString("0000ff11-0000-1000-8000-00805f9b34fb");//发送特征UUIDprivate final static UUID rxUUID = UUID.fromString("0000ff11-0000-1000-8000-00805f9b34fb");//接收特征UUIDprivate static final UUID CCCD_UUID = UUID.fromString("00002901-0000-1000-8000-00805f9b34fb");//客户端特征配置描述符

3.设置列表适配器,让扫描到的蓝牙名称放到列表上

deviceAdapter = new ArrayAdapter<>(this,android.R.layout.simple_list_item_1,deviceNames);listView.setAdapter(deviceAdapter);

4.定义扫描按钮的点击事件,先进行权限检查,当权限检查通过的时候,判断按钮按下为开始扫描或者停止扫描

scan.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {requestPermission();if(!isScanning){startScan();}else{stopScan();;}}

5.定义列表的点击事件,parent指被点击的ListView,view指被点击的具体视图项position指被点击项在列表中的位置(索引)id指被点击项的行ID;先根据点击的位置(position)从deviceNames列表中获取对应的设备名称,再调用getKeyByDeviceName(deviceName)方法,通过设备名称获取对应的Key,最后则调用connectToDevice(device)方法连接该设备

//列表点击事件listView.setOnItemClickListener((parent, view, position, id) -> {String deviceName = deviceNames.get(position);BluetoothDevice device = deviceMap.get(getKeyByDeviceName(deviceName));  // 获取 BluetoothDevice 对象if (device != null){connectToDevice(device);  // 连接设备}});

6.检查并请求权限,定义一个是否获取权限的判断方法isPermission,和请求权限方法requestPermission.

private boolean isPermission(String permission) {return checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;}
private boolean requestPermission() {if (!isPermission(Manifest.permission.ACCESS_COARSE_LOCATION)|| !isPermission(Manifest.permission.BLUETOOTH_SCAN)|| !isPermission(Manifest.permission.BLUETOOTH_CONNECT)) {requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.BLUETOOTH_SCAN,Manifest.permission.BLUETOOTH_CONNECT}, REQUEST_PERMISSION);return false;}return true;}

7.开始扫描,当相关权限获取完成就可以开始扫描,先通过BluetoothManager获取蓝牙适配器,再判断蓝牙是否开启和是否可用。清空之前的扫描结果给新扫描的结果腾地方,通过蓝牙适配器获取蓝牙扫描器的扫描方法开始扫描。

private void startScan() {if (!requestPermission()) return;scan.setText("停止扫描");//获取蓝牙适配器BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);mBluetoothAdapter = bluetoothManager.getAdapter();if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {Toast.makeText(this, "蓝牙不可用或未开启", Toast.LENGTH_SHORT).show();finish();}// 清空扫描结果deviceNames.clear();deviceMap.clear();deviceAdapter.notifyDataSetChanged();//开始扫描设备mBluetoothAdapter.getBluetoothLeScanner().startScan(leScanCallback);isScanning = true;}

8.扫描回调,处理扫描到的设备。ScanCallback是一个抽象类,用于接收蓝牙扫描结果的回调,callbackType是指扫描回调类型,表示扫描是如何被触发的。result:是指包含扫描到的设备信息的ScanResult对象。result.getDevice(): 获取扫描到的蓝牙设备对象。把新扫描到的设备添加到列表。runOnUiThread表示线程操作。Android规定UI操作必须在主线程(UI线程)执行,而蓝牙扫描回调通常发生在后台线程。runOnUiThread确保这些UI更新操作在正确的线程执行,避免应用崩溃。工作的流程:蓝牙适配器开始扫描。每当扫描到一个设备,系统调用onScanResult回调方法提取设备信息。如果是新设备且有名称,则更新UI列表。同时保存设备对象到映射表,供后续连接使用。

private final ScanCallback leScanCallback = new ScanCallback() {@Overridepublic void onScanResult(int callbackType, ScanResult result) {super.onScanResult(callbackType, result);//获取设备信息BluetoothDevice device = result.getDevice();String deviceName = device.getName();String deviceAddress = device.getAddress();//添加新设备到列表if (deviceName != null && !deviceNames.contains(deviceName)) {runOnUiThread(() -> {deviceNames.add(deviceName);deviceMap.put(deviceAddress,device);  // 保存 ScanResultdeviceAdapter.notifyDataSetChanged();});}}};

9.点击设备连接的方法,连接设备前要先停止扫描,因为扫描非常耗电,最好永不循环扫描,并始终设置扫描时间限制。先检查是否已经连接了该设备,然后清理旧的连接资源,因为Android蓝牙API限制,一个设备只能有一个活跃的GATT连接。通过device.connectGatt()方法创建GATT连接。

private void connectToDevice(BluetoothDevice device) {// 连接前先停扫描stopScan();//检查是否已连接该设备if (currentDevice != null && currentDevice.equals(device) && isConnected) {Toast.makeText(this, "已经连接到该设备", Toast.LENGTH_SHORT).show();return;}//关闭现有连接if (mBluetoothGatt != null) {mBluetoothGatt.disconnect();mBluetoothGatt.close();mBluetoothGatt = null;}//保存当前正在连接的设备引用currentDevice = device;Toast.makeText(this, "正在连接: " + device.getName(), Toast.LENGTH_SHORT).show();//建立新连接mBluetoothGatt = device.connectGatt(this, false, gattCallback, BluetoothDevice.TRANSPORT_LE);}

10.GATT回调,onConnectionStateChange这个方法是用来连接状态的回调,status == 0表示操作成功,newState表示新状态。连接成功后,可以调用gatt.discoverServices()去发现蓝牙设备上所有服务;onServicesDiscovered这个方法服务发现的回调方法,首先检查服务发现是否成功,然后确认设备支持我们需要的服务,调用enableNotify()和手动设置,确保通知启用,请求更大的传输单元提高效率,给通知设置留出完成时间,再发送数据请求。详情请参照:https://blog.csdn.net/m0_50891221/article/details/150616109?spm=1011.2124.3001.6209

private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {//连接状态回调@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {Log.d(TAG, "status=" + status + ", newState=" + newState);if (status == 0 && newState == BluetoothProfile.STATE_CONNECTED) {isConnected = true;runOnUiThread(() -> Toast.makeText(MainActivity.this, "连接成功", Toast.LENGTH_SHORT).show());gatt.discoverServices();} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {isConnected = false;runOnUiThread(() -> Toast.makeText(MainActivity.this, "设备已断开", Toast.LENGTH_SHORT).show());}}//服务发现回调@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {if (status != BluetoothGatt.GATT_SUCCESS) return;//查找目标服务BluetoothGattService service = gatt.getService(serviceUUID);if (service == null) {Log.e(TAG, "目标服务不存在");return;}// 开启通知boolean notifySuccess = enableNotify();Log.d(TAG, "开启通知: " + notifySuccess);// 等通知开启完成后再发送电量请求(这里直接延迟执行一次)listView.postDelayed(() -> sendGetBatteryCommand(gatt), 500);}// 发送电量请求命令private void sendGetBatteryCommand(BluetoothGatt gatt) {//获取目标服务BluetoothGattService service = gatt.getService(serviceUUID);if (service == null) return;//获取发送特征BluetoothGattCharacteristic tx = service.getCharacteristic(txUUID);if (tx == null) return;//构造命令数据,自定义协议byte[] cmd = new byte[]{0, 0x27, 0x01, 0x00, 0x02, 0x01, 0x00};//设置特征值:将命令数据设置到特征中。特征可以存储数据值,用于向设备发送信息tx.setValue(cmd);//设置写入类型:WRITE_TYPE_NO_RESPONSE:写入后不需要设备确认(更快,但不可靠)// WRITE_TYPE_DEFAULT:写入后等待设备确认(较慢,但可靠)tx.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);//执行写入操作boolean result = gatt.writeCharacteristic(tx);Log.d(TAG, "发送电量请求: " + result);}//描述符写入完成回调@Overridepublic void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {super.onDescriptorWrite(gatt, descriptor, status);Log.d(TAG, "通知已开启");getMTU();}//特征值改变回调(收到数据)//当设备通过通知(Notification)发送数据时,这个方法被自动调用@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {//从特征中提取设备发送的原始字节数据byte[] value = characteristic.getValue();Log.d(TAG, "收到数据: " + bytesToHex(value));//数据有效性检查if (value == null || value.length < 10) {return;}int leftBattery = parseLeftBattery(value);runOnUiThread(() ->Toast.makeText(MainActivity.this, "左耳电量: " + leftBattery + "%", Toast.LENGTH_LONG).show());}};

11.停止扫描

private void stopScan() {isScanning = false;scan.setText("开始扫描");//停止扫描mBluetoothAdapter.getBluetoothLeScanner().stopScan(leScanCallback);}

代码示例

MainActivity.java

package com.example.myapplication;import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";//UIprivate ListView listView;//列表private TextView scan;//扫描按钮private boolean isScanning = false;//扫描状态标志private boolean isConnected = false;//连接状态标志private ArrayAdapter<String> deviceAdapter;//列表适配器private List<String> deviceNames = new ArrayList<>();//设备名称列表,用于显示UIprivate Map<String,BluetoothDevice> deviceMap = new LinkedHashMap<>();//以MAC地址为键,存储设备对象,便于后续连接操作//蓝牙相关private BluetoothAdapter mBluetoothAdapter;//蓝牙适配器private BluetoothGatt mBluetoothGatt;  // GATT 连接对象private BluetoothDevice currentDevice;//当前连接的设备//序列号private  int seq = 0;//请求码private static final int REQUEST_PERMISSION = 1;//UUIDprivate final static UUID serviceUUID = UUID.fromString("0000fdb3-0000-1000-8000-00805f9b34fb");//服务特征UUIDprivate final static UUID txUUID = UUID.fromString("0000ff17-0000-1000-8000-00805f9b34fb");//发送特征UUIDprivate final static UUID rxUUID = UUID.fromString("0000ff18-0000-1000-8000-00805f9b34fb");//接收特征UUIDprivate static final UUID CCCD_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");//客户端特征配置描述符@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//获取视图listView = findViewById(R.id.listview);scan = findViewById(R.id.tv_scan);//设置列表适配器deviceAdapter = new ArrayAdapter<>(this,android.R.layout.simple_list_item_1,deviceNames);listView.setAdapter(deviceAdapter);//扫描按钮点击事件scan.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {requestPermission();if(!isScanning){startScan();}else{stopScan();;}}});//列表点击事件listView.setOnItemClickListener((parent, view, position, id) -> {String deviceName = deviceNames.get(position);BluetoothDevice device = deviceMap.get(getKeyByDeviceName(deviceName));  // 获取 BluetoothDevice 对象if (device != null){connectToDevice(device);  // 连接设备}});}@Overrideprotected void onPause() {super.onPause();stopScan();}@Overrideprotected void onDestroy() {super.onDestroy();if (mBluetoothGatt != null) {mBluetoothGatt.close();  // 确保在销毁时关闭连接}stopScan();}//通过设备名称获取Key(MAC地址)private String getKeyByDeviceName(String deviceName) {for (Map.Entry<String, BluetoothDevice> entry : deviceMap.entrySet()) {if (entry.getValue().getName() != null &&entry.getValue().getName().equals(deviceName)) {return entry.getKey();}}return null;}//1.检查并请求权限private boolean isPermission(String permission) {return checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;}private boolean requestPermission() {if (!isPermission(Manifest.permission.ACCESS_COARSE_LOCATION)|| !isPermission(Manifest.permission.BLUETOOTH_SCAN)|| !isPermission(Manifest.permission.BLUETOOTH_CONNECT)) {requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.BLUETOOTH_SCAN,Manifest.permission.BLUETOOTH_CONNECT}, REQUEST_PERMISSION);return false;}return true;}//2.开始扫描private void startScan() {if (!requestPermission()) return;scan.setText("停止扫描");//获取蓝牙适配器BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);mBluetoothAdapter = bluetoothManager.getAdapter();if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {Toast.makeText(this, "蓝牙不可用或未开启", Toast.LENGTH_SHORT).show();finish();}// 清空扫描结果deviceNames.clear();deviceMap.clear();deviceAdapter.notifyDataSetChanged();//开始扫描设备mBluetoothAdapter.getBluetoothLeScanner().startScan(leScanCallback);isScanning = true;}//3.扫描回调private final ScanCallback leScanCallback = new ScanCallback() {@Overridepublic void onScanResult(int callbackType, ScanResult result) {super.onScanResult(callbackType, result);//获取设备信息BluetoothDevice device = result.getDevice();String deviceName = device.getName();String deviceAddress = device.getAddress();//添加新设备到列表if (deviceName != null && !deviceNames.contains(deviceName)) {runOnUiThread(() -> {deviceNames.add(deviceName);deviceMap.put(deviceAddress,device);  // 保存 ScanResultdeviceAdapter.notifyDataSetChanged();});}}};//4.连接指定设备private void connectToDevice(BluetoothDevice device) {// 连接前先停扫描stopScan();//检查是否已连接该设备if (currentDevice != null && currentDevice.equals(device) && isConnected) {Toast.makeText(this, "已经连接到该设备", Toast.LENGTH_SHORT).show();return;}//关闭现有连接if (mBluetoothGatt != null) {mBluetoothGatt.disconnect();mBluetoothGatt.close();mBluetoothGatt = null;}//保存当前正在连接的设备引用currentDevice = device;Toast.makeText(this, "正在连接: " + device.getName(), Toast.LENGTH_SHORT).show();//建立新连接mBluetoothGatt = device.connectGatt(this, false, gattCallback, BluetoothDevice.TRANSPORT_LE);}//5.GATT回调private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {//连接状态回调@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {Log.d(TAG, "status=" + status + ", newState=" + newState);if (status == 0 && newState == BluetoothProfile.STATE_CONNECTED) {isConnected = true;runOnUiThread(() -> Toast.makeText(MainActivity.this, "连接成功", Toast.LENGTH_SHORT).show());gatt.discoverServices();} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {isConnected = false;runOnUiThread(() -> Toast.makeText(MainActivity.this, "设备已断开", Toast.LENGTH_SHORT).show());}}//服务发现回调@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {if (status != BluetoothGatt.GATT_SUCCESS) return;//查找目标服务BluetoothGattService service = gatt.getService(serviceUUID);if (service == null) {Log.e(TAG, "目标服务不存在");return;}// 开启通知boolean notifySuccess = enableNotify();Log.d(TAG, "开启通知: " + notifySuccess);// 等通知开启完成后再发送电量请求(这里直接延迟执行一次)listView.postDelayed(() -> sendGetBatteryCommand(gatt), 500);}// 发送电量请求命令private void sendGetBatteryCommand(BluetoothGatt gatt) {//获取目标服务BluetoothGattService service = gatt.getService(serviceUUID);if (service == null) return;//获取发送特征BluetoothGattCharacteristic tx = service.getCharacteristic(txUUID);if (tx == null) return;//构造命令数据,自定义协议byte[] cmd = new byte[]{0, 0x27, 0x01, 0x00, 0x02, 0x01, 0x00};//设置特征值:将命令数据设置到特征中。特征可以存储数据值,用于向设备发送信息tx.setValue(cmd);//设置写入类型:WRITE_TYPE_NO_RESPONSE:写入后不需要设备确认(更快,但不可靠)// WRITE_TYPE_DEFAULT:写入后等待设备确认(较慢,但可靠)tx.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);//执行写入操作boolean result = gatt.writeCharacteristic(tx);Log.d(TAG, "发送电量请求: " + result);}//描述符写入完成回调@Overridepublic void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {super.onDescriptorWrite(gatt, descriptor, status);Log.d(TAG, "通知已开启");getMTU();}//特征值改变回调(收到数据)//当设备通过通知(Notification)发送数据时,这个方法被自动调用@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {//从特征中提取设备发送的原始字节数据byte[] value = characteristic.getValue();Log.d(TAG, "收到数据: " + bytesToHex(value));//数据有效性检查if (value == null || value.length < 10) {return;}int leftBattery = parseLeftBattery(value);runOnUiThread(() ->Toast.makeText(MainActivity.this, "左耳电量: " + leftBattery + "%", Toast.LENGTH_LONG).show());}};// 写数据方法private boolean writeData(byte[] value) {// 写数据方法//检查gatt连接是否存在if (mBluetoothGatt == null) return false;//获取目标服务BluetoothGattService service = mBluetoothGatt.getService(serviceUUID);if (service == null) return false;//获取发送特征BluetoothGattCharacteristic txCharacteristic = service.getCharacteristic(txUUID);if (txCharacteristic == null) return false;//序列号管理value[0] = (byte) seq;//将序列号放入数据包的第一个字节seq++;if (seq > 15) seq = 0;//序列号在0-15之间循环(4位)txCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);txCharacteristic.setValue(value);return mBluetoothGatt.writeCharacteristic(txCharacteristic);}private boolean enableNotify() {// 封装开启通知的方法if (mBluetoothGatt == null) return false;//从 GATT连接中获取指定的服务BluetoothGattService service = mBluetoothGatt.getService(serviceUUID);if (service == null) return false;//找到接收特征BluetoothGattCharacteristic rxCharacteristic = service.getCharacteristic(rxUUID);if (rxCharacteristic == null) return false;//在本地启用通知监听boolean result = mBluetoothGatt.setCharacteristicNotification(rxCharacteristic, true);//配置设备的CCCD描述符BluetoothGattDescriptor descriptor = rxCharacteristic.getDescriptor(CCCD_UUID);if (descriptor != null) {//设置开启通知的值descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);//告诉设备“我要接收通知”mBluetoothGatt.writeDescriptor(descriptor);}return result;}//自定义MTU命令,发送获取MTU大小的命令public void getMTU() {Log.d(TAG,"getMTU()");writeData(new byte[]{0,0x27,1,0,2,(byte)0xFF,0});// 结果通过通知通道返回,需要在onCharacteristicChanged中解析}//解析电量private int parseLeftBattery(byte[] value) {// 错误时返回-1,防止数组越界if (value == null || value.length < 3) return -1;int index = value.length - 3; // 倒数第3个字节return value[index] & 0xFF;   // 转为无符号整数}//3.停止扫描private void stopScan() {isScanning = false;scan.setText("开始扫描");//停止扫描mBluetoothAdapter.getBluetoothLeScanner().stopScan(leScanCallback);}//转换字节为十六进制字符串private String bytesToHex(byte[] bytes) {StringBuilder sb = new StringBuilder();for (byte b : bytes) {sb.append(String.format("%02X ", b));}return sb.toString().trim();}}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".MainActivity"android:layout_marginTop="30dp"><ListViewandroid:id="@+id/listview"android:layout_width="match_parent"android:layout_height="680dp"android:layout_marginBottom="30dp"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/tv_scan"android:text="开始扫描"android:layout_centerHorizontal="true"android:layout_below="@+id/listview"/></RelativeLayout>

http://www.dtcms.com/a/355048.html

相关文章:

  • 基于STM32单片机的车牌识别设计
  • clcd土地利用数据分类
  • Tree Shaking原理
  • SOME/IP-SD事件组订阅
  • 昆泰芯离轴应用技术与产业链协同助力机器人关节产业实现技术突破
  • TDengine 数据订阅支持 MQTT 协议用户手册
  • 本地消息表实现分布式事务保证最终一致性
  • Java框架搭建实用开发
  • DPIN亮相DePIN Expo 2025,定义“DePIN 2.0”企业级应用新范式
  • Linux中Java后端调用外部进程 未处理后台输出流 导致io阻塞问题解决方法
  • K8S架构与组件完全解析
  • Baselight 携手 Walrus 激活链上数据价值,打造无需许可的数据中
  • LeetCode热题100--98. 验证二叉搜索树--中等
  • QT 概述(背景介绍、搭建开发环境、Qt Creator、程序、项目文件解析、编程注意事项)
  • Fortran快速排序算法实现与优化
  • Web安全:深入理解User-Agent报头注入与防御
  • 从CTFshow-pwn入门-pwn43理解栈溢出到底跳转call还是plt
  • 网络安全测试(一)Kali Linux
  • PyTorch实战(3)——PyTorch vs. TensorFlow详解
  • 网络安全设备监控指标
  • jvm锁优化
  • MiniCPM-V 4.5 vs MiniCPM-V 2.6 深度对比分析
  • claude code helper for vscode
  • MTK Linux DRM分析(十七)- MTK KMS实现mtk_drm_fb.c
  • HTML贪吃蛇游戏实现
  • SQLSERVER触发器
  • C++讲解---什么是静态成员函数
  • 云计算学习100天-第28天
  • 软件测试(三):测试流程及测试用例
  • 如果被控端显示器分辨率是2k,远程控制软件的画质设置是4k,主控端显示器的分辨率是2k,那主控端看到的被控端画面是几k