最近做iOS和Android的蓝牙打印功能,蓝牙打印少不了扫描附近的蓝牙设置,本博主要讲解Android开发中扫描蓝牙设备遇到的坑和解决方法
BLUETOOTH ,BLUETOOTH_ADMIN
发起蓝牙连接,接受蓝牙连接和传输数据,允许程序发现和配对蓝牙设备需要该权限
ACCESS_FINE_LOCATION
扫描蓝牙设备需要, 如果工程设置的target 位Android 9 (API level 28) 或更低, 可以声明ACCESS_COARSE_LOCATION 权限来代替ACCESS_FINE_LOCATION,两个权限的区别就是获取地理位置的精度不同。
知道了需要的权限后,我们在AndroidManifest.xml中加入
权限加好后,我们开始coding,代码如下
private fun initBluetooth() {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (null == bluetoothAdapter) {
ToastUtils.createByDefault(requireActivity(), R.string.phone_not_support_bluetooth)
} else {
if (!bluetoothAdapter.isEnabled) {
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE).also {
startActivityForResult(it, REQUEST_ENABLE_BT)
}
} else {
discoverBluetoothDevice()
}
}
}
功能很简单,注意先判断设置是否支持蓝牙,如果不支持提示,如果支持蓝牙但未开启,发起一个Intent开启蓝牙,否则开始进入扫描设备的,我们来看下扫描发现设备的实现discoverBluetoothDevice
private fun discoverBluetoothDevice() {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() ?: return
if (bluetoothAdapter.isDiscovering) {
bluetoothAdapter.cancelDiscovery()
}
val needsPermission = mutableListOf()
if (!AndPermission.hasPermissions(requireActivity(), Permission.ACCESS_COARSE_LOCATION)) {
needsPermission.add(Permission.ACCESS_COARSE_LOCATION)
}
if (!AndPermission.hasPermissions(requireActivity(), Permission.ACCESS_FINE_LOCATION)) {
needsPermission.add(Permission.ACCESS_FINE_LOCATION)
}
if (needsPermission.isEmpty()) {
bluetoothAdapter.startDiscovery()
} else {
AndPermission.with(requireActivity()).runtime()
.permission(needsPermission.toTypedArray())
.onGranted {
bluetoothAdapter.startDiscovery()
}
.onDenied {
Toast.makeText(activity, "没有获取到相关权限,该功能无法使用", Toast.LENGTH_SHORT)
.show()
//true,弹窗再次向用户索取权限
if (AndPermission.hasAlwaysDeniedPermission(activity, it)) {
// 第一种:用默认的提示语。
AlertDialog.Builder(requireActivity())
.setTitle("权限申请被拒绝")
.setMessage("需要开通相关权限才能操作,去设置权限页开通?")
.setPositiveButton(
"好的"
) { dialog, _ ->
dialog.cancel()
AndPermission.with(activity)
.runtime()
.setting()
.start(0)
}
.setNegativeButton("拒绝") { dialog, _ ->
dialog.cancel()
}
.show()
}
}.start()
}
}
注意功能是判断是否开启了ACCESS_COARSE_LOCATION,ACCESS_FINE_LOCATION权限,这两个权限是危险权限,Android 6.0以后需要手动申请,项目配置的不同需要的权限不同,以下为我负责的项目配置
compileSdkVersion = 29
targetSdkVersion = 29
这个为了方便同时申请了两个权限,根据build.gradle配置申请其中的一个,具体用什么权限可以看官方文档
https://developer.android.google.cn/guide/topics/connectivity/bluetooth/permissions?hl=en
申请权限用的是AndPermission框架,不熟悉的可以网上搜索下,没啥好说的,
扫描的功能开发完了,我们怎么获取扫描到的设备信息呢,这时就需要我们注册广播了,BluetoothAdapter扫描到设备就会发一个Action为BluetoothDevice.ACTION_FOUND的广播, 直接上代码
private val mFindBluetoothReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
BluetoothDevice.ACTION_FOUND -> {
val device =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
if (device.type != BluetoothDevice.DEVICE_TYPE_LE) {
BluetoothDeviceInfo(
macAddress = device.address,
name = device.name ?: "unknown"
).also {
mDeviceListAdapter.onDiscoveredDevice(it)
}
}
Log.i("Bluetooth", "onReceive: ===============>${device}")
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requireActivity().registerReceiver(
mFindBluetoothReceiver,
IntentFilter(BluetoothDevice.ACTION_FOUND).apply {
addAction(
BluetoothAdapter.ACTION_DISCOVERY_FINISHED
)
})
}
其中遇到一个恶心问题就是,运行后导致ANR(应用无响应),换了几个设备都没找到原因,最后有一个手机在这里崩溃才找到是因为null传给kotlin的非可空变量才导致的, 完全懵逼,这跟ANR联系上我也是醉了
data class BluetoothDeviceInfo(val macAddress: String, val name: String)
BluetoothDeviceInfo(
macAddress = device.address,
name = device.name ?: "unknown"
)
解决这个问题后,我测试的手机都能正常扫描到蓝牙设备了,
但别高兴的太早,部分Android 10机型就是扫描不到任何蓝牙设备,这时候一通百度,都说没申请权限之类的,最后无果跑官网把蓝牙权限相关的看了一遍,发现了一段
Services running on Android 10 and higher cannot discover Bluetooth
devices unless they have the ACCESS_BACKGROUND_LOCATION permission.
For more information on this requirement, see Access location in the
background.
意思是Android 10以上的设备除非拥有ACCESS_BACKGROUND_LOCATION权限,否则扫描不到蓝牙设备,坑,我测试的真机就是Android 10的能搜索到, 但不是所有的都能扫描到,看到这条信息松了口气,再权限申请的地方加上这条
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !AndPermission.hasPermissions(requireActivity(), Permission.ACCESS_BACKGROUND_LOCATION)) {
needsPermission.add(Permission.ACCESS_BACKGROUND_LOCATION)
}
加完激动的运行,结果果断的崩溃了,我靠忘记AndroidManifest.xml加权限了,好吧加上后允许一切正常。
到这里应该也到了结速的时候了,但今天又发现了一个vivo S5 Android 10的系统就是扫描不到蓝牙设备,又撸了一遍官网关于蓝牙的文档,没有任何发现,最后折腾了两小时通过打开手机GPS解决了问题