開(kāi)啟權(quán)限
AndroidManifest中定義權(quán)限
<!-- 掃描藍(lán)牙設(shè)備或者操作藍(lán)牙設(shè)置 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- 使用藍(lán)牙的權(quán)限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<!--精準(zhǔn)定位權(quán)限,作用于Android6.0+ -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--模糊定位權(quán)限,作用于Android6.0+ -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!--以下三個(gè)是Android12中新增,作用于Android12.0+ -->
<!-- Android 12在不申請(qǐng)定位權(quán)限時(shí),必須加上android:usesPermissionFlags="neverForLocation",否則搜不到設(shè)備 -->
<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" />
動(dòng)態(tài)申請(qǐng)權(quán)限
private fun initPermission() {
val permissionList: ArrayList<String> = ArrayList<String>()
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
// Android 版本大于等于 Android12 時(shí)
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)
}
開(kāi)啟藍(lán)牙
如果藍(lán)牙沒(méi)有開(kāi)啟,跳轉(zhuǎn)到系統(tǒng)藍(lán)牙設(shè)置界面,打開(kāi)藍(lán)牙:
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (!bluetoothAdapter.isEnabled()) {
val intent = Intent(Settings.ACTION_BLUETOOTH_SETTINGS)
context.startActivity(intent)
}
開(kāi)啟藍(lán)牙狀態(tài)監(jiān)聽(tīng)
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來(lái)取得搜索結(jié)果
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) {
//連接成功后的動(dòng)作,比如更新UI,開(kāi)始FTP通信等
......
}
fun onBluetoothDisConnect() {
//連接失敗后的動(dòng)作,比如更新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 -> {
//連接后還需要配對(duì),配對(duì)成功才能通信
if (device != null){
adapter?.notifyDataSetChanged()
}
}
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
if (device != null){
//如果連接失敗或者連接成功,但是沒(méi)有配對(duì),則會(huì)調(diào)用連接失敗
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()
//配對(duì)成功才能通訊,所以只有配對(duì)成功才表示真正的連接成功
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
}
}
}
}
}
}
開(kāi)啟連接與配對(duì)
說(shuō)明一下,原始的實(shí)現(xiàn)方式是在系統(tǒng)藍(lán)牙設(shè)置界面進(jìn)行配對(duì),在廣播收到“BluetoothDevice.ACTION_ACL_CONNECTED”后,開(kāi)始調(diào)用“bluetoothDevice.createRfcommSocketToServiceRecord(SPP_UUID)”進(jìn)行SPP通信,但是此方式對(duì)有的藍(lán)牙服務(wù)端的程序不啟作用,可采用的是另一種方式:
需要進(jìn)行通信的bluetoothDevice,直接調(diào)用createRfcommSocketToServiceRecord函數(shù):
companion object{
//藍(lán)牙串口服務(wù) (SPP) 的 UUID
val SPP_UUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
}
private var bluetoothSocket:BluetoothSocket? = null
private var inputStream: InputStream? = null // 用來(lái)收數(shù)據(jù)
private var outputStream: OutputStream? = null // 用來(lái)發(fā)數(shù)據(jù)
fun startReadyConnect():Boolean {
//需要在線程中連接,否則超時(shí)會(huì)引起ANR
val coroutineScope = CoroutineScope(Dispatchers.IO)
coroutineScope?.launch {
//有個(gè)工具類BluetoothUtils,里面保存了等待連接的藍(lán)牙設(shè)備對(duì)象
var bluetoothDevice = BluetoothUtils.getWaitingForConnectionDevice()
bluetoothDevice?.let {
try {
//通過(guò)createRfcommSocketToServiceRecord函數(shù)調(diào)用,這會(huì)引起藍(lán)牙底層的配對(duì)連接動(dòng)作
//如果連接成功,系統(tǒng)會(huì)自動(dòng)彈出配對(duì)對(duì)話框,需要用戶在一定時(shí)間里完成配對(duì)
//如果在一定時(shí)間里配對(duì)成功,則返回true,否則不配對(duì)或者取消配對(duì),都會(huì)引起異常
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ù)
//在子線程里運(yùn)行,比如先將數(shù)據(jù)放在LinkedBlockingQueue中,子線程while循環(huán)不斷從LinkedBlockingQueue中取出數(shù)據(jù)
//調(diào)用writeBytes函數(shù)發(fā)送出去
fun writeBytes(bytes: ByteArray) {
if (isRun){
try {
outputStream?.write(bytes)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
接收數(shù)據(jù)
//在子線程中的while循環(huán)里運(yù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
}
總結(jié)
1.Android不同版本,藍(lán)牙權(quán)限需要適配,分為6.0之前,6.0至11.0之間、12.0及之后,三類不同版本的適配
2.SPP通信前需要先進(jìn)行藍(lán)牙配對(duì)與連接
3.藍(lán)牙配對(duì)操作針對(duì)實(shí)際情況采用系統(tǒng)藍(lán)牙設(shè)置里進(jìn)行配對(duì)或者采用調(diào)用Create Socket通信接口自動(dòng)觸發(fā)配對(duì)
4.有些操作需要在子線程中進(jìn)行,防止ANR