一、官方简介:
- 对于在Android设备间执行比较耗电的操作,传统蓝牙(Classic Bluetooth)是一种正确的选择;
- 所有关于 Bluetooth的API都在android.bluetooth包下;
二、API学习:
BluetoothAdapter
这个类代表蓝牙适配器,并且是所有蓝牙交互的入口点,通过这个类,我们可以发现其他的蓝牙设备,查询已经配对的设备;
实例化一个BluetoothDevice,BluetoothDevice使用一个已知的MAC地址,并创建一个BluetoothServerSocket监听来自其他设备的通讯。BluetoothDevice
代表一个蓝牙设备,通它可以通过BluetoothSocket向其他的蓝牙设备发出连接请求;
通过它还可以设备的信息包括:设备名称、设备地址、类型、配对状态。BluetoothSocket
代表一个蓝牙套接字接口(类似于一个TCP套接字);
它是一个连接点,允许应用程序与另一个蓝牙设备交换数据通过InputStream OutputStream。BluetoothServerSocket
是一个开放的服务器套接字,侦听传入请求(类似于一个TCP ServerSocket);
为了连接两个Android设备,设备必须用这个类打开一个服务器套接字;
当一个远程蓝牙设备请求连接到这个设备,如果请求的连接被接受,那么BluetoothServerSocket将返回一个已经连接的BluetoothSocket对象。BluetoothClass
描述了一个蓝牙设备的一般特征和功能;
这是一套只读属性,定义设备的主要和次要设备级别和它的服务;
然而,这并不可靠地描述所有蓝牙配置文件和服务支持的设备,但是提示设备类型还是有用的。BluetoothProfile
代表蓝牙规范的一个接口;
BluetoothProfile是一套无线接口规范对于设备之间基本的蓝牙通信;BluetoothHeadset
为手机使用蓝牙耳机提供支持;
它包括了蓝牙耳机和免提(v1.5)规范。BluetoothA2dp
定义如何通过蓝牙连接将高质量的音频流从一个设备到另一个;
“A2DP(Advanced Audio Distribution Profile)”代表先进的蓝牙音频传输模型协定。BluetoothHealth
代表一个可以控制蓝牙设备的健康设备规范代理。BluetoothHealthCallback
一个抽象类,我们可以实现其抽象方法去接收应用程序内部注册状态和蓝牙通道状态的更新变化。BluetoothHealthAppConfiguration
代表了第三方健康应用设备去和其他的健康设备通信的配置。BluetoothProfile.ServiceListener
一个接口,通知BluetoothProfile IPC客户当他们连接或断开服务(即内部服务运行一个特定的配置文件)。
三、权限
AndroidManifest.xml:
注意:如果使用了BLUETOOTH_ADMIN权限,那么必须使用BLUETOOTH权限;
... >
"android.permission.BLUETOOTH" />
"android.permission.BLUETOOTH_ADMIN"/>
四、蓝牙设置
设备是否支持蓝牙
- 在您的应用程序可以通过蓝牙通信之前,你需要确认蓝牙设备上的支持,如果是这样,确保它是启用的;
- 如果不支持蓝牙,那么你应该优雅地禁用任何蓝牙功能。如果蓝牙支持,但禁用,那么你可以要求用户启用蓝牙不离开您的应用程序;
- 这个设置在两个步骤完成,使用BluetoothAdapter。
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// Device does not support Bluetooth
}
如果BluetoothAdapter.getDefaultAdapter()==null,那么说明这个设备不支持蓝牙。
蓝牙重命名
mBluetoothAdapter.setName(name);//本地蓝牙重命名
mBluetoothAdapter.getName();//获取本机蓝牙名
通过监听BluetoothAdpater.ACTION_LOCAL_NAME_CHANGED监听本机蓝牙名称的改变
如果设备支持蓝牙,那么启用蓝牙
//如果蓝牙被关闭
if (!mBluetoothAdapter.isEnabled()) {
//那么打开蓝牙
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
查询已经配对过的设备
Set pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
// Loop through paired devices
for (BluetoothDevice device : pairedDevices) {
// Add the name and address to an array adapter to show in a ListView
Log.d(TAG, "onClick:已配对设备 "+device.getName() + ":" + device.getAddress());
}
* 搜索其他设备 *
- 我们可以通过调用 startDiscovery() 来搜索新设备,这个过程是异步的,并且这个方法会立即返回boolean值说明此次搜索是否成功启动;
- 搜索过程大约12秒的扫描;
- 我们还需要实现一个广播接受者来接收被发现的设备的信息;
- 注意:startDiscovery()对于BluetoothAdapter来说是一项沉重的任务,会消耗大量的资源,所以如果我们尝试连接搜索到的新设备之前需要调用cancelDiscovery()来取消这一过程;
- 同样,如果你已经连接上一个设备,然后执行startDiscovery()将会显著减少可用的带宽连接,所以不应该在连接上设备的同时startDiscovery()。
//自定义广播接收者,监听搜索到的设备
// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// Add the name and address to an array adapter to show in a ListView
Log.d("发现的设备:",device.getName() + "\n" + device.getAddress());
}
}
};
// 注册
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
// 千万别忘记解除注册 during onDestroy
@Override
protected void onDestroy() {
unregisterReceiver(devicesReceiver);
super.onDestroy();
}
设置本机蓝牙可被其他设备检查到
- 如果想让我们的设备可以被其他设备检测到,需要执行以下代码;
- 可被检测时间通过intent设置
BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION
属性,如果不设置默认120秒的可被检测时间;- 可被检测时间下限为0(0意味着始终可以被检测到),上限为3600秒,如果低于0或者高于3600,那么系统默认设置它为120秒;
- 如果蓝牙没有打开而直接设置蓝牙的可检测性,那么蓝牙会被自动打开。
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
监听蓝牙可检测性模式的变化
- 需要注册一个广播接收者,action =
BluetoothAdapter.ACTION_SCAN_MODE_CHANGED
;- 接收到的intent里面的属性如下:
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
/*蓝牙可检测性状态接收者*/
public class BluetoothDiscoverableReceiver extends BroadcastReceiver {
private static final String TAG = "Bluetooth";
public BluetoothDiscoverableReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(action)){
//新的扫描模式
int scanMode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE,-100);
Log.d(TAG, "onReceive: 新的扫描模式"+scanMode);
switch (scanMode){
//设备既能被其他设备检测到,又能被其他设备连接
case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
break;
//设备不可被检测到,但是还是可以被之前发现过这个蓝牙的设备连接到
case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
break;
//设备既不能被其他设备检测到,又不能被其他设备连接
case BluetoothAdapter.SCAN_MODE_NONE:
break;
default:
break;
}
//老的扫描模式
int preScanMode = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_SCAN_MODE,-100);
Log.d(TAG, "onReceive: 老的扫描模式"+preScanMode);
}
}
}
连接设备
前戏:
- 客户端和服务端是通过BluetoothSocket来进行通信的,类似于TCP的Socket通信;
- 服务端接受了客户端的连接请求,那么服务端就得到一个和客户端通信的BluetoothSocket;
- 客户端得到BluetoothSocket是当客户端打开一个RFCOMM 通道到服务器的时候;
- 每台蓝牙设备都可以作为服务端,所以每台设备都有一个server socket来打开和监听连接;
- 同样,每台蓝牙设备都可以作为客户端来进行连接;
连接时作为服务端的实现:
- 服务端必须持有一个打开的BluetoothServerSocket,它的作用是监听客户端传入的连接请求,并且当请求被接受的时候提供一个BluetoothSocket对象返回给客户端;
- 当客户端获取到服务端提供的这个BluetoothSocket,那么服务端的BluetoothServerSocket可以(也应该)关闭了,除非服务端想要接受更多设备的连接;
- UUID(Universally Unique Identifier ):用来唯一地标识应用程序的蓝牙服务,客户端使用服务端的UUID来进行通信匹配,也就是说客户端和服务端用的UUID必须相同才能连接的上;
- 通过调用
BluetoothAdapter
的listenUsingRfcommWithServiceRecord(String, UUID)
来获取BluetoothServerSocket
,参数String代表这个服务的名称,可以是任意的字符串,一般用我们应用的名称;- 调用
BluetoothServerSocket的accept()
开启监听(和TCP的差不多),成功接收那么返回一个BluetoothSocket;BluetoothServerSocket的close()
可以关闭监听;BluetoothServerSocket or BluetoothSocket
需要放在子线程来进行管理,他们内部的方法都是线程安全的;
示例:
private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
// Use a temporary object that is later assigned to mmServerSocket,
// because mmServerSocket is final
BluetoothServerSocket tmp = null;
try {
// MY_UUID is the app's UUID string, also used by the client code 目前只能到配对成功这一步,无法连接上,应该是UUID不一样的问题
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) { }
mmServerSocket = tmp;
}
public void run() {
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
while (true) {
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
break;
}
// If a connection was accepted
if (socket != null) {
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(socket);
mmServerSocket.close();
break;
}
}
}
/** Will cancel the listening socket, and cause the thread to finish */
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) { }
}
}
连接时作为客户端的实现
- 我们上面讲了通过广播接收者可以获得其他蓝牙设备的BluetoothDevice对象,那么我们通过这个BluetoothDevice对象调用
createRfcommSocketToServiceRecord(UUID)
获取BluetoothSocket
,UUID和服务端的要一样;- 调用
BluetoothSocket
的connect()
方法进行连接,这个方法是个耗时方法,不能再主线程执行,连接失败或者12s超时都会抛出异常;- UUID的获取:如果你连接到蓝牙串口板(电脑)可以试着用这个固定的的SPP UUID: 0000 - 0000 - 1000 - 8000 - 1000 - f9b34fb。但是如果你(Android设备)是连接到一个Android设备请获取设备唯一的UUID;
- 区分配对和连接这是两种状态,最好先配对成功再进行连接,如果没有配对过那么Android系统也会提示配对;
/*配对代码*/
private void bond(BluetoothDevice device) {
try {
// 如果远程设备没有配对过
if (device.getBondState() == BluetoothDevice.BOND_NONE) {
//进行配对
device.createBond();
} else {
Log.d(TAG, "bond: 已经配对,无需重复配对");
}
} catch (Exception e) {
Log.d(TAG, "bond: 配对出错");
e.printStackTrace();
}
}
//设备间建立连接代码
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
// Use a temporary object that is later assigned to mmSocket,
// because mmSocket is final
BluetoothSocket tmp = null;
mmDevice = device;
// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
// MY_UUID is the app's UUID string, also used by the server code
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) { }
mmSocket = tmp;
}
public void run() {
// Cancel discovery because it will slow down the connection
mBluetoothAdapter.cancelDiscovery();
try {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
mmSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
try {
mmSocket.close();
} catch (IOException closeException) { }
return;
}
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(mmSocket);
}
/** Will cancel an in-progress connection, and close the socket */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
管理连接
- 如果两个设备成功连接,那么服务端和客户端都会得到一个BluetoothSocket对象,通过它我们就可以进行设备间的数据交互了;
- InputStream and OutputStream输入输出流控制socket之间的数据交互;
3.通过 read(byte[]) and write(byte[])往流里面读取和写入数据;
//连接成功 数据交互
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI activity
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
break;
}
}
}
/* Call this from the main activity to send data to the remote device */
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
/* Call this from the main activity to shutdown the connection */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
猛戳Demo下载:https://github.com/longshun/BluetoothDemo