这几天越学越觉得自己无知….

冷知识!蓝牙权限知多少_java

看这一张充满求知欲的脸~~

目前各种三方应用乱七八糟,一打开全是各种权限请求

像我这种安全意识极强的人,怎么能随意允许呢?
直接拒绝,然后应用退出,然后卸载,然后手机就安静了…开个小玩笑

现在安全意识都越来越强,相关部门也对应用的权限要求做了一定的规范

所以,一个app最好只申请必须要用的权限

开发一个基于蓝牙的应用,需要声明哪些权限?
很简单,就这四个

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

多问一句,这些权限都干嘛的?

不知道,反正加上之后蓝牙就好使

但这些权限肯定是在整个蓝牙通信流程中所要求的权限

回顾一下蓝牙通信流程

先通过配对过程形成通信通道。其中一台设备(可检测到的设备)需将自身设置为可接收传入的连接请求。另一台设备会使用服务发现过程找到此可检测到的设备。在可检测到的设备接受配对请求后,这两台设备会完成绑定过程,并在此期间交换安全密钥。二者会缓存这些密钥,以供日后使用。完成配对和绑定过程后,两台设备会交换信息。当会话完成时,发起配对请求的设备会发布已将其链接到可检测设备的通道。但是,这两台设备仍保持绑定状态,因此在未来的会话期间,只要二者在彼此的范围内且均未移除绑定,便可自动重新连接。

那也就是说是在调用蓝牙API时所要求的权限

那就先来看蓝牙API功能

开启蓝牙

通过intent的方式实现

 Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
 startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);

开启蓝牙所需要的权限是BLUETOOTH

/*
 * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
 */
  @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String
            ACTION_REQUEST_ENABLE = "android.bluetooth.adapter.action.REQUEST_ENABLE";

但还有一个直接enable的API:BluetoothAdapter#enable

所需要的权限是BLUETOOTH_ADMIN

 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
    public boolean enable() {...}

同样,BluetoothAdapter#disable也需要该权限

蓝牙可检测性

Intent discoverableIntent =
        new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);

如果此时你的设备未开启蓝牙,那么在开启可检测性时会自动开启蓝牙

开启可检测性所需要的权限是BLUETOOTH

/*
  * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
  */
  @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String
            ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";

设备可检测性有三种模式

  • SCAN_MODE_CONNECTABLE_DISCOVERABLE,设备处于可检测到模式。

    可配对,可连接

  • SCAN_MODE_CONNECTABLE设备未处于可检测到模式,也就是说不能被扫描到,但仍能收到连接请求,比如曾经配对的设备发来连接请求。

  • SCAN_MODE_NONE设备未处于可检测到模式,既不能被扫描到也无法收到连接请求。

通过action设置的设备可检测性时长默认是BluetoothDiscoverableEnabler#DISCOVERABLE_TIMEOUT_TWO_MINUTE=120s,可检测模式是第一种模式:可检测,可连接

当然你也可以修改,像上述代码中,修改了可检测性时长为300s

扫描设备

传统蓝牙扫描BluetoothAdapter#startDiscovery,BLE蓝牙扫描BluetoothLeScanner#startScan

具体蓝牙扫描功能可参见前文跟我一起学蓝牙基础篇(八)--蓝牙扫描

传统蓝牙扫描需要的权限是BLUETOOTH

@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
    public boolean startDiscovery(){...}

BLE蓝牙扫描需要的权限是BLUETOOTH_ADMIN

  @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
    public void startScan(final ScanCallback callback) {...}

为了获取扫描结果,需要的权限是ACCESS_COARSE_LOCATION或者ACCESS_FINE_LOCATION

/*
 * An app must hold
     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
     * in order to get results.
*/

蓝牙扫描会搜索一定区域内的一起用蓝牙功能的设备,并请求获取远程设备的某些信息,包括设备名、类别、以及蓝牙mac地址。

借助搜索到的设备信息,本机设备可以发起配对、连接操作

那么配对和连接有什么区别呢?

配对是指两台设备知道彼此的存在,具有可用于身份验证的共享链路密钥,并且能够与彼此建立加密连接。

连接是指设备当前共享一个 RFCOMM 通道,并且能够向彼此传输数据。

当前的 Android Bluetooth API 要求规定,只有先对设备进行配对,然后才能建立 RFCOMM 连接。

在使用 Bluetooth API 发起加密连接时,系统会自动执行配对。

查询已配对设备

调用方法为BluetoothAdapter#getBondedDevices()

所需要的权限是BLUETOOTH

 @RequiresPermission(Manifest.permission.BLUETOOTH)
    public Set<BluetoothDevice> getBondedDevices() {...}

如果蓝牙目前是关闭状态,那么读到的列表为空

蓝牙会将已配对的设备保存到bt_conf.conf文件中,在蓝牙每次开启后,就会重新从文件中读取已配对的设备列表

但我如果想要连接设备直接扫描不就好了,为什么要读取这个列表?

因为扫描过程是一个极其消耗蓝牙适配器资源的过程,会严重影响现有连接的带宽

所以在想要连接新设备时,可以看该设备是否存在已配对列表中,若存在,就不需要执行耗资源的扫描操作

而且,如果对方设备目前不可见,那么本机无法扫描到,此时想要连接就要看看是否已配对了,如果已配对,那么就可以直接发起连接

连接建立

常用的连接建立方式就是建立 RFCOMM 通道

准备一个服务器,开放一个BluetoothServerSocket并listening连接。在此情况下,另一台设备发起连接。

这种连接建立方式不需要设备提前配对,在尝试建立连接的过程,系统会自动发起配对,连接请求会一直阻塞,直到配对结束。

如果用户接受配对,即配对成功,那么RFCOMM连接就会建立成功
如果用户拒绝配对或者配对超时,那么RECOMM连接就会建立失败

连接的建立过程需要区分服务端和客户端

针对服务器端
第一步通过调用listenUsingRfcommWithServiceRecord获取BluetoothServerSocket。

所需要的权限是BLUETOOTH权限

 @RequiresPermission(Manifest.permission.BLUETOOTH)
    public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid){...}

顺便说一下这个方法

方法的作用就是往本机蓝牙的SDP服务里注册一条蓝牙服务,以便其他蓝牙设备可以通过SDP服务发现协议来发现该设备支持的服务

方法参数name就是要注册的serviceName,可以随意写。UUID 就是一种标准化的 128 位格式,可以在网上随意生成,是蓝牙服务的一种标志。只要不和其他蓝牙服务重合就可以。

第二步通过调用BluetoothServerSocket#accept() 开始listening连接请求.

这个其实和TCP的socket类似,就一直阻塞监听连接请求,除非服务端接受到连接请求或者是发生异常退出

当然,最后你如果想要结束现有连接,那就调用BluetoothServerSocket#close()

针对客户端

第一步借助蓝牙扫描或者是读取已配对设备列表拿到远程设备的BluetoothDevice对象

第二步使用 BluetoothDevice获取BluetoothSocket对象
通过调用BluetoothDevice#createRfcommSocketToServiceRecord获取BluetoothSocket

该方法所需要的权限是BLUETOOTH

 @RequiresPermission(Manifest.permission.BLUETOOTH)
    public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException {...}

方法参数中的UUID就是刚才创建服务端时所用到的UUID.客户端和服务端使用同一个UUID

也就是说客户端创建一个套接字,支持该UUID

第三步就是去BluetoothSocket#connect了。

当客户端调用此方法时,就会借助SDP查找刚才使用的BluetoothDevice设备是否支持该UUID的蓝牙服务

connect的过程其实就是把使用同一个UUID客户端的套接字和服务端的套接字连接起来,建立RFCOMM通道的过程

同样,connect也是一个阻塞的过程,在连接超时或者是连接失败时会有IO异常

数据传输

在连接建立之后,客户端和服务端均有了自己的BluetoothSocket对象

可以获取输入和输出流,进行读写操作。这个就是一般的socket的通信了。

除了以上这些常用API,还有一些

比如,修改蓝牙名字BluetoothAdapter#setName,需要Bluetooth_ADMIN

@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
    public boolean setName(String name) {...}

其他的可以自行去BluetoothAdapter中查看


其实从蓝牙核心系统架构角度来说,总共提供了三种服务

  • 设备控制服务:用于修改蓝牙设备的行为和工作方式,比如开关蓝牙。由设备管理器DM负责

  • 传输控制服务:用于创建、修改、释放通信单元,由链路管理器LM负责

  • 数据服务:比如数据的分包和重组,由L2CAP资源管理器负责

开放给应用的功能也仅仅是一部分


总结一下就是
BLUETOOTH权限,一般是蓝牙通信相关权限,例如请求连接、接受连接和传输数据等。

BLUETOOTH_ADMIN 权限。大多数应用只是需利用此权限发现、配对本地蓝牙设备

ACCESS_FINE_LOCATION权限,获取蓝牙扫描结果时需要。该权限是dangerous,需要动态申请您的应用需要此权限

ACCESS_COARSE_LOCATION权限,获取蓝牙扫描结果时需要 如果应用适配 Android 9(API 级别 28)或更低版本,则声明ACCESS_COARSE_LOCATION 权限即可,否则的话要使用ACCESS_FINE_LOCATION 权限。

为什么蓝牙会需要位置权限?

因为蓝牙扫描可用于收集用户的位置信息。此类信息可能来自用户自己的设备,以及在商店和交通设施等位置使用的蓝牙信标。

咔,收尾收尾

意犹未尽?那就关注一下呗

https://mp.weixin.qq.com/s/JGciI5hSQl4DMsXX1n_Yww