Android藍(lán)牙SPP通信客戶端實(shí)現(xiàn)

開(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容