目錄
- 前言
- 一般開發步驟
- 相關API介紹
- 一、通用API
- 1.BluetoothAdapter
- 2.BluetoothDevice
- 二、經典藍牙(BT)API
- 1.BluetoothSocket
- 2.BluetoothServerSocket
- 3.BluetoothClass
- 4.BluetoothProfile
- 三、低功耗藍牙(BLE)API
- 1.BluetoothGatt
- 2.BluetoothGattCallback
- 3.BluetoothGattService
- 4.BluetoothGattCharacteristic
- 5.BluetoothGattDescriptor
- 1.加入藍牙權限
- (1)android.permission.BLUETOOTH
- (2)android.permission.BLUETOOTH_ADMIN
- 2.是否支持藍牙
- 3.是否打開藍牙
- (1)判斷藍牙是否打開
- (2)友好提示用戶打開藍牙
- (3)監聽用戶是否打開藍牙的操作回呼
- 4.掃描藍牙
- (1)經典藍牙搜索
- ① 新建藍牙廣播接收器
- ② 動態注冊藍牙廣播接收器
- ② 開啟掃描藍牙
- (2)低功耗藍牙搜索
- 5.藍牙連接與斷開
- (1)經典藍牙(BT)
- ① createRfcommSocketToServiceRecord獲取BluetoothSocket
- ② 反射獲取BluetoothSocket
- ③ 連接藍牙
- ④ 靜默設定配對碼
- ⑤ 斷開連接
- (2)低功耗藍牙(BLE)
- 連接之前
- GATT層次結構
- 各成員作用
- ① 設定自定義回呼
- ② 啟動藍牙連接
- ③ 發現GATT服務
- ④ 配置通信
- ⑤ 斷開連接
- 6.通信
- (1)經典藍牙通信
- ① 獲取輸入輸出流
- ② 發送資料
- ③ 讀取資料
- (2)低功耗藍牙通信
- ① 發送資料
- ② 讀取資料
- 7.補充
- 參考
前言
在做藍牙技術開發前,首先可以了解一下藍牙模塊分類:

現今藍牙模塊開發分為經典藍牙和低功耗藍牙開發,目前市面上最火的物聯網技術大多選擇低功耗藍牙模塊,效率高且功耗低,缺點是只支持小資料量的資料傳輸,在Android 4.3 (API 18)后開始引入;而對于資料量較大的傳輸,如音視頻等開發,則需要使用經典藍牙模塊,在開發前一定要先區分藍牙模塊型別,因為兩種藍牙模塊的開發是不同的,在本篇文章兩種藍牙模塊的開發步驟都會介紹到,
一般開發步驟
藍牙模塊開發步驟如下:

兩種藍牙模塊的開發步驟大致一樣,僅藍牙連接和藍牙通信時有所區分,
相關API介紹
一、通用API
1.BluetoothAdapter
本地藍牙配接器,用于一些藍牙的基本操作,比如判斷藍牙是否開啟、搜索藍牙設備等,
2.BluetoothDevice
藍牙設備物件,包含一些藍牙設備的屬性,比如設備名稱、mac地址等,
二、經典藍牙(BT)API
1.BluetoothSocket
表示藍牙socket的介面(與TCP Socket類似, 關于socket的概念請自行查閱計算機網路的相關內容),該類的物件作為應用中資料傳輸的連接點,
2.BluetoothServerSocket
表示服務器socket,用來監聽未來的請求(和TCP ServerSocket類似),為了能使兩個藍牙設備進行連接,一個設備必須使用該類開啟服務器socket,當遠程的藍牙設備請求該服務端設備時,如果連接被接受,BluetoothServerSocket將會回傳一個已連接的BluetoothSocket類物件,
3.BluetoothClass
描述藍牙設備的主要特征,BluetoothClass的類物件是一個只讀的藍牙設備的屬性集,盡管該類物件并不能可靠地描述BluetoothProfile的所有內容以及該設備支持的所有服務資訊,但是該類物件仍然有助于對該設備的型別進行提示,
4.BluetoothProfile
表示藍牙規范,藍牙規范是兩個基于藍牙設備通信的標準,
三、低功耗藍牙(BLE)API
1.BluetoothGatt
藍牙通用屬性協議,定義了BLE通訊的基本規則,是BluetoothProfile的實作類,Gatt是Generic Attribute Profile的縮寫,用于連接設備、搜索服務等操作,
2.BluetoothGattCallback
藍牙設備連接成功后,用于回呼一些操作的結果,必須連接成功后才會回呼,
3.BluetoothGattService
藍牙設備提供的服務,是藍牙設備特征的集合,
4.BluetoothGattCharacteristic
藍牙設備特征,是構建GATT服務的基本資料單元,
5.BluetoothGattDescriptor
藍牙設備特征描述符,是對特征的額外描述,
1.加入藍牙權限
(1)android.permission.BLUETOOTH
為了能夠在你開發的應用設備中使用藍牙功能,必須宣告藍牙的權限"BLUETOOTH",在進行藍牙的通信,例如請求連接,接受連接以及交換資料中,需要用到該權限
(2)android.permission.BLUETOOTH_ADMIN
如果你的應用程式需要實體化藍牙設備的搜索或者對藍牙的設定進行操作,那么必須宣告BLUETOOTH_ADMIN權限,大多數應用需要該權限對本地的藍牙設備進行搜索,該權限的其他能力并不應當被使用,除非你的應用是一個電源管理的應用,需要對藍牙的設定進行修改
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
這里需要注意的是,Google在Android 6.0之后,為了更好的保護用戶的資料安全,所有需要訪問硬體唯一識別符號的地方都需要申請位置權限,而且搜索周圍的藍牙設備,需要手機提供位置服務,否則呼叫藍牙掃描將搜索不到任何結果,
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
在AndroidManifest.xml中加入位置權限,位置權限屬于Dangerous級別的權限,別忘了在代碼里邊還需要動態申請,
2.是否支持藍牙
//獲取藍牙配接器 若配接器為空則當前手機不支持藍牙
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (bluetoothAdapter == null) {
Toast.makeText(this, "當前手機設備不支持藍牙", Toast.LENGTH_SHORT).show()
}
3.是否打開藍牙
(1)判斷藍牙是否打開
//手機設備支持藍牙,判斷藍牙是否已開啟
if (bluetoothAdapter!!.isEnabled) {
Toast.makeText(this, "手機藍牙已開啟", Toast.LENGTH_SHORT).show()
//可以進行搜索藍牙的操作
searchBtDevice()
} else {
//藍牙沒有打開,去打開藍牙,推薦使用第二種打開藍牙方式
//第一種方式:直接打開手機藍牙,沒有任何提示,一般不采用這種方法
// bluetoothAdapter.enable(); //BLUETOOTH_ADMIN權限
//第二種方式:友好提示用戶打開藍牙
val enableBtIntent = Intent(ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
}
(2)友好提示用戶打開藍牙
//友好提示用戶打開藍牙
val enableBtIntent = Intent(ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)

(3)監聽用戶是否打開藍牙的操作回呼
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_ENABLE_BT) {
if (resultCode == Activity.RESULT_OK) {
// 藍牙已打開
//可以開始搜索藍牙 獲取藍牙串列
searchBtDevice()
} else {
// 藍牙未打開 一般操作是彈出確認彈框提示用戶繼續打開藍牙
DialogUtil.getInstance().showConfirmDialog(this, "提示", "藍牙沒有開啟,是否打開藍牙?", "打開",
"取消", object : DialogUtil.DialogConfirmListener<Any> {
override fun cancel() {
//如果用戶選擇取消的話 可以關閉當前頁面或者提示藍牙未打開
finish()
}
override fun confirm(data: Any?) {
//繼續友好提示用戶打開藍牙
val enableBtIntent = Intent(ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
}
})
}
}
}
4.掃描藍牙
藍牙掃描共有兩種方式,一種是適于經典藍牙開發模式的呼叫藍牙配接器 bluetoothAdapter.startDiscovery()搜索藍牙,通過注冊藍牙廣播接收器獲取掃描到的藍牙資訊,這種搜索方法不管是經典藍牙還是低功耗藍牙都能全部掃描出來;另一種方法則是針對低功耗藍牙進行搜索掃描,只有低功耗藍牙模式的藍牙資訊才會被掃描出來,
(1)經典藍牙搜索
① 新建藍牙廣播接收器
public class BroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "BluetoothReceiver";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
LogUtil.logNormalMsg("----藍牙廣播接收器----------action----------" + action);
//開啟搜索
if (TextUtils.equals(action, BluetoothAdapter.ACTION_DISCOVERY_STARTED)) {
if (onDeviceSearchListener != null) {
onDeviceSearchListener.onDiscoveryStart(); //開啟搜索回呼
}
findConnectedBluetooth();
} else if (TextUtils.equals(action, BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
//完成搜素
if (onDeviceSearchListener != null) {
//完成搜素回呼
onDeviceSearchListener.onDiscoveryStop();
}
} else if (TextUtils.equals(action, BluetoothDevice.ACTION_FOUND)) {
//3.0搜索到設備
//藍牙設備
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//信號強度
int rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
LogUtil.logNormalMsg("TAG", "掃描到設備:" + bluetoothDevice.getName() + "-->" + bluetoothDevice.getAddress());
if (onDeviceSearchListener != null) {
//3.0搜素到設備回呼
onDeviceSearchListener.onDeviceFound(bluetoothDevice, rssi);
}
} else if (TextUtils.equals(action, BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
//藍牙已斷開
//藍牙設備
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
LogUtil.logNormalMsg("--------------藍牙已斷開---------------");
if (onDeviceSearchListener != null) {
//判斷是否是當前藍牙連接斷開了
if (bluetoothDevice.getAddress().equals(Content.bluetoothMac)){
onDeviceSearchListener.onDeviceDisconnect();
}
}
} else if (TextUtils.equals(action, BluetoothDevice.ACTION_ACL_CONNECTED)) {
//藍牙已連接
LogUtil.logNormalMsg("--------------藍牙已連接---------------");
if (onDeviceSearchListener != null) {
onDeviceSearchListener.onDeviceConnect();
}
} else if (TextUtils.equals(action, BluetoothDevice.ACTION_PAIRING_REQUEST)) {
//經典藍牙的配對請求 可以用于攔截配對框,在代碼里設定配對碼
}
}
/**
* 藍牙設備搜索監聽者
* 1、開啟搜索
* 2、完成搜索
* 3、搜索到設備
*/
public interface OnDeviceSearchListener {
void onDiscoveryStart(); //開啟搜索
void onDiscoveryStop(); //完成搜索
void onDeviceFound(BluetoothDevice bluetoothDevice, int rssi); //搜索到設備
//設備斷開連接
void onDeviceDisconnect();
//設備連接成功
void onDeviceConnect();
}
private OnDeviceSearchListener onDeviceSearchListener;
public void setOnDeviceSearchListener(OnDeviceSearchListener onDeviceSearchListener) {
this.onDeviceSearchListener = onDeviceSearchListener;
}
}
② 動態注冊藍牙廣播接收器
/**
* 注冊藍牙廣播接收
*/
private fun initBtBroadcast() {
//注冊廣播接收
btBroadcastReceiver = BtBroadcastReceiver()
//這里需要當前的Activity實作(implements)一下藍牙設備搜索監聽者OnDeviceSearchListener
btBroadcastReceiver!!.setOnDeviceSearchListener(this)
val intentFilter = IntentFilter()
//將優先級調高
intentFilter.priority = 1001
//添加Action
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED) //開始掃描
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)//掃描結束
intentFilter.addAction(BluetoothDevice.ACTION_FOUND)//搜索到設備
intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)//斷開連接
intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED)//連接
intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)//藍牙配對請求
registerReceiver(btBroadcastReceiver, intentFilter)
}
② 開啟掃描藍牙
/**
* 開始搜索藍牙
*/
private fun searchBtDevice() {
if (bluetoothAdapter!!.isDiscovering) {
//當前正在搜索設備...停止當前搜索
bluetoothAdapter!!.cancelDiscovery()
}
//開始搜索
bluetoothAdapter!!.startDiscovery()
}
開啟掃描后在藍牙廣播接收器中可以監聽到手機掃描到的藍牙,然后通過藍牙設備搜索監聽者OnDeviceSearchListener方法回呼中拿到藍牙設備資訊,
override fun onDeviceFound(device: BluetoothDevice?, rssi: Int) {
//每掃描到藍牙資訊都會回呼該方法 這里可以拿到藍牙設備資訊和信號強度
}
到這一步的話,開發者可以將掃描到的藍牙設備資訊串列展示,點擊串列即可做藍牙連接的操作,

(2)低功耗藍牙搜索
通過startLeScan()方法開啟掃描,掃描結果直接在BluetoothAdapter.LeScanCallback或者ScanCallback 中回呼處理,不用經典藍牙模式需要注冊廣播才能獲取掃描到的藍牙資訊,這里注意不同版本呼叫startScan方法的邏輯不一樣,目前大多數android版本都在5.0以上,基本上選用后者的掃描方法即可
//1. Android 4.3以上,Android 5.0以下
mBluetoothAdapter.startLeScan(BluetoothAdapter.LeScanCallback LeScanCallback);
//2. Android 5.0以上,掃描的結果在mScanCallback中進行處理
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
mBluetoothLeScanner.startScan(ScanCallback mScanCallback);
回呼處理:
public abstract class ScanCallback {
/**
* Callback when a BLE advertisement has been found.
*
* @param callbackType Determines how this callback was triggered. Could be one of {@link
* ScanSettings#CALLBACK_TYPE_ALL_MATCHES}, {@link ScanSettings#CALLBACK_TYPE_FIRST_MATCH} or
* {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST}
* @param result A Bluetooth LE scan result.
*/
public void onScanResult(int callbackType, ScanResult result) {
//這里可以處理藍牙掃描結果
BluetoothDevice bluetoothDevice = result.getDevice();
}
/**
* Callback when batch results are delivered.
*
* @param results List of scan results that are previously scanned.
*/
public void onBatchScanResults(List<ScanResult> results) {
}
/**
* Callback when scan could not be started.
*
* @param errorCode Error code (one of SCAN_FAILED_*) for scan failure.
*/
public void onScanFailed(int errorCode) {
}
}
如果只是涉及到低功耗藍牙的技術開發,選擇第二種方法掃描藍牙就可以了,比較簡便,反之選擇第一種方法,
5.藍牙連接與斷開
(1)經典藍牙(BT)
APP與經典藍牙設備連接一般通過BluetoothDevice物件的createRfcommSocketToServiceRecord方法獲取BluetoothSocket,類似Socket編程,呼叫connect()方法開始連接,連接成功以后通過BluetoothSocket的getInputStream()和getOutputStream()方法獲取輸入流和輸出流,進而與藍牙進行通信,createRfcommSocketToServiceRecord方法需要傳入UUID引數,一般通過需要連接的藍牙設備引數得知,沒法獲取UUID也沒關系,可以通過反射機制得到BluetoothSocket物件,再進行連接,這里需要注意藍牙連接是一個耗時的程序,不能在主執行緒中執行,
① createRfcommSocketToServiceRecord獲取BluetoothSocket
//1、獲取BluetoothSocket
try {
//建立安全的藍牙連接,會彈出配對框
mSocket = mDevice.createRfcommSocketToServiceRecord(UUID.fromString(uuid));
} catch (IOException e) {
e.printStackTrace();
LogUtil.logNormalMsg(TAG, "獲取BluetoothSocket例外!" + e.getMessage());
}
② 反射獲取BluetoothSocket
//1、獲取BluetoothSocket
try {
//通過反射獲取Socket
mSocket = (BluetoothSocket) mDevice.getClass().
getMethod("createRfcommSocket", new Class[]{int.class})
.invoke(mDevice, 1);
} catch (IOException e) {
e.printStackTrace();
LogUtil.logNormalMsg(TAG, "獲取BluetoothSocket例外!" + e.getMessage());
}
③ 連接藍牙
獲取BluetoothSocket后就可以與藍牙設備連接了,呼叫connect()連接方法后一般手機就會彈出藍牙配對框,有一些藍牙設備不會出現彈框因為藍牙模塊沒有設定配對碼,這里只針對有配對碼的藍牙模塊,手動輸入配對碼后就成功連接了,一般藍牙開發設備的配對碼是0000或者1234,在考慮用戶體驗的時候,一般不讓用戶自己輸入配對碼,這一步操作顯得繁瑣多余,APP開發時一般都考慮在連接時靜默設定藍牙的配對碼,在用戶無感知的情況下進行連接和配對操作,做這一步操作就需要開發者攔截藍牙配對的Action (BluetoothDevice.ACTION_PAIRING_REQUEST),在代碼中進行配對處理,在BroadcastReceiver 藍牙廣播接收器中增加對藍牙配對請求的攔截處理,
mSocket.connect();
④ 靜默設定配對碼
if (TextUtils.equals(action, BluetoothDevice.ACTION_PAIRING_REQUEST)) {
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//藍牙配對
Log.i(TAG, "##### 是配對請求的請求 ######");
Bundle extras = intent.getExtras();
Log.i(TAG, "-->" + extras.toString());
Object device = extras.get("android.bluetooth.device.extra.DEVICE");
Object pairKey = BaseData.bluetoothPassword;
Log.i(TAG, "device-->" + String.valueOf(device));
Log.i(TAG, "pairkey-->" + String.valueOf(pairKey));
try {
//中斷配對廣播傳遞 如果沒有將廣播終止,則會出現一個一閃而過的配對框,
abortBroadcast();
//呼叫setPin方法進行配對...
setPin(btDevice.getClass(), btDevice, String.valueOf(pairKey));
} catch (Exception e) {
e.printStackTrace();
}
}
記得在動態注冊藍牙廣播接收器的時候添加一下藍牙配對請求的Action
//藍牙配對請求Action
intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)
設定配對碼
/**
* 設定藍牙配對碼
* @param btClass btClass
* @param btDevice BluetoothDevice
* @param str str
* @return boolean
*/
public boolean setPin(Class btClass, BluetoothDevice btDevice, String str) {
boolean flag = false;
try {
Class[] arrayOfClass = new Class[1];
arrayOfClass[0] = byte[].class;
Method removeBondMethod = btClass.getDeclaredMethod("setPin", arrayOfClass);
Object[] arrayOfObject = new Object[1];
arrayOfObject[0] = str.getBytes();
flag = (Boolean) removeBondMethod.invoke(btDevice, arrayOfObject);
LogUtil.logNormalMsg(TAG, "setPin result: " + flag);
} catch (Exception e) {
e.printStackTrace();
}
if (flag) {
LogUtil.logNormalMsg("-------設定藍牙配對碼成功");
} else {
LogUtil.logNormalMsg("-------設定藍牙配對碼失敗");
}
return flag;
}
這里在貼一下我自己藍牙連接的代碼:
public class ConnectThread {
private static final String TAG = "ConnectThread";
private final BluetoothAdapter mBluetoothAdapter;
private BluetoothSocket mmSocket;
private final BluetoothDevice mDevice;
public ConnectThread(BluetoothAdapter bluetoothAdapter, BluetoothDevice bluetoothDevice, String uuid) {
this.mBluetoothAdapter = bluetoothAdapter;
this.mDevice = bluetoothDevice;
//使用一個臨時變數,等會賦值給mmSocket
//因為mmSocket是靜態的
BluetoothSocket tmp = null;
if (mmSocket != null) {
LogUtil.logNormalMsg(TAG, "ConnectThread-->mmSocket != null先去釋放");
try {
mmSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
LogUtil.logNormalMsg(TAG, "ConnectThread-->mmSocket != null已釋放");
//1、獲取BluetoothSocket
try {
//建立安全的藍牙連接,會彈出配對框
tmp = mDevice.createRfcommSocketToServiceRecord(UUID.fromString(uuid));
} catch (IOException e) {
e.printStackTrace();
LogUtil.logNormalMsg(TAG, "ConnectThread-->獲取BluetoothSocket例外!" + e.getMessage());
}
mmSocket = tmp;
if (mmSocket != null) {
LogUtil.logNormalMsg(TAG, "ConnectThread-->已獲取BluetoothSocket");
}
}
public void connectBluetooth() {
connect();
}
private void connect() {
//連接之前先取消發現設備,否則會大幅降低連接嘗試的速度,并增加連接失敗的可能性
if (mBluetoothAdapter == null) {
LogUtil.logNormalMsg(TAG, "ConnectThread:run-->mBluetoothAdapter == null");
return;
}
//取消發現設備
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
if (mmSocket == null) {
LogUtil.logNormalMsg(TAG, "ConnectThread:run-->mmSocket == null");
return;
}
boolean firstFlag = false;
//2、通過socket去連接設備
try {
LogUtil.logNormalMsg(TAG, "ConnectThread:run-->去連接...");
if (onBluetoothConnectListener != null) {
onBluetoothConnectListener.onStartConn(); //開始去連接回呼
}
mmSocket.connect();
firstFlag = true;
} catch (Exception e) {
LogUtil.logNormalMsg(TAG, "ConnectThread:run-->第一次連接例外!" + e.getMessage());
boolean flag = false;
try {
LogUtil.logNormalMsg(TAG, "ConnectThread:run-->嘗試第二次通過反射獲取Socket連接");
mmSocket = (BluetoothSocket) mDevice.getClass().
getMethod("createRfcommSocket", new Class[]{int.class})
.invoke(mDevice, 1);
mmSocket.connect();
flag = true;
Log.e(TAG, "mmSocket.isConnected():" + mmSocket.isConnected());
} catch (Exception e2) {
LogUtil.logNormalMsg("mmSocket連接失敗:" + e.getMessage());
e2.printStackTrace();
LogUtil.logNormalMsg(TAG, "ConnectThread:run-->連接例外2!" + e2.getMessage());
if (onBluetoothConnectListener != null) {
onBluetoothConnectListener.onConnFailure("連接例外:" + e.getMessage());
}
//釋放
cancel();
} finally {
if (flag) {
if (onBluetoothConnectListener != null) {
//連接成功回呼
onBluetoothConnectListener.onConnSuccess(mmSocket);
LogUtil.logNormalMsg(TAG, "ConnectThread:run-->連接成功");
}
} else {
if (onBluetoothConnectListener != null) {
//連接失敗回呼
onBluetoothConnectListener.onConnFailure("");
}
}
}
} finally {
if (firstFlag) {
if (onBluetoothConnectListener != null) {
//連接成功回呼
onBluetoothConnectListener.onConnSuccess(mmSocket);
LogUtil.logNormalMsg(TAG, "ConnectThread:run-->連接成功");
}
}
}
}
/**
* 釋放
*/
public void cancel() {
try {
if (mmSocket != null && mmSocket.isConnected()) {
LogUtil.logNormalMsg(TAG, "ConnectThread:cancel-->mmSocket.isConnected() = " + mmSocket.isConnected());
mmSocket.close();
mmSocket = null;
return;
}
if (mmSocket != null) {
mmSocket.close();
mmSocket = null;
}
LogUtil.logNormalMsg(TAG, "ConnectThread:cancel-->關閉已連接的套接字釋放資源");
} catch (IOException e) {
LogUtil.logNormalMsg(TAG, "ConnectThread:cancel-->關閉已連接的套接字釋放資源例外!" + e.getMessage());
}
}
private OnBluetoothConnectListener onBluetoothConnectListener;
public void setOnBluetoothConnectListener(OnBluetoothConnectListener onBluetoothConnectListener) {
this.onBluetoothConnectListener = onBluetoothConnectListener;
}
//連接狀態監聽者
public interface OnBluetoothConnectListener {
void onStartConn(); //開始連接
void onConnSuccess(BluetoothSocket bluetoothSocket); //連接成功
void onConnFailure(String errorMsg); //連接失敗
}
}
藍牙連接成功后,就可通過BluetoothSocket 的輸入輸出流和藍牙設備進行通信了,
需要注意的是,靜默設定藍牙配對碼一般需要和藍牙設備廠家協商一個固定的配對碼,不然藍牙配對碼一直改變的話會導致APP連接不上藍牙而且沒有任何錯誤提示,
⑤ 斷開連接
斷開經典藍牙連接其實就是關閉BluetoothSocket和輸入輸出流,這里注意需要先把輸入輸出流關閉再將BluetoothSocket關閉,
try {
if(mInStream != null){
mInStream.close(); //關閉輸入流
}
if(mOutStream != null){
mOutStream.close(); //關閉輸出流
}
if(mSocket != null){
mSocket.close(); //關閉socket
}
mInStream = null;
mOutStream = null;
mmSocket = null;
LogUtil.logNormalMsg(TAG,"成功斷開連接");
} catch (Exception e) {
e.printStackTrace();
// 任何一部分報錯,都將強制關閉socket連接
mInStream = null;
mOutStream = null;
mmSocket = null;
LogUtil.logNormalMsg(TAG, "斷開連接例外!" + e.getMessage());
}
(2)低功耗藍牙(BLE)
低功耗藍牙的連接稍微復雜一點:

連接之前
在連接藍牙這之前需要對GATT了解一下,方便加深我們對低功耗藍牙開發技術的理解,
GATT層次結構

該層次結構的頂層是一個 profile(概要檔案),profile 由滿足 use case(用例)所需的一個或多個 services(服務)組成,services(服務)由對其他服務的 characteristics(特征)或 references(參考)組成,每個 characteristics(特征)都包含一個值,并且可以包含關于該值的可選資訊,service(服務)和 characteristic(特性)以及 characteristic(特性)的組成部分(即、值和 descriptors(描述符))包含 profile data,并且都存盤在 server(服務器)上的 Attributes(屬性)中,
各成員作用
1、Profile
Profile規格規定了交換組態檔資料的架構,此架構定義了組態檔所用的基本元素,例如服務和特征,該層級的最高層是組態檔(profile),組態檔由實作用例所需的一個或多個服務組成,服務由特征或有關其它服務的參考組成,每一個特征包括一個值,還可能包括有關該值的可選資訊,服務、特征以及特征的組件(即特征值和特征描述符)構成了組態檔資料,并全部存盤在服務器的屬性中,沒錯,上面這是摘抄的,說簡單點就是協議規范,各個藍牙兼容都靠這個規范,使用標準的Profile就不需要過多關注它了,
2、Service
服務分管不同的資料處理,例如有電池電量的,有心率的,它不參與具體的資料互動,承擔的是功能磁區的職責,每個服務都有一個UUID進行區分,
3、Characteristic
特征是直接參與資料互動的成員之一,特征也有一個UUID進行標識,通過UUID獲取到特征,修改特征里面的Value達到通訊目的,這樣的通訊模式和觀察者模式相似,當Value有改變的時候會通知監聽這個特征的觀察者,告訴它資料有改變,觀察者去獲取資料就完成了一次通訊,具體的實作往后詳述,
4、Descriptor
對特征值的描述,是用來定義特征值的已定義屬性,例如:描述符可以指定可讀的描述,特征值的可接受范圍或者特征值特定的度量單位,這個東西用的不是很多,假如中心設備收不到資料,那么就看看是不是Descriptor沒有設定可通知,
5、Advertising
廣播,外圍設備讓自身可以被發現的手段,只有發出廣播了,才能通過Ble掃描到,一旦設備連接上,廣播就會停止,
低功耗藍牙通過BluetoothGatt進行連接和管理,通過BluetoothDevice的connectGatt()方法可以得到BluetoothGatt物件,其中需要傳入三個引數context背景關系,autoConnect是否自動連接,callback連接回呼,關鍵在于第三個引數BluetoothGattCallback,因為開發者需要在BluetoothGattCallback回呼中發現服務和配置通信管道,同時可以在其中監聽下位機(藍牙模塊)回復的訊息,這里需要注意的是,獲取到GATT中的服務Service、特征Characteristic和特征值描述Descriptor都需要指定UUID,這些UUID一般從藍牙模塊廠家獲取,獲取通過代碼方式遍歷所有的UUID實驗出正確的UUID,后續會講到,
connectGatt()方法原始碼:
/**
* Connect to GATT Server hosted by this device. Caller acts as GATT client.
* The callback is used to deliver results to Caller, such as connection status as well
* as any further GATT client operations.
* The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct
* GATT client operations.
*
* @param callback GATT callback handler that will receive asynchronous callbacks.
* @param autoConnect Whether to directly connect to the remote device (false) or to
* automatically connect as soon as the remote device becomes available (true).
* @throws IllegalArgumentException if callback is null
*/
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback) {
return (connectGatt(context, autoConnect, callback, TRANSPORT_AUTO));
}
① 設定自定義回呼
/**
* 低功耗藍牙連接回呼
* 接收藍牙資料
*/
inner class MyBluetoothGattCallback : BluetoothGattCallback() {
override fun onConnectionStateChange(
gatt: BluetoothGatt,
status: Int,
newState: Int
) {
super.onConnectionStateChange(gatt, status, newState)
Content.isConnectBluetoothNow = false
//連接成功 開始發現服務
when {
newState == BluetoothAdapter.STATE_CONNECTED -> {
//到這一步 gatt已經連接成功了 可以呼叫發現gatt的服務
mGatt?.discoverServices()
}
newState == BluetoothAdapter.STATE_DISCONNECTED -> {
//連接斷開
}
status == 133 -> {
//連接失敗
}
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
super.onServicesDiscovered(gatt, status)
if (status == BluetoothGatt.GATT_SUCCESS) {
//到這一步 已經發現了gatt的所有服務 可以通過gatt.services獲取
//如果我們知道gatt相關的所有UUID 則可以開始配置通信管道
//獲取寫入的特征值
writeCharact = this.bluetoothGattService?.getCharacteristic(writeUUID)
//獲取通知監聽的特征值 相當于讀取監聽 開啟監聽后可實時監聽下位機訊息
notifyCharact = this.bluetoothGattService?.getCharacteristic(notifyUUID)
//設定開啟監聽 實時監聽下位機訊息
//1.設定特征值通知
mGatt?.setCharacteristicNotification(notifyCharact , true)
//2.獲取descriptor
val descriptor: BluetoothGattDescriptor = notifyCharact ?.getDescriptor(descriptorUUID)!!
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
//3.設定descriptor 這里設定結果會在onDescriptorWrite方法中回呼
mGatt?.writeDescriptor(descriptor)
//至此低功耗藍牙連接和配置完成
}
}
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
super.onCharacteristicRead(gatt, characteristic, status)
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
super.onCharacteristicWrite(gatt, characteristic, status)
val value = characteristic.value
val data = Util.bytesToAscii(value)
//發送資料成功后會回呼該方法
LogUtil.logNormalMsg("onCharacteristicWrite", "發送成功:$data")
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
super.onCharacteristicChanged(gatt, characteristic)
// value為設備發送的資料,根據資料協議進行決議
//接收處理資料
handBluetoothData(characteristic.value)
}
override fun onDescriptorRead(
gatt: BluetoothGatt,
descriptor: BluetoothGattDescriptor,
status: Int
) {
super.onDescriptorRead(gatt, descriptor, status)
}
override fun onDescriptorWrite(
gatt: BluetoothGatt,
descriptor: BluetoothGattDescriptor,
status: Int
) {
super.onDescriptorWrite(gatt, descriptor, status)
if (status == BluetoothGatt.GATT_SUCCESS) {
LogUtil.logNormalMsg("LockBluetoothService", "設定Descriptor成功,可以發送資料了")
//一些藍牙模塊需要在連接成功后設定mtu 根據開發需要配置
mGatt?.requestMtu(103)
}
}
}
② 啟動藍牙連接
啟動藍牙藍牙連接后,通過connectGatt方法得到BluetoothGatt物件,連接結果可在上一步自定義的BluetoothGattCallback中回呼處理,
mGatt = bluetoothDevice1!!.connectGatt(appContext, false, bluetoothGattCallback)
③ 發現GATT服務
這一步操作需在連接成功后執行,發現服務后才可配置藍牙通信
mGatt?.discoverServices()
④ 配置通信
獲取BluetoothGattService服務和用于讀寫的BluetoothGattCharacteristic特征值,配置藍牙通信,
//獲取寫入的特征值
writeCharact = this.bluetoothGattService?.getCharacteristic(writeUUID)
//獲取通知監聽的特征值 相當于讀取監聽 開啟監聽后可實時監聽下位機訊息
notifyCharact = this.bluetoothGattService?.getCharacteristic(notifyUUID)
//設定開啟監聽 實時監聽下位機訊息
//1.設定特征值通知
mGatt?.setCharacteristicNotification(notifyCharact , true)
//2.獲取descriptor
val descriptor: BluetoothGattDescriptor = notifyCharact ?.getDescriptor(descriptorUUID)!!
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
//3.設定descriptor 這里設定結果會在onDescriptorWrite方法中回呼
mGatt?.writeDescriptor(descriptor)
//至此低功耗藍牙連接和配置完成 可以進行通信了
一般藍牙開發廠商都會告知相關的UUID,如果不知道藍牙模塊的各UUID,可以通過遍歷Gatt的所有服務查找,直至找到正確的UUID,
/**
* 查找低功耗藍牙uuid
*/
private fun findUUID(gatt: BluetoothGatt) {
LogUtil.logNormalMsg("gatt.getServices().size():" + gatt.services.size)
if (gatt.services != null && gatt.services.size > 0) {
//遍歷所有服務
for (i in gatt.services.indices) {
val service = gatt.services[i]
LogUtil.logNormalMsg("---------------------------------------------")
//輸出當前服務的UUID
LogUtil.logNormalMsg("service.getUuid=" + service.uuid)
//判斷當前服務是否有特征值
if (service.characteristics != null && service.characteristics.size > 0) { //遍歷當前所有服務的特征值
for (j in service.characteristics.indices) {
val characteristic =
service.characteristics[j]
//輸出當前特征值的UUID
LogUtil.logNormalMsg("characteristic.getUuid=" + characteristic.uuid)
//獲取特征值屬性
val charaProp = characteristic.properties
//是否支持讀取
if (charaProp or BluetoothGattCharacteristic.PROPERTY_READ > 0) {
LogUtil.logNormalMsg("-------type:PROPERTY_READ")
}
//是否支持寫入
if (charaProp or BluetoothGattCharacteristic.PROPERTY_WRITE > 0) {
LogUtil.logNormalMsg("-------type:PROPERTY_WRITE")
}
//是否支持開啟監聽
if (charaProp or BluetoothGattCharacteristic.PROPERTY_NOTIFY > 0) {
LogUtil.logNormalMsg("-------type:PROPERTY_NOTIFY")
}
if (characteristic.descriptors != null && characteristic.descriptors.size > 0) {
for (descriptor in characteristic.descriptors) {
LogUtil.logNormalMsg("descriptor.getUuid()=" + descriptor.uuid)
}
}
}
}
}
}
}
這里注意要找全面一點的服務,有些服務下沒有特征,有些特征不支持讀寫,有些特征下沒有描述,無法開啟讀取監聽,

⑤ 斷開連接
mGatt?.let {
//斷開BluetoothGatt連接
it.disconnect()
//關閉BluetoothGatt
it.close()
}
6.通信
(1)經典藍牙通信
根據5中提到的連接邏輯,可以得到BluetoothSocket,類似Socket編程,通過BluetoothSocket的到輸入輸出流與藍牙模塊進行資料通信,這里建議另起執行緒進行通信操作,需要實時監聽InputStream輸入流里邊是否有資料可讀取,
① 獲取輸入輸出流
//獲取 InputStream 和 OutputStream
try {
mInStream = socket.getInputStream();
mOutStream = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
LogUtil.logNormalMsg(TAG,"獲取InputStream 和 OutputStream例外!");
}
② 發送資料
//發送資料
public boolean write(byte[] bytes){
try {
if(mOutStream == null){
LogUtil.logNormalMsg(TAG, "mmOutStream == null");
return false;
}
//發送資料
mOutStream.write(bytes);
mOutStream.flush();
Log.d(TAG, "寫入成功:"+ bytes2HexString(bytes, bytes.length));
return true;
} catch (IOException e) {
LogUtil.logNormalMsg("發送資料出錯:"+e.getMessage());
e.printStackTrace();
return false;
}
}
③ 讀取資料
//執行緒的run方法
@Override
public void run(){
//最大快取區 存放流
byte[] buffer = new byte[1024 * 2]; //buffer store for the stream
//從流的read()方法中讀取的位元組數
int bytes = 0; //bytes returned from read()
//持續監聽輸入流直到發生例外
while(!isStop){
try {
if(mInStream == null){
LogUtil.logNormalMsg(TAG,"ConnectedThread:run-->輸入流mmInStream == null");
break;
}
//先判斷是否有資料,有資料再讀取
if(mInStream.available() != 0){
//從(mmInStream)輸入流中(讀取內容)讀取的一定數量位元組數,并將它們存盤到緩沖區buffer陣列中,bytes為實際讀取的位元組數
bytes = mInStream.read(buffer);
LogUtil.logNormalMsg(TAG,"讀取資料長度:"+bytes);
//存放實際讀取的資料內容
byte[] b = Arrays.copyOf(buffer,bytes);
//處理藍牙資料
handBluetoothData(b);
}
Thread.sleep(150);
} catch (Exception e) {
LogUtil.logNormalMsg(TAG,"接收訊息例外!" + e.getMessage());
//關閉流和socket
boolean isClose = cancel();
if(isClose){
LogUtil.logNormalMsg(TAG,"接收訊息例外,成功斷開連接!");
}
break;
}
}
//關閉流和socket
boolean isClose = cancel();
if(isClose){
Log.d(TAG,"接收訊息結束,斷開連接!");
}
}
(2)低功耗藍牙通信
低功耗藍牙通信較為簡單一些,直接通過寫入的特征值Characteristic就可發送資料,而讀取資料在BluetoothGattCallback的
onCharacteristicChanged回呼方法中監聽讀取藍牙設備發送過來的資料,
① 發送資料
fun sendMsgToBluetooth(msg: ByteArray): Boolean {
if (writeCharact != null) {
//設定特征值的value
writeCharact?.value = msg
//設定特征值的寫入型別 這里根據需要選擇型別 詳細可閱讀原始碼
writeCharact?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
//通過gatt寫入資料
return mGatt!!.writeCharacteristic(writeCharact)
}
return false
}
② 讀取資料
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
super.onCharacteristicChanged(gatt, characteristic)
//接收處理資料
handBluetoothData(characteristic.value)
}
7.補充
(1)在多次除錯中發現,在經典藍牙模式開發中,有兩種情況會掃描不到藍牙設備:
- APP內斷開藍牙連接,再次掃描藍牙或是重啟APP再次掃描;
- 再啟動APP時,藍牙設備已與手機APP配對連接;
出現這種情況不要慌,只要把與手機藍牙連接的藍牙設備找到并加入到掃描的藍牙串列中,重新點擊藍牙連接就可以解決,
搜索已與手機連接的藍牙:
/**
* 搜索已連接的藍牙
*/
private void findConnectedBluetooth() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
Class<BluetoothAdapter> bluetoothAdapterClass = BluetoothAdapter.class;//得到BluetoothAdapter的Class物件
try {//得到連接狀態的方法
Method method = bluetoothAdapterClass.getDeclaredMethod("getConnectionState", (Class[]) null);
//打開權限
method.setAccessible(true);
int state = (int) method.invoke(adapter, (Object[]) null);
if(state == BluetoothAdapter.STATE_CONNECTED){
LogUtil.logNormalMsg("BLUETOOTH","BluetoothAdapter.STATE_CONNECTED");
Set<BluetoothDevice> devices = adapter.getBondedDevices();
LogUtil.logNormalMsg("BLUETOOTH","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){
//找到已連接的藍牙設備 進行下一步處理
if (device != null) {
onDeviceSearchListener.onDeviceFound(device, 50);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
(2)低功耗藍牙GATT的默認MTU支持發送的資料量為23位元組(byte), 除去GATT的opcode一個位元組以及GATT的handle 2個位元組之后,剩下的20個位元組便是留給GATT的了,GATT默認支持最大512位元組的資料通信,可以通過設定MTU的大小來改變,一般在藍牙連接成功后設定,這里推薦在onDescriptorWrite回呼方法中配置:
override fun onDescriptorWrite(
gatt: BluetoothGatt,
descriptor: BluetoothGattDescriptor,
status: Int
) {
super.onDescriptorWrite(gatt, descriptor, status)
if (status == BluetoothGatt.GATT_SUCCESS) {
//藍牙連接配置通信成功 可以設定mtu了
mGatt?.requestMtu(103)
}
}
設定MTU是否的結果在onMtuChanged中可以得到
override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
super.onMtuChanged(gatt, mtu, status)
if (status == BluetoothGatt.GATT_SUCCESS) {
//MTU設定成功 回傳當前所支持的mtu大小
this.supportedMTU = mtu
}
}
參考
安卓BLE藍牙開發詳解
Android 從開發角度來看經典藍牙和低功耗(BLE)藍牙的區別
Android連接經典藍牙
Android 藍牙BLE開發詳解
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/286800.html
標籤:其他
