開啟權限
AndroidManifest中定義權限
<!-- 掃描藍牙設備或者操作藍牙設置 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- 使用藍牙的權限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<!--精準定位權限,作用于Android6.0+ -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--模糊定位權限,作用于Android6.0+ -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!--以下三個是Android12中新增,作用于Android12.0+ -->
<!-- Android 12在不申請定位權限時,必須加上android:usesPermissionFlags="neverForLocation",否則搜不到設備 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" tools:targetApi="s"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
動態(tài)申請權限
private fun initPermission() {
val permissionList: ArrayList<String> = ArrayList<String>()
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
// Android 版本大于等于 Android12 時
permissionList.add(Manifest.permission.BLUETOOTH_SCAN)
permissionList.add(Manifest.permission.BLUETOOTH_ADVERTISE)
permissionList.add(Manifest.permission.BLUETOOTH_CONNECT)
} else {
// Android 版本小于 Android12 及以下版本
permissionList.add(Manifest.permission.ACCESS_COARSE_LOCATION)
permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION)
}
ActivityCompat.requestPermissions(this,permissionList.toArray(emptyArray()),1000)
}
開啟藍牙
如果藍牙沒有開啟,跳轉到系統(tǒng)藍牙設置界面,打開藍牙:
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (!bluetoothAdapter.isEnabled()) {
val intent = Intent(Settings.ACTION_BLUETOOTH_SETTINGS)
context.startActivity(intent)
}
開啟藍牙狀態(tài)監(jiān)聽
private var adapter: BluetoothTerminalAdapter?=null
private var list:MutableList<BluetoothDevice> = mutableListOf()
private var broadcastReceiver: BluetoothBroadcastReceive?=null
fun registerReceiver() {
if (broadcastReceiver == null) {
broadcastReceiver = BluetoothBroadcastReceive()
val intent = IntentFilter()
intent.addAction(BluetoothDevice.ACTION_FOUND) // 用BroadcastReceiver來取得搜索結果
intent.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
intent.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
intent.addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
intent.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
intent.addAction(BluetoothDevice.ACTION_NAME_CHANGED)
intent.addAction("android.bluetooth.BluetoothAdapter.STATE_OFF")
intent.addAction("android.bluetooth.BluetoothAdapter.STATE_ON")
intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
requireContext().registerReceiver(broadcastReceiver, intent)
}
}
fun stopDiscovery() {
BluetoothAdapter.getDefaultAdapter().cancelDiscovery()
}
fun onBluetoothConnect(device: BluetoothDevice) {
//連接成功后的動作,比如更新UI,開始FTP通信等
......
}
fun onBluetoothDisConnect() {
//連接失敗后的動作,比如更新UI,
}
inner class BluetoothBroadcastReceive : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val action = intent?.action
val device = intent?.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
when (action) {
BluetoothDevice.ACTION_FOUND -> {
if (device != null)){
if (!list.contains(device)) {
list.add(device)
adapter?.notifyDataSetChanged()
}
}
}
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
stopDiscovery()
}
BluetoothDevice.ACTION_ACL_CONNECTED -> {
//連接后還需要配對,配對成功才能通信
if (device != null){
adapter?.notifyDataSetChanged()
}
}
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
if (device != null){
//如果連接失敗或者連接成功,但是沒有配對,則會調用連接失敗
onBluetoothDisConnect()
adapter?.notifyDataSetChanged()
}
}
BluetoothAdapter.ACTION_STATE_CHANGED -> {
val blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0)
when (blueState) {
BluetoothAdapter.STATE_OFF -> {
stopDiscovery()
onBluetoothDisConnect()
}
}
}
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
val state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1)
when (state) {
BluetoothDevice.BOND_NONE -> {adapter?.notifyDataSetChanged()}
BluetoothDevice.BOND_BONDED -> {
if (device != null){
adapter?.notifyDataSetChanged()
//配對成功才能通訊,所以只有配對成功才表示真正的連接成功
onBluetoothConnect(device)
}
}
}
}
BluetoothDevice.ACTION_NAME_CHANGED -> {
for(i in 0 until list.size) {
if (list[i].address == device?.address) {
if (device != null) {
list[i] = device
adapter?.notifyDataSetChanged()
}
break
}
}
}
}
}
}
開啟連接與配對
說明一下,原始的實現(xiàn)方式是在系統(tǒng)藍牙設置界面進行配對,在廣播收到“BluetoothDevice.ACTION_ACL_CONNECTED”后,開始調用“bluetoothDevice.createRfcommSocketToServiceRecord(SPP_UUID)”進行SPP通信,但是此方式對有的藍牙服務端的程序不啟作用,可采用的是另一種方式:
需要進行通信的bluetoothDevice,直接調用createRfcommSocketToServiceRecord函數(shù):
companion object{
//藍牙串口服務 (SPP) 的 UUID
val SPP_UUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
}
private var bluetoothSocket:BluetoothSocket? = null
private var inputStream: InputStream? = null // 用來收數(shù)據(jù)
private var outputStream: OutputStream? = null // 用來發(fā)數(shù)據(jù)
fun startReadyConnect():Boolean {
//需要在線程中連接,否則超時會引起ANR
val coroutineScope = CoroutineScope(Dispatchers.IO)
coroutineScope?.launch {
//有個工具類BluetoothUtils,里面保存了等待連接的藍牙設備對象
var bluetoothDevice = BluetoothUtils.getWaitingForConnectionDevice()
bluetoothDevice?.let {
try {
//通過createRfcommSocketToServiceRecord函數(shù)調用,這會引起藍牙底層的配對連接動作
//如果連接成功,系統(tǒng)會自動彈出配對對話框,需要用戶在一定時間里完成配對
//如果在一定時間里配對成功,則返回true,否則不配對或者取消配對,都會引起異常
bluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(SPP_UUID)
bluetoothSocket?.connect()
inputStream = bluetoothSocket?.inputStream
outputStream = bluetoothSocket?.outputStream
if (inputStream != null && outputStream != null) {
BluetoothUtils.waitingForConnectionDevice = null
BluetoothUtils.connectedDevice = it
return true
}
} catch (e:Exception) {
e.printStackTrace()
}
}
BluetoothUtils.waitingForConnectionDevice = null
BluetoothUtils.connectedDevice = null
BluetoothUtils.bluetoothDisConnect()
return false
}
}
發(fā)送數(shù)據(jù)
//在子線程里運行,比如先將數(shù)據(jù)放在LinkedBlockingQueue中,子線程while循環(huán)不斷從LinkedBlockingQueue中取出數(shù)據(jù)
//調用writeBytes函數(shù)發(fā)送出去
fun writeBytes(bytes: ByteArray) {
if (isRun){
try {
outputStream?.write(bytes)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
接收數(shù)據(jù)
//在子線程中的while循環(huán)里運行
fun read(): ByteArray? {
val num = inputStream?.read(buffer)?:0
if (num > 0) {
val readBytes = ByteArray(num)
System.arraycopy(buffer,0,readBytes,0,num)
return readBytes
}
return null
}
總結
1.Android不同版本,藍牙權限需要適配,分為6.0之前,6.0至11.0之間、12.0及之后,三類不同版本的適配
2.SPP通信前需要先進行藍牙配對與連接
3.藍牙配對操作針對實際情況采用系統(tǒng)藍牙設置里進行配對或者采用調用Create Socket通信接口自動觸發(fā)配對
4.有些操作需要在子線程中進行,防止ANR