Android蓝牙扫描设备

Android蓝牙扫描设备踩坑

最近做iOS和Android的蓝牙打印功能,蓝牙打印少不了扫描附近的蓝牙设置,本博主要讲解Android开发中扫描蓝牙设备遇到的坑和解决方法
  • 所需权限

BLUETOOTHBLUETOOTH_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解决了问题

你可能感兴趣的:(android,UI,bluetooth,android)