一、根本介绍
所谓蓝牙 (Bluetooth) 技术,实际上是一种短距离无线电技术,最后是由爱立信公司公司创造的。技术始于爱立信公司 1994 计划,它是钻研在移动电话和其他配件间进行低功耗、低成本无线通信连贯的办法。发明者心愿为设施间的通信发明一组对立规定(标准化协定)用来解决用户间互相不兼容的挪动电子设备。
1998 年 5 月 20 日,索尼以立信、国内商业机器、英特尔、诺基亚及东芝公司等业界龙头创建“特地兴趣小组”(Special Interest Group SIG),即蓝牙技术联盟的前身,指标是开发一个成本低、效益高、能够在短距离范畴内随便无线连接的蓝牙技术标准, 是负责蓝牙标准的制订和推广的国内组织。
蓝牙倒退至今经验了多个版本的更新,1.1、1.2、2.0、2.1、3.0、4.0、4.1、4.2、5.0 等。其中,将 1.x~3.0 之间的版本称之为经典蓝牙,4.x 开始的蓝牙称之为低功耗蓝牙,也就是蓝牙 ble。依据利用、协定类型等,能够对蓝牙进行以下分类:
<br/>
二、经典蓝牙 API 介绍
Android 平台蕴含蓝牙网络堆栈反对,此反对能让设施以无线形式与其余蓝牙设施替换数据。利用框架提供通过 Android Bluetooth API 拜访蓝牙性能的权限。这些 API 容许利用以无线形式连贯到其余蓝牙设施,从而实现点到点和多点无线性能。Android 利用可通过 Bluetooth API 执行以下操作:
- 扫描其余蓝牙设施
- 查问本地蓝牙适配器的配对蓝牙设施
- 建设 RFCOMM 通道
- 通过服务发现连贯到其余设施
- 与其余设施进行双向数据传输
- 治理多个连贯
以下对经典蓝牙开发相干的 API 进行介绍:
1、BluetoothAdapter 类
BluetoothAdapter 代表了挪动设施的本地的蓝牙适配器, 通过该蓝牙适配器能够对蓝牙进行基本操作, 例如 : 启动设施发现,获取已配对设施,通过 mac 蓝牙地址获取蓝牙设施等。
(1)获取本地蓝牙适配器实例
办法定义:
/**
* 作用:* 获取本地蓝牙适配器实例
* 参数:* 无
* 返回:* 如果设施具备蓝牙性能,返回 BluetoothAdapter 实例;否则,返回 null 对象。*/
public static synchronized BluetoothAdapter getDefaultAdapter();
应用阐明:
1、获取默认本地蓝牙适配器的句柄。目前 Android 仅反对一个蓝牙适配器,但该 API 能够扩大为反对更多。
(2)关上蓝牙
办法定义:
/**
* 作用:* 关上蓝牙
* 参数:* 无
* 返回:* 如果蓝牙开始关上,则返回 true;如果蓝牙关上产生问题,则返回 false。*/
public boolean enable();
应用阐明:
1、须要 BLUETOOTH_ADMIN 权限。
2、该办法将不通过用户批准,间接启用底层蓝牙硬件,并启动所有蓝牙零碎服务。因为不同 Android 设施零碎的实现不同,所以局部 Android 零碎在调用该办法时也会弹框申请用户批准。
3、关上蓝牙,还能够通过调用 startActivityForResult 办法,应用 ACTION_REQUEST_ENABLE 用意来实现,此办法将弹出对话框,申请容许关上蓝牙。能够在 Activity 中的 onActivityResult()办法中解决操作后果。
4、该办法是一个异步调用:它将立刻返回后果。如果此调用返回 true,则适配器状态将立刻从 STATE_OFF 转换为 STATE_TURNING_ON,并且稍后过渡到 STATE_OFF 或 STATE_ON。如果此调用返回 false,则阐明呈现问题阻止适配器开启,例如设施处于航行模式,或者蓝牙已关上。因而还该当监听 ACTION_STATE_CHANGED 播送,以跟踪后续蓝牙状态更改。
(3)敞开蓝牙
办法定义:
/**
* 作用:* 敞开蓝牙
* 参数:* 无
* 返回:* 如果蓝牙开始敞开,则返回 true;如果蓝牙敞开产生问题,则返回 false。*/
public boolean disable();
应用阐明:
1、须要 BLUETOOTH_ADMIN 权限。
2、该办法将不通过用户批准,敞开所有蓝牙连贯,进行蓝牙零碎服务并敞开底层蓝牙硬件。因为不同 Android 设施零碎的实现不同,所以局部 Android 零碎在调用该办法时也会弹框申请用户批准。
3、该办法是一个异步调用:它将立刻返回后果。如果此调用返回 true,则适配器状态将立刻从 STATE_ON 转换为 STATE_TURNING_OFF,并且稍后过渡到 STATE_OFF 或 STATE_ON。如果此调用返回 false,则阐明呈现问题阻止适配器敞开,例如适配器已敞开。因而还该当监听 ACTION_STATE_CHANGED 播送,以跟踪后续蓝牙状态更改。
(4)验证蓝牙 MAC 地址
办法定义:
/**
* 作用:* 验证蓝牙设施 MAC 地址是否无效。* 参数:* address:蓝牙 MAC 地址,字母必须大写,例如:"00:43:A8:23:10:F1"。* 返回:* 如果蓝牙 MAC 地址无效,则返回 true;否则返回 false。*/
public static boolean checkBluetoothAddress(String address);
应用阐明:
无
(5)获取本地蓝牙 MAC 地址
办法定义:
/**
* 作用:* 获取本地蓝牙适配器的硬件地址(MAC 地址)* 参数:* 无
* 返回:* 本地的硬件地址,例如:"00:11:22:AA:BB:CC"。*/
public String getAddress();
应用阐明:
1、须要 BLUETOOTH 权限。
(6)获取蓝牙绑定列表
办法定义:
/**
* 作用:* 获取与本机蓝牙所有绑定的近程蓝牙信息。* 参数:* 无
* 返回:* 将本地蓝牙适配器绑定的一组 BluetoothDevice 对象返回。若呈现谬误返回 null。*/
public Set<BluetoothDevice> getBondedDevices();
应用阐明:
1、须要 BLUETOOTH 权限。
2、若蓝牙未关上,将返回空集。
(7)获取蓝牙名称
办法定义:
/**
* 作用:* 获取本地蓝牙适配器的蓝牙名称。* 参数:* 无
* 返回:* 本地蓝牙名称。若呈现谬误,返回 null。*/
public String getName();
应用阐明:
1、须要 BLUETOOTH 权限。
(8)设置蓝牙名称
办法定义:
/**
* 作用:* 设置本地蓝牙适配器的蓝牙名称。* 参数:* name:蓝牙名称。* 返回:* 设置胜利返回 true,否则返回 false。*/
public boolean setName(String name);
应用阐明:
1、须要 BLUETOOTH_ADMIN 权限。
2、如果蓝牙未关上,该办法将返回 false。
3、只管许多近程设施只能显示前 40 个字符,而有些可能仅限于 20 个,但无效的蓝牙名称最多应用 UTF- 8 编码为 248 个字节。
(9)获取蓝牙扫描模式
办法定义:
/**
* 作用:* 获取本地蓝牙适配器的以后蓝牙扫描模式。* 参数:* 无
* 返回:* 以后蓝牙适配器的蓝牙扫描模式。*/
public int getScanMode();
应用阐明:
1、须要 BLUETOOTH 权限。
2、蓝牙扫描模式确定本地蓝牙适配器是否可被近程蓝牙设施连贯和发现。
3、如果蓝牙未关上,此办法将返回 SCAN_MODE_NONE。
蓝牙扫描模式:
名称 | 值(int) | 含意 |
---|---|---|
SCAN_MODE_NONE | 20 | 该设施不能扫描以及被扫描。 |
SCAN_MODE_CONNECTABLE | 21 | 该设施能够扫描其余蓝牙设施。 |
SCAN_MODE_CONNECTABLE_DISCOVERABLE | 23 | 该设施既能够扫描其余设施,也能够被其余设施扫描发现。 |
(10)获取蓝牙适配器状态
办法定义:
/**
* 作用:* 获取本地蓝牙适配器的以后状态。* 参数:* 无
* 返回:* 以后蓝牙适配器状态。*/
public int getState();
应用阐明:
1、须要 BLUETOOTH 权限。
蓝牙适配器状态:
名称 | 值(int) | 含意 |
---|---|---|
STATE_OFF | 10 | 示意本地蓝牙适配器已敞开 |
STATE_TURNING_ON | 11 | 示意本地蓝牙适配器正在关上 |
STATE_ON | 12 | 示意本地蓝牙适配器已开启,并可供使用 |
STATE_TURNING_OFF | 13 | 示意本地蓝牙适配器正在敞开 |
(11)蓝牙是否关上
办法定义:
/**
* 作用:* 判断以后蓝牙适配器是否关上
* 参数:* 无
* 返回:* 若蓝牙为关上状态,则返回 true,否则返回 false。*/
public boolean isEnabled();
应用阐明:
1、须要 BLUETOOTH 权限。
2、如果蓝牙正处于关上状态并可用,则返回 true 值,getState()==STATE_ON 等价。
(12)蓝牙是否正在扫描
办法定义:
/**
* 作用:* 判断蓝牙适配器是否正在处于扫描过程中。* 参数:* 无
* 返回:* 若蓝牙处于扫描状态,则返回 true;否则返回 false。*/
public boolean isDiscovering();
应用阐明:
1、须要 BLUETOOTH 权限。
2、若蓝牙未关上,该办法将返回 false。
3、扫描设施是一个重量级过程,不应在扫描时尝试建设连贯,而此时已存在的蓝牙连贯将取得有限度的带宽以及高提早。
(13)启动扫描
办法定义:
/**
* 作用:* 开始扫描周边蓝牙设施。* 参数:* 无
* 返回:* 若启动胜利,返回 true;否则返回 false。*/
public boolean startDiscovery();
应用阐明:
1、须要 BLUETOOTH_ADMIN 权限。
2、通常为 12 秒左右的查问扫描过程。
3、这是一个异步调用,它会立刻返回。注册 ACTION_DISCOVERY_STARTED 和 ACTION_DISCOVERY_FINISHED 播送以确定发现何时开始和实现的确切工夫。注册 ACTION_FOUND 以便在发现近程蓝牙设施时收到告诉。
4、若蓝牙未关上,该办法将返回 false。
5、扫描设施是一个重量级过程,不应在扫描时尝试建设连贯,而此时已存在的蓝牙连贯将取得有限度的带宽以及高提早。能够应用 cancelDiscovery()勾销扫描操作。
(14)勾销扫描
办法定义:
/**
* 作用:* 勾销蓝牙搜寻操作
* 参数:* 无
* 返回:* 如果勾销胜利, 则返回 true; 如果勾销失败, 返回 false。*/
public boolean cancelDiscovery()
1、须要 BLUETOOTH_ADMIN 权限。
2、若蓝牙未关上,该办法将返回 false。
3、因为蓝牙搜寻是一个重量级过程,会消耗大量资源,所以在连贯近程蓝牙设施前,必须调用这个办法,勾销搜寻。
(15)获取近程蓝牙设施
办法定义:
/**
* 作用:* 获取给定蓝牙硬件地址的 BluetoothDevice 对象。* 参数:* address:蓝牙 MAC 地址,字母必须大写,例如:"00:43:A8:23:10:F1"。* 返回:* 指定的近程蓝牙设施。*/
public BluetoothDevice getRemoteDevice(String address);
应用阐明:
1、如果 MAC 有效有效,将抛出 IllegalArgumentException 异样。
(16)创立不平安的蓝牙服务套接字
办法定义:
/**
* 作用:* 创立一个正在监听的不平安的带有服务记录的无线射频通信(RFCOMM)蓝牙端口。* 参数:* name:SDP 记录下的服务器名,能够是任意字符串。* uuid:SDP 记录下的 UUID。* 返回:* BluetoothServerSocket 对象。*/
public BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid);
应用阐明:
1、须要 BLUETOOTH 权限。
2、零碎将调配一个未应用的 RFCOMM 通道进行侦听。
3、当产生谬误时,例如蓝牙不可用、权限有余、通道被占用等,将抛出 IOException 异样。
4、通过此形式创立的蓝牙服务套接字是不平安的,连贯时不须要进行配对。
(17)创立平安的蓝牙服务套接字
办法定义:
/**
* 作用:* 创立一个正在监听的平安的带有服务记录的无线射频通信(RFCOMM)蓝牙端口。* 参数:* name:SDP 记录下的服务器名,能够是任意字符串。* uuid:SDP 记录下的 UUID。* 返回:* BluetoothServerSocket 对象。*/
public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid);
应用阐明:
1、须要 BLUETOOTH 权限。
2、零碎将调配一个未应用的 RFCOMM 通道进行侦听。
3、当产生谬误时,例如蓝牙不可用、权限有余、通道被占用等,将抛出 IOException 异样。
4、通过此形式创立的蓝牙服务套接字是平安的,连贯时须要进行配对。
<br/>
2、BluetoothDevice 类
BluetoothDevice 对象代表了一个近程的蓝牙设施, 通过这个类能够查问近程设施的物理地址, 名称, 连贯状态等信息。这个类实际上只是一个蓝牙硬件地址的简略包装,这个类的对象是不可变的。对这个类的操作, 会执行在近程蓝牙设施的硬件上。
(1)获取蓝牙名称
办法定义:
/**
* 作用:* 获取近程蓝牙设施的蓝牙名称。* 参数:* 无
* 返回:* 胜利则返回蓝牙名称,若呈现问题则返回 null。*/
public String getName();
应用阐明:
1、须要 BLUETOOTH 权限。
2、执行设施扫描时,本地适配器将自动检索近程名称,并将缓存它们。此办法仅从缓存中返回此设施的名称。
(2)获取蓝牙 MAC
办法定义:
/**
* 作用:* 获取近程蓝牙设施的硬件地址
* 参数:* 无
* 返回:* 蓝牙硬件地址
*/
public String getAddress();
应用阐明:
无
(3)获取蓝牙绑定状态
办法定义:
/**
* 作用:* 获取近程蓝牙设施的绑定状态
* 参数:* 无
* 返回:* 蓝牙绑定状态
*/
public int getBondState();
应用阐明:
1、须要 BLUETOOTH 权限。
蓝牙绑定状态:
名称 | 值(int) | 含意 |
---|---|---|
BOND_NONE | 10 | 近程设施未绑定。 |
BOND_BONDING | 11 | 正在与近程设施进行绑定。 |
BOND_BONDED | 12 | 近程设施已绑定。 |
(4)绑定近程设施
办法定义:
/**
* 作用:* 开始与近程蓝牙设施的绑定过程。* 参数:* 无
* 返回:* 若胜利开始绑定则返回 true,否则返回 false。*/
public boolean createBond();
应用阐明:
1、须要 BLUETOOTH_ADMIN 权限。
2、这是一个异步调用,它会立刻返回。注册监听 ACTION_BOND_STATE_CHANGED 播送,当绑定过程实现时及时获取其后果告诉。
3、Android 零碎服务将解决必要的用户交互以确认并实现绑定过程。
(5)创立不平安的蓝牙套接字
办法定义:
/**
* 作用:* 创立不平安的蓝牙套接字。* 参数:* uuid:用于查找 RFCOMM 通道的服务记录 UUID
* 返回:* 一个筹备好外界连贯的 RFCOMM 蓝牙服务端口
*/
public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid);
应用阐明:
1、须要 BLUETOOTH 权限。
2、通信渠道将不会有认证的链接密钥,即它将受到中间人攻打。
3、对于蓝牙 2.1 设施,链接将被加密,因为加密是强制性的。对于旧设施(蓝牙 2.1 之前的设施),链接不会被加密。
4、它旨在与 listenUsingInsecureRfcommWithServiceRecord(String, UUID)用于对等蓝牙利用。
5、呈现谬误时,例如蓝牙不可用、权限有余,将抛出 IOException 异样。
(6)创立平安的蓝牙套接字
办法定义:
/**
* 作用:* 创立平安的蓝牙套接字。* 参数:* uuid:用于查找 RFCOMM 通道的服务记录 UUID
* 返回:* 一个筹备好外界连贯的 RFCOMM 蓝牙服务端口
*/
public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid);
应用阐明:
1、须要 BLUETOOTH 权限。
2、只有通过身份验证的套接字链接才能够应用此套接字。认证是指认证链路密钥,以避免中间人攻打。
3、此套接字上的通信将被加密。
4、这是为与对等蓝牙应用程序 listenUsingRfcommWithServiceRecord(String, UUID)配合应用而设计的。
5、呈现谬误时,例如蓝牙不可用、权限有余,将抛出 IOException 异样。
<br/>
3、BluetoothServerSocket 类
BluetoothServerSocket 是一个侦听蓝牙服务套接字。应用 BluetoothServerSocket 能够创立一个监听服务端口, 应用 accept 办法阻塞, 当该办法监测到连贯的时候, 就会返回一个 BluetoothSocket 对象来治理这个连贯。BluetoothServerSocket 是线程平安的,close 办法始终会立刻停止正在进行的操作并敞开蓝牙服务套接字。须要 BLUETOOTH 权限。
(1)阻塞期待连贯(无超时)
办法定义:
/**
* 作用:* 阻塞直到建设连贯。* 参数:* 无
* 返回:* 胜利连贯时连贯的 BluetoothSocket 对象。*/
public BluetoothSocket accept();
应用阐明:
1、一旦这个调用返回,它能够被再次调用来承受后续的传入连贯。
2、close()可用于从另一个线程停止此调用。
3、产生谬误时,例如该调用被停止、超时,将抛出 IOException 异样。
(2)阻塞期待连贯(有超时)
办法定义:
/**
* 作用:* 阻塞直到建设连贯或超时。* 参数:* timeout:阻塞期待的超时工夫。* 返回:* 胜利连贯时连贯的 BluetoothSocket 对象。*/
public BluetoothSocket accept(int timeout);
应用阐明:
1、一旦这个调用返回,它能够被再次调用来承受后续的传入连贯。
2、close()可用于从另一个线程停止此调用。
3、产生谬误时,例如该调用被停止、超时,将抛出 IOException 异样。
(3)敞开
办法定义:
/**
* 作用:* 敞开该监听服务端口,并开释所有关联的资源。* 参数:* 无
* 返回:* 无
*/
public void close();
应用阐明:
1、该办法将导致其余线程在此套接字上阻塞的调用立刻引发 IOException 异样。
2、敞开这个端口不会敞开 accept()办法返回的 BluetoothSocket 对象。
3、该办法调用呈现问题,将抛出 IOException 异样。
<br/>
4、BluetoothSocket 类
BluetoothSocket 是蓝牙套接口。在服务器端,应用 BluetoothServerSocket 创立侦听服务器套接字。当连贯被 BluetoothServerSocket 承受时,它将返回一个新的 BluetoothSocket 来治理连贯。在客户端,应用单个 BluetoothSocket 来启动连贯并治理连贯。最常见的蓝牙套接字类型是 RFCOMM,它是 Android API 反对的类型。RFCOMM 是一种通过蓝牙实现的面向连贯的流媒体传输。它也被称为串口行为规范(SPP)。BluetoothSocket 是线程平安的,close 办法始终会立刻停止正在进行的操作并敞开套接字。须要 BLUETOOTH 权限。
(1)连贯
办法定义:
/**
* 作用:* 尝试连贯到近程蓝牙服务器。* 参数:* 无
* 返回:* 无
*/
public void connect();
应用阐明:
1、此办法将阻塞,直到建设连贯或连贯失败。如果此办法没有异样返回,则此套接字当初已连贯。
2、该办法调用呈现问题时,例如连贯失败,将抛出 IOException 异样。
(2)是否连贯
办法定义:
/**
* 作用:* 获取此套接字的连贯状态,即是否与近程蓝牙服务连贯。* 参数:* 无
* 返回:* 若连贯则返回 true,否则返回 false。*/
public boolean isConnected();
应用阐明:
无
(3)获取近程蓝牙设施
办法定义:
/**
* 作用:* 获取此套接字连贯的近程蓝牙设施。* 参数:* 无
* 返回:* 连贯的近程蓝牙设施 BluetoothDevice 对象。*/
public BluetoothDevice getRemoteDevice();
应用阐明:
无
(4)获取输出流
办法定义:
/**
* 作用:* 获取与此套接字关联的输出流。* 参数:* 无
* 返回:* 输出流对象。*/
public InputStream getInputStream();
应用阐明:
1、即便套接字尚未连贯,输出流也会返回,但对该流的操作将抛出 IOException 异样,直到关联的套接字连贯。
2、该办法调用出错时,将抛出 IOException 异样。
3、通过此办法获取的输出流对象,能够读取对端发送的数据。
(5)获取输入流
办法定义:
/**
* 作用:* 获取与此套接字关联的输入流。* 参数:* 无
* 返回:* 输入流对象。*/
public OutputStream getOutputStream();
应用阐明:
1、即便套接字尚未连贯,输入流也会返回,但对该流的操作将抛出 IOException 异样,直到关联的套接字连贯。
2、该办法调用出错时,将抛出 IOException 异样。
3、通过此办法获取的输入流对象,能够发送数据给对端。
(6)敞开
办法定义:
/**
* 作用:* 敞开此流并开释与其关联的所有系统资源。如果流曾经敞开,则调用此办法不起作用。* 参数:* 无
* 返回:* 无
*/
public void close();
应用阐明:
1、该办法调用呈现问题,将抛出 IOException 异样。
<br/>
三、经典蓝牙开发流程
1、经典蓝牙开发流程剖析
2、蓝牙服务端实现
(1)在工程清单文件 AndroidManifest.xml 中增加权限:
<!-- 如果应用了 BLUETOOTH_ADMIN 权限,那么必须应用 BLUETOOTH 权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!--android6.0 后须要搜寻周边蓝牙设施,须要增加以下两个权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- 要求设施硬件必须反对蓝牙 -->
<uses-feature android:name="android.hardware.bluetooth" android:required="true"/>
(2)获取本地蓝牙适配器:
BluetoothAdapter mAdapter= BluetoothAdapter.getDefaultAdapter();
(3)关上蓝牙:
// 形式一:通过 Intent 来向用户弹框申请关上蓝牙,能够重写 onActivityResult 来监听关上蓝牙的申请后果
// 关上蓝牙
public void openBluetooth(){if(mBluetoothAdapter==null){
// 自定义办法,用来往 TextView 上增加提示信息
showTip("以后设施不反对蓝牙性能!");
return;
}
if(mBluetoothAdapter.isEnabled()){showTip("蓝牙已关上");
return;
}
Intent intent=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent,GlobalDef.REQ_CODE_OPEN_BT);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);
if(requestCode==GlobalDef.REQ_CODE_OPEN_BT){if(resultCode == Activity.RESULT_OK){showTip("蓝牙关上胜利");
}
else{showTip("蓝牙关上失败");
}
}
}
// 形式二:通过 enable 办法静默关上蓝牙,无需用户批准(局部 Android 零碎应用该办法仍然会弹框提醒,向用户申请关上蓝牙)mBluetoothAdapter.enable();
(4)敞开蓝牙
// 敞开蓝牙,无需用户批准(局部 Android 零碎应用该办法仍然会弹框提醒)mBluetoothAdapter.disable();
(5)容许蓝牙可见:
// 形式一:通过 Intent 形式向用户申请容许蓝牙被搜寻
// 注:如果蓝牙没有开启,用户点击确定后,会首先开启蓝牙,继而设置蓝牙能被扫描
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
// 设置蓝牙可见性的工夫,默认持续时间为 120 秒,每个申请的最长持续时间下限为 300 秒
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120);
startActivity(intent);
// 形式二:通过反射的形式来设置蓝牙可见性,且不会呈现弹框
// 注:如果蓝牙没有开启,通过此形式并不会间接关上蓝牙
/**
* 设置蓝牙可见
* @param adapter
* @param timeout 超时为 0 时,永恒可见
*/
public static void setDiscoverableTimeout(BluetoothAdapter adapter, int timeout) {//BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter();
try {Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class);
setDiscoverableTimeout.setAccessible(true);
Method setScanMode =BluetoothAdapter.class.getMethod("setScanMode", int.class,int.class);
setScanMode.setAccessible(true);
setDiscoverableTimeout.invoke(adapter, timeout);
setScanMode.invoke(adapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,timeout);
} catch (Exception e) {e.printStackTrace();
}
}
/**
* 敞开蓝牙可见
* @param adapter
*/
public static void closeDiscoverableTimeout(BluetoothAdapter adapter) {
try {Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class);
setDiscoverableTimeout.setAccessible(true);
Method setScanMode =BluetoothAdapter.class.getMethod("setScanMode", int.class,int.class);
setScanMode.setAccessible(true);
setDiscoverableTimeout.invoke(adapter, 1);
setScanMode.invoke(adapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE,1);
} catch (Exception e) {e.printStackTrace();
}
}
(6)创立蓝牙服务套接字,期待其余蓝牙客户端连贯:
try{mSocketList=new LinkedList<BluetoothSocket>();// 用来治理连贯的蓝牙套接字
mExecutorService= Executors.newCachedThreadPool();// 创立线程池
// 创立蓝牙服务端
mServerSocket=mBluetoothAdapter.listenUsingRfcommWithServiceRecord("BluetoothTool", GlobalDef.BT_UUID);
mServerRunningFlag=true;
showTip("蓝牙服务端胜利启动");
new Thread(){
@Override
public void run(){
try{
BluetoothSocket socket=null;
// 循环期待蓝牙 socket 连贯
while(mServerRunningFlag){socket=mServerSocket.accept();// 阻塞式
mSocketList.add(socket);
//SocketThread 为自定义的线程类,用于治理 BluetoothSocket 的读写操作
mExecutorService.execute(new SocketThread(socket));
}
}catch (Exception e){e.printStackTrace();
}
}
}.start();}catch(IOException e){e.printStackTrace();
showTip("服务端启动出现异常");
Log.e(TAG,"runServer IOException");
}
(7)连贯胜利之后,就通过获取 BluetoothSocket 的输入输出流进行数据传输:
// 获取流
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
// 写出、读入
byte[] temp=new byte[1024];
inputStream.read(temp);// 当无数据时将阻塞期待
outputStream.write(temp);
(8)以下为用于操作 BluetoothSocket 的 SocketThread 的简略实现,仅供参考:
class SocketThread extends Thread {
private BluetoothSocket mSocket=null;
private InputStream mIn;
private OutputStream mOut;
private boolean isOpen = false;
public SocketThread(BluetoothSocket socket) {
try {
mSocket=socket;
mIn = mSocket.getInputStream();
mOut = mSocket.getOutputStream();
isOpen = true;
Log.d(TAG, "a socket thread create");
} catch (IOException e) {e.printStackTrace();
Log.e(TAG, "create SocketThread fail");
}
}
@Override
public void run() {
int readLen=0;
byte[] buffer=new byte[1024];
try{while(isOpen){readLen=mIn.read(buffer);
if(readLen>0){Log.i(TAG,"read data length="+readLen);
Log.i(TAG,"read data hex ="+StringUtil.bytesToHexString(buffer,0,readLen));
}
}
}catch (Exception e){e.printStackTrace();
release();}
}
/**
* 写入数据
* @param data
* @param offset
* @param len
*/
public void writeData(byte[] data,int offset,int len){if (data == null || offset<0 || len<=0 || (len+offset)>data.length) {Log.e(TAG,"BT writeData params fail");
return;
}
try {mOut.write(data,offset,len);
mOut.flush();} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();}
}
public void release(){Log.d(TAG,"A socketThread release");
try{
isOpen=false;
if(mOut!=null){
try{mOut.close();
}catch (Exception e){e.printStackTrace();
}
mOut=null;
}
if(mIn!=null){
try{mIn.close();
}catch (Exception e){e.printStackTrace();
}
mIn=null;
}
if(mSocket!=null){
try{mSocket.close();
}catch (Exception e){e.printStackTrace();
}
mSocket=null;
}
}catch (Exception e){e.printStackTrace();
}
}
}
3、蓝牙客户端实现
(1)增加权限,同蓝牙服务端。
(2)获取本地蓝牙适配器,同蓝牙服务端。
(3)关上蓝牙,同蓝牙服务端。
(4)敞开蓝牙,同蓝牙服务端。
(5)容许蓝牙可见,同蓝牙服务端。
(6)定义蓝牙播送接收器,用于接管蓝牙搜寻、连贯状态扭转等的播送:
class BluetoothBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent){String action=intent.getAction();
Log.d(TAG,"Action received is"+action);
// 蓝牙搜寻
if(BluetoothDevice.ACTION_FOUND.equals(action)){BluetoothDevice scanDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if(scanDevice == null || scanDevice.getName() == null){return;}
int btType=scanDevice.getType();
if(btType==BluetoothDevice.DEVICE_TYPE_LE || btType==BluetoothDevice.DEVICE_TYPE_UNKNOWN){return;}
Log.d(TAG, "bt name="+scanDevice.getName()+"address="+scanDevice.getAddress());
// 将搜寻到的蓝牙设施退出列表
deviceList.add(scanDevice);
short rssi=intent.getExtras().getShort(BluetoothDevice.EXTRA_RSSI);
rssiList.add(rssi);
listAdapter.notifyDataSetChanged();// 告诉 ListView 适配器更新}
// 蓝牙配对
else if(BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)){BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if(mCurDevice!=null && btDevice.getAddress().equals(mCurDevice.getAddress())){int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
if(state==BluetoothDevice.BOND_NONE){showTip("已勾销与设施" + btDevice.getName() + "的配对");
mFlag=-1;
}
else if(state==BluetoothDevice.BOND_BONDED){showTip("与设施" + btDevice.getName() + "配对胜利");
mFlag=1;
}
}
}
else if(BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)){int blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
switch (blueState) {
case BluetoothAdapter.STATE_TURNING_ON:
Log.i(TAG,"onReceive---------STATE_TURNING_ON");
break;
case BluetoothAdapter.STATE_ON:
Log.i(TAG,"onReceive---------STATE_ON");
showTip("蓝牙以后状态:ON");
break;
case BluetoothAdapter.STATE_TURNING_OFF:
Log.i(TAG,"onReceive---------STATE_TURNING_OFF");
break;
case BluetoothAdapter.STATE_OFF:
Log.i(TAG,"onReceive---------STATE_OFF");
showTip("蓝牙以后状态:OFF");
break;
}
}
}
}
(7)注册播送:
mBluetoothBroadcastReceiver=new BluetoothBroadcastReceiver();
IntentFilter filter=new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(mBluetoothBroadcastReceiver,filter);
(8)搜寻周边蓝牙设施:
if(mBluetoothAdapter.isDiscovering()){mBluetoothAdapter.cancelDiscovery();
}
// 搜寻到的蓝牙设施通过播送接管
mBluetoothAdapter.startDiscovery();
(9)建设与蓝牙服务器的连贯:
/**
* 蓝牙配对并连贯
*/
public void bondAndConnect(BluetoothDevice mCurDevice){
// 勾销搜寻
if(mBluetoothAdapter.isDiscovering()){mBluetoothAdapter.cancelDiscovery();
}
if(mCurDevice==null){showTip("近程蓝牙设施为空!");
return;
}
// 以后蓝牙设施未配对,则先进行配对
if(mCurDevice.getBondState()==BluetoothDevice.BOND_NONE){Log.d(TAG,"create bond to"+mCurDevice.getName());
boolean nRet= BluetoothUtil.createBond(mCurDevice);
if(!nRet){showTip("createBond fail!");
return;
}
showLoadingDialog("正在与【"+mCurDevice.getName()+"】进行配对...");
mFlag=0;
while(mFlag==0){SystemClock.sleep(250);
}
if(mFlag==-1){showTip("与【"+mCurDevice.getName()+"】的蓝牙配对失败");
dismissLoadingDialog();
return;
}
}
if(mCurDevice.getBondState()==BluetoothDevice.BOND_BONDED){showLoadingDialog("正在与【"+mCurDevice.getName()+"】进行连贯...");
try {
// 创立 Socket
BluetoothSocket socket = mCurDevice.createRfcommSocketToServiceRecord(GlobalDef.BT_UUID);
// 连贯蓝牙服务套接字
socket.connect();
mThread=new SocketThread(socket);
mThread.start();
showTip(("胜利与【"+mCurDevice.getName()+"】建设连贯"));
} catch (IOException e) {Log.d(TAG,"socket connect fail");
showTip(("连贯【"+mCurDevice.getName()+"】失败"));
e.printStackTrace();}
}
dismissLoadingDialog();}
(10)连贯胜利之后,就通过输入输出流进行数据传输,同蓝牙服务端。
<br/>
四、注意事项与常见问题
1、Android6.0 当前,搜寻蓝牙设施须要地位权限(危险权限,须要动静申请)。
2、高版本 Android 零碎上进行蓝牙搜寻时,除了动静申请地位权限外,有的可能还须要手动关上设施的地位信息,否则无奈搜寻蓝牙。
3、搜寻周边蓝牙设施时,本机并不需要处于蓝牙可见状态。但其余设施必须处于蓝牙可见状态,本机才能够搜寻到。已知蓝牙设施(处于无效范畴内)的 MAC 地址,则随时能够向其发动连贯,而无需关上蓝牙可见性。
4、如果尚未在设施上启用蓝牙,则启用设施可检测性将会主动启用蓝牙。
5、搜寻周边设备对于蓝牙适配器而言是一个十分沉重的操作过程,并且会耗费大量资源。在找到要连贯的设施后,确保始终应用 cancelDiscovery() 进行发现,而后再尝试连贯。此外,如果曾经放弃与某台设施的连贯,那么执行搜寻操作可能会大幅缩小可用于该连贯的带宽,因而不应该在处于连贯状态时执行搜寻周边蓝牙的操作。
6、在调用 connect() 时,应始终确保设施未在进行搜寻操作。如果正在进行搜寻操作,则会大幅升高连贯尝试的速度,并减少连贯失败的可能性。
7、搜寻周边设备,通过播送可能获取到周边蓝牙设施的名称、mac 地址、rssi 信号强度等。
8、BluetoothSocket 是线程平安的, close()办法会终止 BluetoothSocket 进行的所有操作, 并且同时会敞开连贯。
9、经典蓝牙是通过流来进行数据收发的,对流进行数据读取操作时,因为接管方不晓得音讯之间的界线,不晓得一次性提取多少字节的数据,因此容易产生数据粘包问题。对于这种问题,能够给每个残缺的数据包增加一个起始符与结束符,那么接管方就能够确定须要读取并解决的数据范畴。
<br/>
五、附录
- 深刻理解 Android 蓝牙 Bluetooth——《根底篇》
- Android 经典蓝牙开发
- Android 蓝牙开发—经典蓝牙具体开发流程
- 蓝牙概览