Android 蓝牙通信的开发与使用
1、使用蓝牙,应该先理清一个使用的基本流程,大致如下:
- 扫描其他蓝牙设备
- 查询本地蓝牙适配器的配对蓝牙设备
- 建立 RFCOMM 通道
- 与其他设备进行双向数据传输
- 管理多个连接
2、明确了蓝牙的基本使用流程,我们就要了解一下我们实际代码中常用的一些类和方法(API里已经讲的很清楚了):
BluetoothAdapter
表示本地蓝牙适配器(蓝牙无线装置)。 BluetoothAdapter
是所有蓝牙交互的入口点。 利用它可以发现其他蓝牙设备,查询绑定(配对)设备的列表,使用已知的 MAC 地址实例化 BluetoothDevice
,以及创建 BluetoothServerSocket
以侦听来自其他设备的通信。
BluetoothDevice
表示远程蓝牙设备。利用它可以通过 B
luetoothSocket
请求与某个远程设备建立连接,或查询有关该设备的信息,例如设备的名称、地址、类和绑定状态等。
BluetoothSocket
表示蓝牙套接字接口(与 TCP S
ocket
相似)。这是允许应用通过 InputStream 和 OutputStream 与其他蓝牙设备交换数据的连接点。
BluetoothServerSocket
表示用于侦听传入请求的开放服务器套接字(类似于 TCP S
erverSocket
)。 要连接两台 Android 设备,其中一台设备必须使用此类开放一个服务器套接字。 当一台远程蓝牙设备向此设备发出连接请求时, BluetoothServerSocket
将会在接受连接后返回已连接的 BluetoothSocket
。
BluetoothClass
描述蓝牙设备的一般特征和功能。 这是一组只读属性,用于定义设备的主要和次要设备类及其服务。 不过,它不能可靠地描述设备支持的所有蓝牙配置文件和服务,而是适合作为设备类型提示。
BluetoothProfile
表示蓝牙配置文件的接口。 蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。 免提配置文件便是一个示例。 如需了解有关配置文件的详细讨论,请参阅使用配置文件
BluetoothHeadset
提供蓝牙耳机支持,以便与手机配合使用。 其中包括蓝牙耳机和免提(1.5 版)配置文件。
BluetoothA2dp
定义高质量音频如何通过蓝牙连接和流式传输,从一台设备传输到另一台设备。“A2DP”代表高级音频分发配置文件。
BluetoothHealth
表示用于控制蓝牙服务的健康设备配置文件代理。
BluetoothHealthCallback
用于实现 B
luetoothHealth
回调的抽象类。您必须扩展此类并实现回调方法,以接收关于应用注册状态和蓝牙通道状态变化的更新内容。
BluetoothHealthAppConfiguration
表示第三方蓝牙健康应用注册的应用配置,以便与远程蓝牙健康设备通信。
BluetoothProfile.ServiceListener
在 B
luetoothProfile
IPC 客户端连接到服务(即,运行特定配置文件的内部服务)或断开服务连接时向其发送通知的接口。
3、老规矩,少不了的权限:
<uses-permission android:name="android.permission.BLUETOOTH" />
4、接下来就是按照咱讲好的流程使用就是了:
- 设置蓝牙,先获取 BluetoothAdapter ,判断设备是否支持蓝牙
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
//不支持的话到这就拜拜咯
}
- 既然支持蓝牙,就该启动蓝牙了。调用 isEnabled() 以检查当前是否已启用蓝牙。 如果此方法返回 false,则表示蓝牙处于停用状态:
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
- 蓝牙都打开了,开始查找搜索,要发起连接,
BlueToothDevice
对象仅仅需要提供 MAC 地址。将保存为显示给用户的 ArrayAdapter 的一部分。 之后可提取该 MAC 地址,以便发起连接:
BlueToothDevice
对象仅仅需要提供 MAC 地址。将保存为显示给用户的 ArrayAdapter 的一部分。 之后可提取该 MAC 地址,以便发起连接:Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
for (BluetoothDevice device : pairedDevices) {
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
- 发现设备,都搜索了,也差不多该有结果了,这里需要调用 startDiscovery()。该方法会立即返回一个布尔值,指示是否已成功启动发现操作:
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
};
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
(必须针对 ACTION_FOUND
Intent 注册一个 BroadcastReceiver,以便接收每台发现的设备的相关信息。 针对每台设备,系统将会广播ACTION_FOUND
Intent。此 Intent 将携带额外字段 EXTRA_DEVICE
和 EXTRA_CLASS
,二者分别包含 BluetoothDevice
和 BluetoothClass
。要发起连接,BluetoothDevice
对象仅仅需要提供 MAC 地址。它将保存为显示给用户的 ArrayAdapter 的一部分。 之后可提取该 MAC 地址,以便发起连接。 您可以在有关连接设备的部分详细了解如何创建连接。)
- 开始连接:
要在两台设备上的应用之间创建连接,必须同时实现服务器端和客户端机制,因为其中一台设备必须开放服务器套接字,而另一台设备必须发起连接(使用服务器设备的 MAC 地址发起连接)。 当服务器和客户端在同一 RFCOMM 通道上分别拥有已连接的 BluetoothSocket
时,二者将被视为彼此连接。 这种情况下,每台设备都能获得输入和输出流式传输,并且可以开始传输数据,在有关管理连接的部分将会讨论这一主题。 本部分介绍如何在两台设备之间发起连接。服务器设备和客户端设备分别以不同的方法获得需要的 BluetoothSocket
。服务器将在传入连接被接受时收到套接字。 客户端将在其打开到服务器的 RFCOMM 通道时收到该套接字。
5、前文中有提到 管理多个连接,在成功连接多个设备后,每台设备都会有一个已连接的 BluetoothSocket。 这表示可以在设备之间共享数据。 利用BluetoothSocket,传输任意数据:
- 获取
InputStream
和 OutputStream
,二者分别通过套接字以及 getInputStream() 和 getOnputStream()
来处理数据传输。 - 使用
read(byte[])
和 write(byte[])
读取数据并写入到流式传输。
在这再贴个代码,这是一个手机连接一个蓝牙可操控的LED灯的小Demo,供大家参考:
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.ListView;
import android.widget.Toast;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* 蓝牙的操作流程
* 1 你的设备要支持蓝牙 然后打开蓝牙
* 2 扫描设备
* 3 连接设备
* 4 数据交换(通过 socket 传输流)
*/
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_ENABLE_BT = 1000;//启动蓝牙的请求码
private BluetoothAdapter mBluetoothAdapter;//蓝牙是入口
private MyBroadCastReciver myBroadCastReciver;//用于接收设备的广播
private List<BluetoothDevice> mList;//存放蓝牙设备的集合
private ListView listView;
private MyAdapter myAdapter;
private BluetoothDevice mBluetoothDevice;//将要连接的指定设备
private OutputStream outputStream;
private BluetoothSocket mBluethoothSocket;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();//获取默认的 蓝牙adapter
if (mBluetoothAdapter == null) {//如果返回空,代表设备不支持蓝牙
Toast.makeText(this, "什么破手机,连个蓝牙都不支持,给我一百万,我帮你砸了", Toast.LENGTH_SHORT).show();
return;
}
listView = (ListView) findViewById(R.id.listview);//用于显示所有蓝牙设备的列表
mList = new ArrayList<>();
myAdapter = new MyAdapter(mList, this);
listView.setAdapter(myAdapter);
IntentFilter mIntentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);//发现蓝牙设备的意图过滤器
mIntentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//开始扫描的广播
mIntentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//扫描结束的广播
myBroadCastReciver = new MyBroadCastReciver();
registerReceiver(myBroadCastReciver, mIntentFilter);//注册广播
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.startdis://开始扫描
mList.clear();//清理掉集合
if (!mBluetoothAdapter.isEnabled()) {// 如果蓝牙被禁用,应该要求用户启用蓝牙
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
// mBluetoothAdapter.enable();//启用蓝牙.但是不推荐这种方式
mBluetoothAdapter.startDiscovery();//启动扫描
break;
case R.id.openlight://开灯
//01 99 01 00 99 十六进制
sendControl(new byte[]{0x01, (byte) 0x99, 0x01, 0x00, (byte) 0x99});
break;
case R.id.closelight://关灯
//01 99 01 01 99 十六进制
sendControl(new byte[]{0x01, (byte) 0x99, 0x01, 0x01, (byte) 0x99});
break;
}
}
/**
* 发送指令
*
* @param bs
*/
private void sendControl(byte[] bs) {
//应该在子线程中执行
if (mBluetoothDevice != null) {//如果发现了指定的设备
try {
if (mBluethoothSocket == null) {//如果没有建立网络连接,就创建
mBluethoothSocket = mBluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
//开始交换数据
mBluethoothSocket.connect();//连接
//获取输出流
outputStream = mBluethoothSocket.getOutputStream();
}
if (outputStream != null) {
outputStream.write(bs);//写入数据
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 用于接收蓝牙设备的广播
*/
private class MyBroadCastReciver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//在这里处理收到的蓝牙设备即可
switch (intent.getAction()) {
case BluetoothDevice.ACTION_FOUND://匹配一个发现蓝牙设备的广播
BluetoothDevice mBluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);//通过意图获取到被发现的蓝牙设备
Log.e("自定义标签", "类名==MyBroadCastReciver" + "方法名==onReceive=====:" + "name===" + mBluetoothDevice.getName() + "add==" + mBluetoothDevice.getAddress());
if ("BRK05".equals(mBluetoothDevice.getName()) || "98:D3:31:20:7E:F1".equals(mBluetoothDevice.getAddress())) {
//如果是我想要的设备
MainActivity.this.mBluetoothDevice = mBluetoothDevice;
}
mList.add(mBluetoothDevice);
myAdapter.notifyDataSetChanged();
break;
case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
Toast.makeText(MainActivity.this, "开始扫描蓝牙", Toast.LENGTH_SHORT).show();
// Log.e("自定义标签", "类名==MyBroadCastReciver" + "方法名==onReceive=====:" + "开始扫描蓝牙");
break;
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
Toast.makeText(MainActivity.this, "蓝牙扫描结束", Toast.LENGTH_SHORT).show();
// Log.e("自定义标签", "类名==MyBroadCastReciver" + "方法名==onReceive=====:" + "蓝牙扫描结束");
break;
}
}
}
/**
* 释放资源
*/
@Override
protected void onDestroy() {
unregisterReceiver(myBroadCastReciver);
outputStream = null;
mBluethoothSocket = null;
mBluetoothDevice = null;
myBroadCastReciver = null;
super.onDestroy();
}
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
};
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
(必须针对 ACTION_FOUND
Intent 注册一个 BroadcastReceiver,以便接收每台发现的设备的相关信息。 针对每台设备,系统将会广播ACTION_FOUND
Intent。此 Intent 将携带额外字段 EXTRA_DEVICE
和 EXTRA_CLASS
,二者分别包含 BluetoothDevice
和 BluetoothClass
。要发起连接,BluetoothDevice
对象仅仅需要提供 MAC 地址。它将保存为显示给用户的 ArrayAdapter 的一部分。 之后可提取该 MAC 地址,以便发起连接。 您可以在有关连接设备的部分详细了解如何创建连接。)
- 开始连接:
要在两台设备上的应用之间创建连接,必须同时实现服务器端和客户端机制,因为其中一台设备必须开放服务器套接字,而另一台设备必须发起连接(使用服务器设备的 MAC 地址发起连接)。 当服务器和客户端在同一 RFCOMM 通道上分别拥有已连接的 BluetoothSocket
时,二者将被视为彼此连接。 这种情况下,每台设备都能获得输入和输出流式传输,并且可以开始传输数据,在有关管理连接的部分将会讨论这一主题。 本部分介绍如何在两台设备之间发起连接。服务器设备和客户端设备分别以不同的方法获得需要的 BluetoothSocket
。服务器将在传入连接被接受时收到套接字。 客户端将在其打开到服务器的 RFCOMM 通道时收到该套接字。
5、前文中有提到 管理多个连接,在成功连接多个设备后,每台设备都会有一个已连接的 BluetoothSocket。 这表示可以在设备之间共享数据。 利用BluetoothSocket,传输任意数据:
- 获取
InputStream
和 OutputStream
,二者分别通过套接字以及 getInputStream() 和 getOnputStream()
来处理数据传输。 - 使用
read(byte[])
和 write(byte[])
读取数据并写入到流式传输。
InputStream
和 OutputStream
,二者分别通过套接字以及 getInputStream() 和 getOnputStream()
来处理数据传输。read(byte[])
和 write(byte[])
读取数据并写入到流式传输。在这再贴个代码,这是一个手机连接一个蓝牙可操控的LED灯的小Demo,供大家参考:
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.ListView;
import android.widget.Toast;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* 蓝牙的操作流程
* 1 你的设备要支持蓝牙 然后打开蓝牙
* 2 扫描设备
* 3 连接设备
* 4 数据交换(通过 socket 传输流)
*/
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_ENABLE_BT = 1000;//启动蓝牙的请求码
private BluetoothAdapter mBluetoothAdapter;//蓝牙是入口
private MyBroadCastReciver myBroadCastReciver;//用于接收设备的广播
private List<BluetoothDevice> mList;//存放蓝牙设备的集合
private ListView listView;
private MyAdapter myAdapter;
private BluetoothDevice mBluetoothDevice;//将要连接的指定设备
private OutputStream outputStream;
private BluetoothSocket mBluethoothSocket;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();//获取默认的 蓝牙adapter
if (mBluetoothAdapter == null) {//如果返回空,代表设备不支持蓝牙
Toast.makeText(this, "什么破手机,连个蓝牙都不支持,给我一百万,我帮你砸了", Toast.LENGTH_SHORT).show();
return;
}
listView = (ListView) findViewById(R.id.listview);//用于显示所有蓝牙设备的列表
mList = new ArrayList<>();
myAdapter = new MyAdapter(mList, this);
listView.setAdapter(myAdapter);
IntentFilter mIntentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);//发现蓝牙设备的意图过滤器
mIntentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//开始扫描的广播
mIntentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//扫描结束的广播
myBroadCastReciver = new MyBroadCastReciver();
registerReceiver(myBroadCastReciver, mIntentFilter);//注册广播
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.startdis://开始扫描
mList.clear();//清理掉集合
if (!mBluetoothAdapter.isEnabled()) {// 如果蓝牙被禁用,应该要求用户启用蓝牙
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
// mBluetoothAdapter.enable();//启用蓝牙.但是不推荐这种方式
mBluetoothAdapter.startDiscovery();//启动扫描
break;
case R.id.openlight://开灯
//01 99 01 00 99 十六进制
sendControl(new byte[]{0x01, (byte) 0x99, 0x01, 0x00, (byte) 0x99});
break;
case R.id.closelight://关灯
//01 99 01 01 99 十六进制
sendControl(new byte[]{0x01, (byte) 0x99, 0x01, 0x01, (byte) 0x99});
break;
}
}
/**
* 发送指令
*
* @param bs
*/
private void sendControl(byte[] bs) {
//应该在子线程中执行
if (mBluetoothDevice != null) {//如果发现了指定的设备
try {
if (mBluethoothSocket == null) {//如果没有建立网络连接,就创建
mBluethoothSocket = mBluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
//开始交换数据
mBluethoothSocket.connect();//连接
//获取输出流
outputStream = mBluethoothSocket.getOutputStream();
}
if (outputStream != null) {
outputStream.write(bs);//写入数据
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 用于接收蓝牙设备的广播
*/
private class MyBroadCastReciver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//在这里处理收到的蓝牙设备即可
switch (intent.getAction()) {
case BluetoothDevice.ACTION_FOUND://匹配一个发现蓝牙设备的广播
BluetoothDevice mBluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);//通过意图获取到被发现的蓝牙设备
Log.e("自定义标签", "类名==MyBroadCastReciver" + "方法名==onReceive=====:" + "name===" + mBluetoothDevice.getName() + "add==" + mBluetoothDevice.getAddress());
if ("BRK05".equals(mBluetoothDevice.getName()) || "98:D3:31:20:7E:F1".equals(mBluetoothDevice.getAddress())) {
//如果是我想要的设备
MainActivity.this.mBluetoothDevice = mBluetoothDevice;
}
mList.add(mBluetoothDevice);
myAdapter.notifyDataSetChanged();
break;
case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
Toast.makeText(MainActivity.this, "开始扫描蓝牙", Toast.LENGTH_SHORT).show();
// Log.e("自定义标签", "类名==MyBroadCastReciver" + "方法名==onReceive=====:" + "开始扫描蓝牙");
break;
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
Toast.makeText(MainActivity.this, "蓝牙扫描结束", Toast.LENGTH_SHORT).show();
// Log.e("自定义标签", "类名==MyBroadCastReciver" + "方法名==onReceive=====:" + "蓝牙扫描结束");
break;
}
}
}
/**
* 释放资源
*/
@Override
protected void onDestroy() {
unregisterReceiver(myBroadCastReciver);
outputStream = null;
mBluethoothSocket = null;
mBluetoothDevice = null;
myBroadCastReciver = null;
super.onDestroy();
}
}