版权声明
文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/10136332.html
前言
蓝牙开发其实分2个部分,一个是正常蓝牙功能的开发(比如Android蓝牙的互相连接、读取蓝牙列表、文件传输、蓝牙耳机等等)、另外一个是BLE蓝牙开发(属于低功耗蓝牙设备,设备大多是血糖仪、蓝牙手环、蓝牙手表、蓝牙温度枪等等)
经典蓝牙功能开发
权限
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-feature android:name="android.hardware.location.gps" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
为什么需要gps和定位权限,因为蓝牙搜索需要这2个权限(我也觉得莫名其妙)
打开蓝牙,关闭蓝牙
注意以下的开发以6.0为准,其他版本蓝牙开启、关闭、搜索些许不同
public class BTDemo extends AppCompatActivity implements View.OnClickListener{
private final String TAG = "BTDemo";
private Button btn_bt_open,btn_bt_close,btn_bt_goSttings,btn_bt_visible,in_BTList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_btdemo);
btn_bt_open = (Button)findViewById(R.id.btn_BT_open);
btn_bt_close = (Button)findViewById(R.id.btn_BT_close);
btn_bt_visible = (Button)findViewById(R.id.btn_BT_visible);
btn_bt_goSttings = (Button)findViewById(R.id.btn_BT_GoSttings);
in_BTList = (Button)findViewById(R.id.in_BTList);
btn_bt_open.setOnClickListener(this);
btn_bt_close.setOnClickListener(this);
btn_bt_visible.setOnClickListener(this);
btn_bt_goSttings.setOnClickListener(this);
in_BTList.setOnClickListener(this);
}
//打开蓝牙
public void openBT(){
//创建蓝牙适配器
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null){
Log.e(TAG, "该设备不支持蓝牙");
}
if(!bluetoothAdapter.isEnabled()){
Log.e(TAG, "准备打开蓝牙" );
//弹窗询问方式打开蓝牙
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);//蓝牙适配器. 行动-请求-打开
//startActivity(intent);也可以使用这个
startActivityForResult(intent,RESULT_OK);
//bluetoothAdapter.enable(); 不询问直接打开蓝牙
}
}
//关闭蓝牙
public void closeBT(){
Log.e(TAG, "coloseBT 被调用" );
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(bluetoothAdapter.isEnabled()){
bluetoothAdapter.disable();
}
}
//跳转到设置-蓝牙界面中
public void settingsOpen(){
Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
startActivity(intent);
}
//蓝牙可见(蓝牙可以被其他设备发现)
public void btVisible(){
BluetoothAdapter blueteoothAdapter = BluetoothAdapter.getDefaultAdapter();
//开启被其它蓝牙设备发现的功能
//getScanMode 获得扫描模式 扫描-模式-连接-发现
if (blueteoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
Intent i = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);//行动-请求-发现
//设置为一直开启 0是一直开着,如果设置了时间就会按照设置时间显示蓝牙可见
i.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0);
startActivity(i);
}
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_BT_open:
openBT();
break;
case R.id.btn_BT_close:
closeBT();
break;
case R.id.btn_BT_visible:
btVisible();
break;
case R.id.btn_BT_GoSttings:
settingsOpen();
break;
case R.id.in_BTList:
Intent intent = new Intent(BTDemo.this,BTListView.class);
startActivity(intent);
break;
default:
break;
}
}
}
蓝牙开关状态的监听
/** * 初始化蓝牙状态广播监听 */ private void initBtState(){ mBtTemperatureReceiver = new BtTemperatureReceiver(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); registerReceiver(mBtTemperatureReceiver,intentFilter); } /** * 蓝牙状态广播回调 */ class BtTemperatureReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); switch (action){ //注意!这里是先拿action 等于 BluetoothAdapter.ACTION_STATE_CHANGED 在解析intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0) case BluetoothAdapter.ACTION_STATE_CHANGED: L.e("触发蓝牙状态"); int blState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0); switch (blState){ case BluetoothAdapter.STATE_TURNING_ON: L.e("蓝牙正在开启"); break; case BluetoothAdapter.STATE_ON: L.e("蓝牙已经开启"); break; case BluetoothAdapter.STATE_TURNING_OFF: L.e("蓝牙正在关闭"); break; case BluetoothAdapter.STATE_OFF: L.e("蓝牙已经关闭"); break; case BluetoothAdapter.ERROR: break; default: break; } break; default: break; } } }
蓝牙搜索与蓝牙内容列表显示
public class BTListView extends AppCompatActivity { private ListView listView; private ArrayAdapter mArrayAdapter; private BluetoothAdapter mBluetoothAdapter; private BroadcastReceiver broadcastReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_btlist_view); //创建蓝牙适配器 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); //搜索蓝牙 mBluetoothAdapter.startDiscovery(); listView = (ListView) findViewById(R.id.BTlistView); //创建listView的适配器 mArrayAdapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1); //意图过滤器 IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothDevice.ACTION_FOUND); //蓝牙搜索 intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); //开始搜索 intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); //结束搜索 //创建广播接收器 broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //获得intent的行动 String action = intent.getAction(); /* 三组蓝牙广播状态分别是: BluetoothAdapter.ACTION_DISCOVERY_STARTED 开始蓝牙搜索 BluetoothDevice.ACTION_FOUND 蓝牙搜索中 BluetoothAdapter.ACTION_DISCOVERY_FINISHED 蓝牙搜索完毕 */ if (BluetoothDevice.ACTION_FOUND.equals(action)) { //创建蓝牙设备,我们可以从BluetoothDevice 里获得各种信息 名称、地址 等等 BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // 将设备名称和地址放入array adapter,以便在ListView中显示 mArrayAdapter.add(bluetoothDevice.getName() + "\n" + bluetoothDevice.getAddress()); } else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) { Toast.makeText(BTListView.this,"开始搜索", Toast.LENGTH_SHORT).show(); } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { Toast.makeText(BTListView.this,"搜索完毕",Toast.LENGTH_SHORT).show(); } } }; registerReceiver(broadcastReceiver,intentFilter);//添加广播 listView.setAdapter(mArrayAdapter); } @Override protected void onDestroy() { super.onDestroy(); mBluetoothAdapter.cancelDiscovery();// 取消搜索蓝牙 unregisterReceiver(broadcastReceiver);//注销广播接收器 } }
配对与取消配对
/** * 配对(配对成功与失败通过广播返回) * * @param device */ fun pinBlue(device: BluetoothDevice?) { if (device == null) { Log.e(TAG, "bond device null") return } if (!mBluetoothAdapter.isEnabled) { Log.e(TAG, "Bluetooth not enable!") return } //配对之前把扫描关闭 if (mBluetoothAdapter.isDiscovering()) { mBluetoothAdapter.cancelDiscovery() } //判断设备是否配对,没有配对在配,配对了就不需要配了 if (device.bondState == BluetoothDevice.BOND_NONE) { Log.d(TAG, "attemp to bond:" + device.name) try { val createBondMethod: Method = device.javaClass.getMethod("createBond") createBondMethod.invoke(device) } catch (e: Exception) { e.printStackTrace() Log.e(TAG, "attemp to bond fail!") } } } /** * 取消配对(取消配对成功与失败通过广播返回 也就是配对失败) * * @param device */ fun cancelPinBlue(device: BluetoothDevice?) { if (device == null) { Log.d(TAG, "cancel bond device null") return } if (!mBluetoothAdapter.isEnabled) { Log.e(TAG, "Bluetooth not enable!") return } //判断设备是否配对,没有配对就不用取消了 if (device.bondState != BluetoothDevice.BOND_NONE) { Log.d(TAG, "attemp to cancel bond:" + device.name) try { val removeBondMethod: Method = device.javaClass.getMethod("removeBond") removeBondMethod.invoke(device) } catch (e: Exception) { e.printStackTrace() Log.e(TAG, "attemp to cancel bond fail!") } } }
获取已经连接的蓝牙设备
只能获取3种设备类型 A2DP , HEADSET , HEALTH
//定义两个设备间如何通过Bluetooth连接进行高质量的音频传输。 val a2dp = mBluetoothAdapter.getProfileConnectionState(BluetoothProfile.A2DP) //提供移动电话的Bluetooth耳机支持。包括Bluetooth耳机和Hands-Free (v1.5) profiles。 val headset = mBluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET) //手环? val health = mBluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEALTH) var flag = -1 if (a2dp == BluetoothProfile.STATE_CONNECTED){ flag = a2dp } else if (headset == BluetoothProfile.STATE_CONNECTED){ flag = headset } else if (health == BluetoothProfile.STATE_CONNECTED){ flag = health } val listener = object : BluetoothProfile.ServiceListener { override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { val b: List<BluetoothDevice> = proxy.getConnectedDevices() Log.e("zh", "onServiceConnected: ${b}") } override fun onServiceDisconnected(profile: Int) { Log.e("zh", "onServiceDisconnected: ${profile}") } } mBluetoothAdapter.getProfileProxy(this, listener, flag)
通过反射获取已经配对与已经连接的蓝牙设备
说明一下,这里为什么需要用反射获取蓝牙设备,在正常获取已经连接经典蓝牙设备,google只开放了3个类型,音频传输,耳机,健康检测设备。
而且其他遥控器,额温枪等等其他蓝牙设备则无法获取到(google把他们通用归类到gatt, ble设备),但是在一些tv设备的开发上 遥控器接收app其实作为系统级应用在后台运行,而我们依然有需求要在其他app里管理这些蓝牙设备。所以只能使用反射方式获取全部设备
kotlin
kotlin的反射需要添加
implementation "org.jetbrains.kotlin:kotlin-reflect:1.6.20"
代码
fun deviceConnected() { val declaredFunctions = BluetoothAdapter::class.declaredFunctions for (f in declaredFunctions) { if (f.name == "getConnectionState") { val state = f.call(mBluetoothAdapter) as Int if (state == BluetoothAdapter.STATE_CONNECTED) { //已经绑定过的全部设备,这些设备可能没有连接 val devices: Set<BluetoothDevice> = mBluetoothAdapter.getBondedDevices() for (device in devices) { val declaredFunctions = BluetoothDevice::class.declaredFunctions for (item in declaredFunctions) { if (item.name == "isConnected") { val isConnected = item.call(device) as Boolean if (isConnected) { Log.e( TAG, "device = ${device.name} address = ${device.address} ", ) } } } } } } } }
java
Class<BluetoothAdapter> bluetoothAdapterClass = BluetoothAdapter.class;//得到BluetoothAdapter的Class对象 try {//得到蓝牙状态的方法 Method method = bluetoothAdapterClass.getDeclaredMethod("getConnectionState", (Class[]) null); //打开权限 method.setAccessible(true); int state = (int) method.invoke(_bluetoothAdapter, (Object[]) null); if(state == BluetoothAdapter.STATE_CONNECTED){ LogUtil.i("BluetoothAdapter.STATE_CONNECTED"); Set<BluetoothDevice> devices = _bluetoothAdapter.getBondedDevices(); LogUtil.i("devices:"+devices.size()); for(BluetoothDevice device : devices){ Method isConnectedMethod = BluetoothDevice.class.getDeclaredMethod("isConnected", (Class[]) null); method.setAccessible(true); boolean isConnected = (boolean) isConnectedMethod.invoke(device, (Object[]) null); if(isConnected){ LogUtil.i("connected:"+device.getAddress()); return device.getAddress(); } } } } catch (Exception e) { e.printStackTrace(); }
监听蓝牙连接状态广播
ACTION_CONNECTION_STATE_CHANGED 与 ACTION_ACL_CONNECTED , ACTION_ACL_DISCONNECTED 的区别。
ACTION_CONNECTION_STATE_CHANGED : 只能获取 A2DP , HEADSET , HEALTH 这三种设备(都是耳机,蓝牙音箱)的状态
ACTION_ACL_CONNECTED,ACTION_ACL_DISCONNECTED : 可以获取任何设备的连接与断开状态
注册
private fun registeredBroadcastReceiver() { mBtReceiver = BtReceiver() val intentFilter = IntentFilter() //蓝牙连接状态 intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED) //蓝牙设备连接 intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED) //蓝牙设备断开 intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED) mBuild?.mContext?.registerReceiver(mBtReceiver, intentFilter) }
广播
inner class BtReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent) { when (intent.action) { BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED->{ val blueconState = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 0) val conncetDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) as BluetoothDevice when (blueconState) { BluetoothAdapter.STATE_CONNECTED -> { Log.e("zh", "blueConnectStateBroadcastReceiver>>>>STATE_CONNECTED") } BluetoothAdapter.STATE_CONNECTING -> { Log.e("zh", "blueConnectStateBroadcastReceiver>>>>STATE_CONNECTING") } BluetoothAdapter.STATE_DISCONNECTED -> { Log.e("zh", "blueConnectStateBroadcastReceiver>>>>STATE_DISCONNECTED") } BluetoothAdapter.STATE_DISCONNECTING -> { Log.e("zh", "blueConnectStateBroadcastReceiver>>>>STATE_DISCONNECTING") } else -> {} } } BluetoothDevice.ACTION_ACL_CONNECTED->{ val bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) as BluetoothDevice Log.e("zh", "ACTION_ACL_CONNECTED >>>> ${bluetoothDevice.name}") } BluetoothDevice.ACTION_ACL_DISCONNECTED->{ val bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) as BluetoothDevice Log.e("zh", "ACTION_ACL_DISCONNECTED >>>> ${bluetoothDevice.name}") } } } }
蓝牙配对广播
这里你需要注意,蓝牙的连接不等于配对! 蓝牙可以很容易连接成功,但是不等于配对成功。因为大多数蓝牙的配对都需要人为的在蓝牙设备上确认或者在Android设备中确认,才能算配对成功。
另外,一般蓝牙是在配对结束与取消配对后恢复蓝牙的搜索功能。因为蓝牙的连接会自动停止搜索功能,自然在上面的蓝牙连接监听在连接成功后的广播并不能恢复搜索(因为其实还在连接配对中)。只有确定蓝牙已经配对or取消配对后才能恢复搜索。
广播监听代码
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> { val device: BluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) when (device.bondState) { BluetoothDevice.BOND_BONDING -> //正在配对 Log.e("zh", "ACTION_BOND_STATE_CHANGED:正在配对 ") BluetoothDevice.BOND_BONDED -> { //配对结束 Log.e("zh", "ACTION_BOND_STATE_CHANGED:配对结束 ") startDiscovery() } BluetoothDevice.BOND_NONE -> { //取消配对/未配对 Log.e("zh", "ACTION_BOND_STATE_CHANGED:取消配对/未配对 ") startDiscovery() } } }
经典蓝牙的一些坑....
配对后重建activity的坑
这是Android的特性,在经典蓝牙连接后会立即重建当前页面的activity,原因也很好理解在插入一些蓝牙键盘,蓝牙鼠标这些输入设备后,你可能需要重建activity改变状态。
如果,不希望activity被重建可以在AndroidManifest.xml里的对应activity中添加:
<activity android:name=".ui.RemoteControlActivity" android:exported="false" android:configChanges="fontScale|keyboard|keyboardHidden|locale|orientation|screenLayout|uiMode|screenSize|navigation" />
BLE蓝牙设备开发
参考:Android BLE 蓝牙开发入门 https://www.jianshu.com/p/3a372af38103
参考:https://www.jianshu.com/p/d70e22ce61bc
服务、特征与对应UUID的概念
Service服务
一个低功耗蓝牙设备可以定义许多 Service, Service 可以理解为一个功能的集合。设备中每一个不同的 Service 都有一个 128 bit 的 UUID 作为这个 Service 的独立标志。蓝牙核心规范制定了两种不同的UUID,一种是基本的UUID,一种是代替基本UUID的16位UUID。所有的蓝牙技术联盟定义UUID共用了一个基本的UUID:
0x0000xxxx-0000-1000-8000-00805F9B34FB
为了进一步简化基本UUID,每一个蓝牙技术联盟定义的属性有一个唯一的16位UUID,以代替上面的基本UUID的‘x’部分。例如,心率测量特性使用0X2A37作为它的16位UUID,因此它完整的128位UUID为:
0x00002A37-0000-1000-8000-00805F9B34FB
Characteristic特征
在 Service 下面,又包括了许多的独立数据项,我们把这些独立的数据项称作 Characteristic。同样的,每一个 Characteristic 也有一个唯一的 UUID 作为标识符。在 Android 开发中,建立蓝牙连接后,我们说的通过蓝牙发送数据给外围设备就是往这些 Characteristic 中的 Value 字段写入数据;外围设备发送数据给手机就是监听这些 Charateristic 中的 Value 字段有没有变化,如果发生了变化,手机的 BLE API 就会收到一个监听的回调。
关于UUID
你购买的蓝牙设备够遵守开源思想并且尊重蓝牙设备行业规则,那么你可以在蓝牙官网上查看一份所有特征码的对应UUID.他们规范自定了各种数据对应的UUID.
如果蓝牙设备商够无耻厚脸皮就可以随便瞎改, 你拿官网的UUID表对照读取写入数据就根本没有意义了.(骂一下这些设备商脑子里根本没有开源2个字,脸皮这么厚为什么还使用蓝牙协议?这么牛皮怎么不自己开发无线传输协议?)
蓝牙官网:https://www.bluetooth.com/specifications/gatt/characteristics
权限
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-feature android:name="android.hardware.location.gps" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
开启蓝牙
public void startBt(){ BluetoothManager bluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); // bluetoothAdapter.enable();//8.0版本 使用这个可以弹窗询问开启 其他版本则是不提示启动 if (bluetoothAdapter == null||!bluetoothAdapter.isEnabled()){ Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(intent,1); } Log.e(TAG, "startBt: 开启蓝牙"); }
关闭蓝牙
public void closeBt(){ BluetoothManager bluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); if (bluetoothAdapter.isEnabled()){ bluetoothAdapter.disable(); } Log.e(TAG, "closeBt: 关闭蓝牙"); }
搜索设备/停止搜索,并且连接
Android5.0以下版本搜索方法
public void searchBt(){ BluetoothManager bluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); if (!bluetoothAdapter.isEnabled()){ return; } mCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { //得到设备扫描结果回调,每扫描到一个就回调一次。 if (TextUtils.isEmpty(device.getName())){ return; } if (device.getName().equals("BY21S")){//判断扫描到的设备名称,如果你需要更准确也可以根据蓝牙地址判断 device.getAddress() device.connectGatt(MainActivity.this,true,btCallback());//连接设备,1.参数为上下文 2.断开是否自动重连 3.设备连接回调接口类btCallback()方法我在下面有描述 stopSearchBt();//连接后依然要手动关闭搜索,否则会一直保持在搜索状态 } } }; bluetoothAdapter.startLeScan(mCallback);//开始扫描 Log.e(TAG, "searchBt: 搜索蓝牙"); }
Android5.0以下版本停止搜索
public void stopSearchBt(){ BluetoothManager bluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); if (!bluetoothAdapter.isEnabled()||mCallback == null){ return; } bluetoothAdapter.stopLeScan(mCallback);//注意这里的mCallback,要跟开启蓝牙搜索的Callback一致,否则无法关闭对应蓝牙搜索 }
Android5.0以上版本搜索方法
private void initSearchBt(){ if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()){ Log.e(TAG,"蓝牙未开启"); } scanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { // super.onScanResult(callbackType, result); if (TextUtils.isEmpty(result.getDevice().getName())){ return; } if (result.getDevice().getName().equals("AET-WD")){ result.getDevice().connectGatt(BtActivity.this,true,btCallback()); Log.e(TAG,"蓝牙连接成功"); mBluetoothAdapter.getBluetoothLeScanner().stopScan(scanCallback); } Log.e(TAG, "\n----------------------------\n name="+result.getDevice().getName()+"\n"+"address="+result.getDevice().getAddress()); } @Override public void onBatchScanResults(List<ScanResult> results) { super.onBatchScanResults(results); } @Override public void onScanFailed(int errorCode) { super.onScanFailed(errorCode); } }; mBluetoothAdapter.getBluetoothLeScanner().startScan(scanCallback); }
Android5.0以上版本停止搜索
mBluetoothAdapter.getBluetoothLeScanner().stopScan(scanCallback);
设备接口回调
这个回调接口类负责设备的全部数据交互
public BluetoothGattCallback btCallback(){ return new BluetoothGattCallback() { @Override public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) { super.onPhyUpdate(gatt, txPhy, rxPhy, status);
//PHY触发的回调,或者远程设备更改PHY,一般我们不需要这个回调。 } @Override public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) { super.onPhyRead(gatt, txPhy, rxPhy, status);
//PHY的读取,一般我们不需要这个回调。 } @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState);
//设备状态变化回调,连接成功后会首先触发回调 回调参数分别为 1.蓝牙网关 2.蓝牙状态 3.连接情况 } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status);
//发现服务回调,使用发现服务后会首先触发这个回调,我们在这里可以获得对应UUID的蓝牙服务(Services)和特征(Characteristic) } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status);
//读取特征的回调 } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicWrite(gatt, characteristic, status);
//写入特征数据的回调,写入后会回调一次这个方法,你可以读取一次你写入的数据以确认写入数据无误。 } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic);
//特征变化回调,一般是设置特征通知后,指定的特征在主动蓝牙设备上给手机app回调数据时触发的回调 } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorRead(gatt, descriptor, status);
//读取描述符的回调 } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorWrite(gatt, descriptor, status);
//写入描述符的回调 } @Override public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { super.onReliableWriteCompleted(gatt, status);
//可信写入回调,当你写入非法范围的值(比如温度范围10-40,但是你输入了一个50)时可以调用对应方法,切换到这个回调中。处理后续逻辑。 } @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { super.onReadRemoteRssi(gatt, rssi, status);
//读取信号强度的回调 rssi为信号强度值() } @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { super.onMtuChanged(gatt, mtu, status);
//蓝牙网卡变化回调 } }; }
各个接口回调方法对应的方法
(1) setCharacteristicNotification对应onCharacteristicChanged;
gatt.setCharacteristicNotification(characteristic, true);
该方法一般是在发现服务后,进行设置的,设置该方法的目的是让硬件在数据改变的时候,发送数据给app,app则通过onCharacteristicChanged方法回调给用户,从参数中可获取到回调回来的数据。
(2) readCharacteristic对应onCharacteristicRead;
gatt.readCharacteristic(characteristic);
(3) writeCharacteristic对应onCharacteristicWrite;
gatt.wirteCharacteristic(mCurrentcharacteristic);
(4) 连接蓝牙或者断开蓝牙 对应 onConnectionStateChange;
bluetoothDevice.connectGatt(this, false, mGattCallback);
或
gatt.disconnect();(断开连接后务必记得gatt.close();)
(5) readDescriptor对应onDescriptorRead;
gatt.readDescriptor(descriptor);
(6) writeDescriptor对应onDescriptorWrite;
gatt.writeDescriptor(descriptor);
(7) readRemoteRssi对应onReadRemoteRssi;
gatt.readRemoteRssi();
(8) executeReliableWrite对应onReliableWriteCompleted;
gatt.executeReliableWrite();
(9) discoverServices对应onServicesDiscovered
gatt.discoverServices();//发现服务
获取已经连接的BLE设备
val bluetoothManager = this.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager val devices = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT) Log.e(TAG, "deviceConnected: devices${devices}") for (device in devices) { Log.e("zh", "devices:${devices} ") }
操作步骤流程
开启蓝牙>搜索蓝牙设备>连接设备>发现设备服务>获取指定UUID服务>获取指定服务下的UUID特征>操作特征发送数据或者读取数据>设置长时间广播监听某项蓝牙特征回调>断开设备>关闭蓝牙
开启蓝牙和搜索蓝牙/连接设备已经在上面有介绍了,不需要重复,下面我们说下后续的步骤
发现设备服务
@Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); if (status == BluetoothGatt.GATT_SUCCESS && BluetoothGatt.STATE_CONNECTED == newState){ Log.e(TAG, "onConnectionStateChange: 设备连接成功 status状态="+status+"连接情况="+newState); gatt.discoverServices();//发现服务 } // 其他类型newState状态 // BluetoothGatt.STATE_CONNECTED;//已经连接 // BluetoothGatt.STATE_CONNECTING;//正在连接 // BluetoothGatt.STATE_DISCONNECTED;//已经断开 // BluetoothGatt.STATE_DISCONNECTING;//正在断开 }
在设备状态变化回调触发后,就可以使用gatt.discoverServices()发现设备服务了
获取指定UUID服务与获取指定服务下的UUID特征
@Override public void onServicesDiscovered(final BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); //发现服务回调,使用发现服务后会首先触发这个回调,我们在这里可以获得对应UUID的蓝牙服务(Services)和特征(Characteristic) Log.e(TAG, "onServicesDiscovered: "); mGatt = gatt; BluetoothGattService service = gatt.getService(UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e"));//获取对应uuid的服务 mCharacteristic = service.getCharacteristic(UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e"));//从服务里获取对应uuid特征 }
读取全部设备里的全部服务与特征(以及特征状态)
@Override public void onServicesDiscovered(final BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); Log.e(TAG, "onServicesDiscovered: "); new Thread(new Runnable() { @Override public void run() { List<BluetoothGattService> serviceList = gatt.getServices();//获取设备里的全部服务List集合 for (BluetoothGattService service : serviceList){ Log.e(TAG, "service uuid = "+service.getUuid()+"----------------"); List<BluetoothGattCharacteristic> characteristicList = service.getCharacteristics();//获取指定服务里全部特征的List集合 for (BluetoothGattCharacteristic characteristic : characteristicList){ int charaProp = characteristic.getProperties(); Log.e(TAG, "characteristic uuid="+characteristic.getUuid()); if ((charaProp | BluetoothGattCharacteristic.PERMISSION_READ)>0){ Log.e(TAG, "可读"); } if ((charaProp | BluetoothGattCharacteristic.PERMISSION_WRITE)>0){ Log.e(TAG, "可写"); } if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY)>0){ Log.e(TAG, "可通知"); } } } } }).start();
因为回调方法都在蓝牙操作线程里,为了不堵塞蓝牙消息,所以耗时操作建议创建线程单独操作.
操作特征发送数据或者读取数据
发送数据
public void sendData(View view){ Log.e(TAG, "sendData: 发送数据"); mCharacteristic.setValue(setValue()); mGatt.writeCharacteristic(mCharacteristic); } private byte[] setValue(){ data = new byte[20]; data[0] = (byte)0xAB; data[1] = (byte)0x00; data[2] = (byte)0x00; data[3] = (byte)0xff; data[4] = (byte)0x31; data[5] = (byte)0x09; data[6] = (byte)0x01; data[7] = (byte)0x00; data[8] = (byte)0x00; data[9] = (byte)0x00; data[10] = (byte)0x00; data[11] = (byte)0x00; data[12] = (byte)0x00; data[13] = (byte)0x00; data[14] = (byte)0x00; data[15] = (byte)0x00; data[16] = (byte)0x00; data[17] = (byte)0x00; data[18] = (byte)0x00; data[19] = (byte)0x00; return data; }
一次数据发送最多发送20个字节的数据,多了需要分包发送.另外如果有自定义数据协议,按照协议在指定位置插入对应数据.写入数据后后会执行一次onCharacteristicWrite()回调方法(你可以在这个方法再次确认写入的数据,也可以不管这个方法)
读取数据
public void readData(){ BluetoothGattService service = mGatt.getService(SERVICE_UUID); BluetoothGattCharacteristic characteristic = service.getCharacteristic(CHARACTER_UUID); mGatt.readCharacteristic(characteristic); }
操作读取数据后,数据会在onCharacteristicRead方法里回调
@Override public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) { Log.d(TAG, "callback characteristic read status " + status + " in thread " + Thread.currentThread()); if (status == BluetoothGatt.GATT_SUCCESS) { Log.d(TAG, "read value: " + characteristic.getValue()); } }
向蓝牙设备注册监听实现实时读取蓝牙设备的数据
BLE app通常需要获取设备中characteristic 变化的通知。下面的代码演示了怎么为一个Characteristic 设置一个监听。
@Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); List<BluetoothGattService> list = gatt.getServices(); for (BluetoothGattService service : list){ Log.e(TAG,"uuid="+service.getUuid()); } UUID suuid = UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb"); BluetoothGattService bluetoothGattService = gatt.getService(suuid); BluetoothGattCharacteristic readCharacteristic = bluetoothGattService.getCharacteristic(UUID.fromString("0000fff3-0000-1000-8000-00805f9b34fb")); gatt.setCharacteristicNotification(readCharacteristic,true); List<BluetoothGattDescriptor> list1 = readCharacteristic.getDescriptors(); for (BluetoothGattDescriptor descriptor : list1){ Log.e(TAG,"descriptor uuid = "+descriptor.getUuid()); } //下面readCharacteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")) 的uuid就是上面遍历描述符获取的来的,也可以在蓝牙协议书上找到. //但是这里有一个坑,如果蓝牙协议书提供的不完整,你会下意识的以为此处描述符的uuid就是读取特征的uuid.会发现BluetoothGattDescriptor descriptor怎么获取都是null,并且后续操作不会报错.. //所以建议遍历描述符目视区分和确认uuid,避坑 BluetoothGattDescriptor descriptor = readCharacteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); gatt.writeDescriptor(descriptor); //另外注意一个坑,如果下面的代码你需要发送值给蓝牙设备,那么最好做异步.操作蓝牙是不允许 写/读/设置 短时间内同时操作的 }
值得注意的是,除了通过 BluetoothGatt#setCharacteristicNotification 开启 Android 端接收通知的开关,还需要往 Characteristic 的 Descriptor 属性写入开启通知的数据开关使得当硬件的数据改变时,主动往手机发送数据。
断开连接
当我们连接蓝牙设备完成一系列的蓝牙操作之后就可以断开蓝牙设备的连接了。通过 BluetoothGatt#disconnect 可以断开正在连接的蓝牙设备。当这一个方法被调用之后,系统会异步回调 BluetoothGattCallback#onConnectionStateChange 方法。通过这个方法的 newState 参数可以判断是连接成功还是断开成功的回调。
由于 Android 蓝牙连接设备的资源有限,当我们执行断开蓝牙操作之后必须执行 BluetoothGatt#close 方法释放资源。需要注意的是通过 BluetoothGatt#close 方法也可以执行断开蓝牙的操作,不过 BluetoothGattCallback#onConnectionStateChange 将不会收到任何回调。此时如果执行 BluetoothGatt#connect 方法会得到一个蓝牙 API 的空指针异常。所以,我们推荐的写法是当蓝牙成功连接之后,通过 BluetoothGatt#disconnect 断开蓝牙的连接,紧接着在 BluetoothGattCallback#onConnectionStateChange 执行 BluetoothGatt#close 方法释放资源。
以下是代码示例:
@Override public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) { Log.d(TAG, "onConnectionStateChange: thread " + Thread.currentThread() + " status " + newState); if (status != BluetoothGatt.GATT_SUCCESS) { String err = "Cannot connect device with error status: " + status; // 当尝试连接失败的时候调用 disconnect 方法是不会引起这个方法回调的,所以这里 // 直接回调就可以了。 gatt.close(); Log.e(TAG, err); return; } if (newState == BluetoothProfile.STATE_CONNECTED) { gatt.discoverService(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { gatt.close(); } }
断开连接设备的坑....
当你以为操作完上面的断开设备的时候,你会以为真的断开了设备.其实...是断开了设备,但是设备可能还被放入到蓝牙记忆设备列表里.下次你开启服务或者开启蓝牙的时候你会发现,设备居然搜索不到,其实是因为设备已经自动根据记忆设备连接上了....
是不是很蛋疼....下面可以使用这种反射方法删除所有记忆设备,这样下次蓝牙就不会乱自动连接设备了
//得到配对的设备列表,清除已配对的设备 public void removePairDevice(){ if(mBluetoothAdapter!=null){ Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices(); for(BluetoothDevice device : bondedDevices ){ unpairDevice(device); } } } //反射来调用BluetoothDevice.removeBond取消设备的配对 private void unpairDevice(BluetoothDevice device) { try { Method m = device.getClass() .getMethod("removeBond", (Class[]) null); m.invoke(device, (Object[]) null); } catch (Exception e) { Log.e("ytzn", e.getMessage()); } }
或者
private synchronized void refreshDeviceCache() { try { final Method refresh = BluetoothGatt.class.getMethod("refresh"); if (refresh != null && bluetoothGatt != null) { boolean success = (Boolean) refresh.invoke(bluetoothGatt); BleLog.i("refreshDeviceCache, is success: " + success); } } catch (Exception e) { BleLog.i("exception occur while refreshing device: " + e.getMessage()); e.printStackTrace(); } }
重连设备的坑...
连接断开之后可以根据实际情况进行重连,但如果是连接失败的情况,建议不要立即重连而是调用void closeBluetoothGatt0 清空一下状态,并延迟一段时间等待复位,否则会把gatt阻塞,导致手机不重启蓝牙就再也无法连接任何设备的严重情况
end
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/10136332.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具