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

開啟權限

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

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容