前言
上一篇文章给大家分享了Kotlin版的Android蓝牙的基础知识和基础用法,不过上一篇都是一些零散碎片化的程序,,这一篇给大家分享Android蓝牙开发实战项目Kotlin+Compose的初步使用
效果演示 :
Android Compose 蓝牙开发
Android蓝牙实战开发步骤
1.新建Android项目添加蓝牙权限
下图所示:MyBluetoothDemo为刚刚创建的Android空项目,我们现在清单文件中把我们需要用到的权限声明一下,其中定位权限还需要做动态申请
2.封装BluetoothAdapter类
BluetoothAdapter类提供了常用的蓝牙API,我这里创建了一个BlueToothController类,小编这里是先将这些API封装到了一个BlueToothController类中,方便后续使用和操作
package com.example.bluetoothcompose
import android.annotation.SuppressLint
import android.app.Activity
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket
import android.content.Context
import android.content.Intent
object BlueToothController {
val mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
/**
* 检查设备是否支持蓝牙
*/
fun isBluetoothSupport(): Boolean {
return mBluetoothAdapter !=null
}
/**
* 检查该设备蓝牙是否开启
*/
@SuppressLint("MissingPermission")
fun isBluetoothEnabled(): Boolean {
return mBluetoothAdapter.enable()
}
/**
* 打开蓝牙
*/
@SuppressLint("MissingPermission")
fun turnOnBlueTooth(activity: Activity, requestCode: Int) {
val intent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
activity.startActivityForResult(intent, requestCode)
}
/**
* 打开蓝牙可见性
*/
@SuppressLint("MissingPermission")
fun enableVisibily(context: Context) {
val intent = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300)
context.startActivity(intent)
}
/**
* 停止查找设备
*/
@SuppressLint("MissingPermission")
fun cancelFindDevice() {
mBluetoothAdapter.cancelDiscovery()
}
/**
* 判断当前设备是否在查找蓝牙设备
*/
@SuppressLint("MissingPermission")
fun isStartDiscovering(): Boolean {
return mBluetoothAdapter.isDiscovering
}
/**
* 判断当前设备是否未在查找蓝牙设备
*/
@SuppressLint("MissingPermission")
fun isCancelDiscovering(): Boolean {
return !mBluetoothAdapter.isDiscovering
}
/**
* 查找设备
*/
@SuppressLint("MissingPermission")
fun findDevice() {
mBluetoothAdapter.startDiscovery()
}
/**
* 获取已绑定设备
*/
@SuppressLint("MissingPermission")
fun getBondedDeviceList(): List<BluetoothDevice?>? {
return ArrayList(mBluetoothAdapter.bondedDevices)
}
/**
* 判断蓝牙是否连接
*/
@SuppressLint("MissingPermission")
fun isConnectBlue(bluetoothSocket: BluetoothSocket?): Boolean {
return bluetoothSocket != null && bluetoothSocket.isConnected
}
}
3. 编写Compose UI页面
这里的UI样式,在后面我给出了完整版的,大家可以去复制一下
MainScreen:这是我们MainActivity的UI,放置了一个Column(竖向布局)和Menu
@Composable
fun MainScreen() {
var expanded = remember {
mutableStateOf(false)
}
Column(
modifier = Modifier.fillMaxSize()
)
{
Row(
modifier = Modifier
.fillMaxWidth()
.background(Blue)
.padding(vertical = 12.dp)
.height(35.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Text(
text = "可用设备",
modifier = Modifier
.weight(1f)
.offset(10.dp)
)
if(isRefresh.value){
CircularProgressIndicator(
modifier = Modifier.size(25.dp),
color = White
)
}
Box() {
Icon(
painter = painterResource(id = R.drawable.ic_setting),
contentDescription = null,
modifier = Modifier
.width(50.dp)
.fillMaxHeight()
.clickable {
expanded.value = true
},
)
if(expanded.value){
DropdownMenu(
expanded = expanded.value,
onDismissRequest = {
expanded.value = false
}) {
data.forEachIndexed{ index: Int, s: String ->
DropdownMenuItem(onClick = {
when (index) {
0 -> {
if(BlueToothController.isBluetoothSupport()){
Toast.makeText(this@MainActivity,"本机支持蓝牙功能",Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(this@MainActivity,"本机暂不支持蓝牙功能",Toast.LENGTH_SHORT).show()
}
}
1 -> {
if(BlueToothController.isBluetoothEnabled()){
Toast.makeText(this@MainActivity,"用户允许开启蓝牙",Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(this@MainActivity,"用户拒绝开启蓝牙",Toast.LENGTH_SHORT).show()
}
}
2 -> {
selected.value = 3
Log.d(TAG,"查看已绑定设备")
if(BlueToothController.isStartDiscovering()){
BlueToothController.cancelFindDevice()
}
deviceList.clear()
for (device in BlueToothController.getBondedDeviceList()!!){
deviceList.add(device!!)
}
}
3 -> {
if(BlueToothController.isStartDiscovering()){
Log.d(TAG,"停止查找")
BlueToothController.cancelFindDevice()
deviceList!!.clear()
}
selected.value = 4
BlueToothController.findDevice()
Log.d(TAG,"开始查找")
}
}
Log.d(TAG,selected.value.toString())
expanded.value = false
}) {
Text(text = s)
}
}
}
}
}
}
DeviceListView()
}
if(openDialog.value){
AlterDialog()
}
}
AlterDialog: 用来显示弹窗
@Composable
fun AlterDialog() {
AlertDialog(
onDismissRequest = { openDialog.value = false },
title = { Text(text = text.value) },
text = {
Text(
text = "0c 11 09 41 23 00 01 03 FF"
)
}, confirmButton = {
TextButton(onClick = {
openDialog.value = false
sendMessage()
}) {
Text(text = "发送")
}
}, dismissButton = {
TextButton(onClick = { openDialog.value = false }) {
Text(text = "取消")
}
})
}
DeviceListView: 这是一个列表控件,相当于RecycleView
@Composable
fun DeviceListView(){
LazyColumn(
Modifier
.fillMaxSize(),
contentPadding = PaddingValues(5.dp,1.dp),
verticalArrangement = Arrangement.spacedBy(5.dp)
){
items(deviceList!!.size){ index->
ListItem(index, deviceList[index])
}
}
}
ListItem:这是每个LazyColumn中每个列表的UI样式
@Composable
fun ListItem(index: Int, blueToothDevice: BluetoothDevice){
Card(
shape = RoundedCornerShape(4.dp),
elevation = 2.dp
) {
Row(
modifier = Modifier
.height(50.dp)
.fillMaxWidth()
.clickable {
openDialog.value = true
if (blueToothDevice.name == null) {
text.value = "N/A"
} else {
text.value = blueToothDevice.name
}
//Gatt协议连接蓝牙
var bluetoothGatt =
blueToothDevice.connectGatt(this@MainActivity, true, mGattCallback)
bluetoothGatt.connect()
Log.d(TAG, "点击了第$index 个item")
},
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painterResource(R.drawable.ic_blue),
contentDescription = null,
modifier = Modifier
.fillMaxHeight()
.padding(all = 5.dp)
)
Column(
modifier = Modifier.fillMaxWidth()
) {
if(blueToothDevice.name==null){
Text(
text = "N/A",
fontWeight = FontWeight.Bold
)
}else{
Text(
text = blueToothDevice.name,
fontWeight = FontWeight.Bold
)
}
Text(
text = blueToothDevice.address,
)
}
}
}
}
4. 蓝牙搜索,配对,连接,通信
小编这里为了让大家方便,便将搜索,配对,连接都写在了MainActivity中了,Compose UI也在这里了,大家可以复制直接去运行
package com.example.bluetoothcompose
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.annotation.SuppressLint
import android.bluetooth.*
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.content.PermissionChecker.PERMISSION_GRANTED
import com.example.bluetoothcompose.ui.theme.Blue
import com.example.bluetoothcompose.ui.theme.BlueToothComposeTheme
import com.example.bluetoothcompose.ui.theme.White
import java.util.*
class MainActivity : ComponentActivity() {
private val TAG = "yf"
private var deviceList = mutableStateListOf<BluetoothDevice>()
private var data = mutableListOf(
"检查设备是否支持蓝牙",
"检查设备是否开启蓝牙",
"查看已配过的蓝牙设备",
"查找蓝牙设备"
)
var selected = mutableStateOf(0)
var openDialog = mutableStateOf(false)
var text = mutableStateOf("")
var mGatt: BluetoothGatt? = null
var mWriter: BluetoothGattCharacteristic? = null
private var isRefresh = mutableStateOf(false)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BlueToothComposeTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
MainScreen()
}
}
}
}
override fun onStart() {
super.onStart()
isPermission()
registerBluetoothReceiver()
}
//处理找到蓝牙设备和搜索完成的广播消息
var receiver: BroadcastReceiver = object : BroadcastReceiver() {
@SuppressLint("MissingPermission")
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
//开始查找设备
when {
BluetoothAdapter.ACTION_DISCOVERY_STARTED == action -> {
//开始搜索
if(deviceList!=null){
deviceList!!.clear()
}
isRefresh.value = true
}
BluetoothDevice.ACTION_FOUND == action -> {
//搜到蓝牙设备
val device =
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
//把搜索到的设备添加到已找到列表中,显示它的信息
deviceList?.add(device!!)
Log.d(TAG,"找到了: ${deviceList.size}")
}
BluetoothAdapter.ACTION_DISCOVERY_FINISHED == action -> {
//搜索完毕
isRefresh.value = false
when (selected.value) {
3 -> {
}
4 -> {
Toast.makeText(this@MainActivity,"选择要配对的蓝牙设备",Toast.LENGTH_SHORT).show()
}
}
}
BluetoothDevice.ACTION_BOND_STATE_CHANGED == action -> {
val device =
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
if (device == null) {
Toast.makeText(this@MainActivity,"无设备",Toast.LENGTH_SHORT).show()
return
}
val state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 0)
when (state) {
BluetoothDevice.BOND_BONDED -> {
Toast.makeText(this@MainActivity,"已配对",Toast.LENGTH_SHORT).show()
}
BluetoothDevice.BOND_BONDING -> {
Toast.makeText(this@MainActivity,"正在配对",Toast.LENGTH_SHORT).show()
}
BluetoothDevice.BOND_NONE -> {
Toast.makeText(this@MainActivity,"未配对",Toast.LENGTH_SHORT).show()
}
}
}
}
}
}
//动态获取位置权限
@SuppressLint("WrongConstant")
private fun isPermission() {
if (checkSelfPermission(ACCESS_COARSE_LOCATION) !== PERMISSION_GRANTED
|| checkSelfPermission(ACCESS_FINE_LOCATION) !== PERMISSION_GRANTED
) {
requestPermissions(
arrayOf(
ACCESS_COARSE_LOCATION,
ACCESS_FINE_LOCATION
), 200
)
}
}
private fun registerBluetoothReceiver() {
//filter注册广播接收器
val filter = IntentFilter()
//蓝牙当前状态
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
//开始扫描蓝牙设备广播
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
//找到蓝牙设备广播
filter.addAction(BluetoothDevice.ACTION_FOUND)
//扫描蓝牙设备结束广播
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
//蓝牙设备配对状态改变广播
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
//设备扫描模式改变广播
filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED)
registerReceiver(receiver, filter)
}
@SuppressLint("MissingPermission")
@Composable
fun MainScreen() {
var expanded = remember {
mutableStateOf(false)
}
Column(
modifier = Modifier.fillMaxSize()
)
{
Row(
modifier = Modifier
.fillMaxWidth()
.background(Blue)
.padding(vertical = 12.dp)
.height(35.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Text(
text = "可用设备",
modifier = Modifier
.weight(1f)
.offset(10.dp)
)
if(isRefresh.value){
CircularProgressIndicator(
modifier = Modifier.size(25.dp),
color = White
)
}
Box() {
Icon(
painter = painterResource(id = R.drawable.ic_setting),
contentDescription = null,
modifier = Modifier
.width(50.dp)
.fillMaxHeight()
.clickable {
expanded.value = true
},
)
if(expanded.value){
DropdownMenu(
expanded = expanded.value,
onDismissRequest = {
expanded.value = false
}) {
data.forEachIndexed{ index: Int, s: String ->
DropdownMenuItem(onClick = {
when (index) {
0 -> {
if(BlueToothController.isBluetoothSupport()){
Toast.makeText(this@MainActivity,"本机支持蓝牙功能",Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(this@MainActivity,"本机暂不支持蓝牙功能",Toast.LENGTH_SHORT).show()
}
}
1 -> {
if(BlueToothController.isBluetoothEnabled()){
Toast.makeText(this@MainActivity,"用户允许开启蓝牙",Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(this@MainActivity,"用户拒绝开启蓝牙",Toast.LENGTH_SHORT).show()
}
}
2 -> {
selected.value = 3
Log.d(TAG,"查看已绑定设备")
if(BlueToothController.isStartDiscovering()){
BlueToothController.cancelFindDevice()
}
deviceList.clear()
for (device in BlueToothController.getBondedDeviceList()!!){
deviceList.add(device!!)
}
}
3 -> {
if(BlueToothController.isStartDiscovering()){
Log.d(TAG,"停止查找")
BlueToothController.cancelFindDevice()
deviceList!!.clear()
}
selected.value = 4
BlueToothController.findDevice()
Log.d(TAG,"开始查找")
}
}
Log.d(TAG,selected.value.toString())
expanded.value = false
}) {
Text(text = s)
}
}
}
}
}
}
DeviceListView()
}
if(openDialog.value){
AlterDialog()
}
}
@Preview(
showBackground = true,
group = "Group1",
)
@Composable
fun DefaultPreview() {
MainScreen()
}
@SuppressLint("MissingPermission")
@Composable
fun DeviceListView(){
LazyColumn(
Modifier
.fillMaxSize(),
contentPadding = PaddingValues(5.dp,1.dp),
verticalArrangement = Arrangement.spacedBy(5.dp)
){
items(deviceList!!.size){ index->
ListItem(index, deviceList[index])
}
}
}
@SuppressLint("MissingPermission")
@Composable
fun ListItem(index: Int, blueToothDevice: BluetoothDevice){
Card(
shape = RoundedCornerShape(4.dp),
elevation = 2.dp
) {
Row(
modifier = Modifier
.height(50.dp)
.fillMaxWidth()
.clickable {
openDialog.value = true
if (blueToothDevice.name == null) {
text.value = "N/A"
} else {
text.value = blueToothDevice.name
}
//Gatt协议连接蓝牙
var bluetoothGatt =
blueToothDevice.connectGatt(this@MainActivity, true, mGattCallback)
bluetoothGatt.connect()
Log.d(TAG, "点击了第$index 个item")
},
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painterResource(R.drawable.ic_blue),
contentDescription = null,
modifier = Modifier
.fillMaxHeight()
.padding(all = 5.dp)
)
Column(
modifier = Modifier.fillMaxWidth()
) {
if(blueToothDevice.name==null){
Text(
text = "N/A",
fontWeight = FontWeight.Bold
)
}else{
Text(
text = blueToothDevice.name,
fontWeight = FontWeight.Bold
)
}
Text(
text = blueToothDevice.address,
)
}
}
}
}
@SuppressLint("MissingPermission")
@Composable
fun AlterDialog() {
AlertDialog(
onDismissRequest = { openDialog.value = false },
title = { Text(text = text.value) },
text = {
Text(
text = "0c 11 09 41 23 00 01 03 FF"
)
}, confirmButton = {
TextButton(onClick = {
openDialog.value = false
sendMessage()
}) {
Text(text = "发送")
}
}, dismissButton = {
TextButton(onClick = { openDialog.value = false }) {
Text(text = "取消")
}
})
}
private val mGattCallback: BluetoothGattCallback = object : BluetoothGattCallback() {
@SuppressLint("MissingPermission")
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
//连接成功
if (newState == BluetoothProfile.STATE_CONNECTED) {
//进行服务发现
gatt.discoverServices()
Log.d(TAG, "连接成功")
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
//连接断开,处理断开逻辑
Log.d(TAG, "连接断开")
}
}
@SuppressLint("MissingPermission")
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
Log.d(TAG, "onServicesDiscovered : $status ==>> $gatt")
//发现服务成功,处理服务和特征值
if (status == BluetoothGatt.GATT_SUCCESS) {
//发送消息
mGatt = gatt
val service =
gatt.getService(UUID.fromString("0000180a-0000-1000-8000-00805F9B34FB"))
mWriter =
service.getCharacteristic(UUID.fromString("00002ad9-0000-1000-8000-00805F9B34FB"))
//打开消息通知
mGatt!!.setCharacteristicNotification(mWriter, true)
val descriptor: BluetoothGattDescriptor =
mWriter!!.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
mGatt!!.writeDescriptor(descriptor)
} else {
Log.d(TAG, "发现服务失败")
}
}
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
Log.e(TAG, "onCharacteristicRead $status")
//读取特征成功,处理特征值
if (status == BluetoothGatt.GATT_SUCCESS) {
}
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
Log.e(TAG, "onCharacteristicWrite $status")
//写入特征成功
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "发送成功")
} else {
Log.d(TAG, "发送失败")
}
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
//接收到数据
val data = characteristic.value
//处理接收到的数据
Log.d(TAG, "Received data: " + bytesToHexFun2(data))
}
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)
}
override fun onReliableWriteCompleted(gatt: BluetoothGatt, status: Int) {
super.onReliableWriteCompleted(gatt, status)
}
override fun onReadRemoteRssi(gatt: BluetoothGatt, rssi: Int, status: Int) {
super.onReadRemoteRssi(gatt, rssi, status)
}
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
super.onMtuChanged(gatt, mtu, status)
}
override fun onServiceChanged(gatt: BluetoothGatt) {
super.onServiceChanged(gatt)
}
override fun onPhyUpdate(gatt: BluetoothGatt, txPhy: Int, rxPhy: Int, status: Int) {
super.onPhyUpdate(gatt, txPhy, rxPhy, status)
}
override fun onPhyRead(gatt: BluetoothGatt, txPhy: Int, rxPhy: Int, status: Int) {
super.onPhyRead(gatt, txPhy, rxPhy, status)
}
}
private fun bytesToHexFun2(bytes: ByteArray): String? {
var result = 0
for (i in bytes.indices) {
result += bytes[i]
}
return byte2Hex((result.inv() and 0xFF).toByte())
}
fun byte2Hex(inByte: Byte?): String //1字节转2个Hex字符
{
return String.format("%02x", inByte).toUpperCase()
}
@SuppressLint("MissingPermission")
fun sendMessage(){
if (null == mWriter) {
Log.e("yf123", "ble:发送失败:null == writer !!!!")
} else {
mWriter!!.value = byteArrayOf(
0x0c.toByte(),
0x11.toByte(),
0x09.toByte(),
0x41.toByte(),
0x23.toByte(),
0x00.toByte(),
0x01.toByte(),
0x03.toByte(),
0xFF.toByte()
)
mGatt!!.writeCharacteristic(mWriter)
}
}
}
到此为止,我们的程序就到这里了,蓝牙搜索,配对,连接,通信便已经成功实现了,大家可以把代码copy一下拿去运行,具体效果演示图在文章最上方,大家还想了解更多关于Android蓝牙开发的可以继续看我下一篇给大家的分享