一、蓝牙概述
蓝牙协议分4层:核心协议层,电缆替代协议层,电话控制协议层,采纳的其他协议层。最重要的是核心协议,包括基带,链路管理(LMP 负责蓝牙组件间连接的建立 设备的搜索配对连接),逻辑链路控制和适应协议(L2CAP 位于基带协议层上,属于数据链路层,是一个为高层传输和应用层协议屏蔽基带协议的适配协议。
二、打开和关闭蓝牙设备
方式1:请求打开
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, 1);
会出现蓝牙请求权限对话框
方式2:静默打开
要先在配置文件里添加权限
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
// 设备不支持蓝牙
}
adapter.enable(): //打开
adapter.disable(); //关闭
三、搜索蓝牙设备
前提:必须先打开蓝牙模块
PS. 被动搜索:还要打开可见性
/* 使自己的蓝牙设备可见 */
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
// 可自定义可见时间(s),最大长度为3600。最小为0,表示始终可见。默认120,超过3600会被设为120
// 请求可见性的方法
startActivityForResult(discoverableIntent);
3.1. 查询已配对设备
在搜索设备之前,要查询已配对的设备集,看看想要连接的设备是否已经配对。
// 获取已配对的设备集合
Set pairedDevices = mBluetoothAdapter.getBondedDevices();
// 如果有已配对的设备
if (pairedDevices.size() > 0) {
// 遍历已配对设备
for (BluetoothDevice device : pairedDevices) {
// 把他们的名字和地址加入数组
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
3.2. 发现设备
发现蓝牙设备,执行startDiscovery()方法。该过程是异步的,该方法将会立刻返回一个布尔值表明搜索是否已经开始。通常情况下,该搜索的过程调用12秒钟的查询,随后返回找到的设备。搜索到的设备通过广播回传
/* stap1. 定义和注册广播接收器 */
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
// FOUND: 找到一个设备,会发送一个广播
this.registerReceiver(receiver, filter);
filter = new IntentFilter(BluetoothAdapter.ACTION_FINISHED);
// FINISHED: 搜索结束,发送一个广播
this.registerReceiver(receiver, filter);
//记得在onDestroy()中反注销
/* step2. 开启搜索 */
public void onClick_Search(View view){
if(myBluetoothAapter.isDiscovering()){
//如果正在搜索,要停止。因为startDiscovery()不能重复调用
myBluetoothAapter.cancelDiscovery();
}
myBluetoothAapter.startDiscovery();
}
/* step3. 重写接收器的onReceive方法 */
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@override
public void onReceiver(Context context, Intent intent) {
String action = intent.getAction();
//当发现一个设备时
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
// 判断设备是否已存在于已配对的设备集中
if(BluetoothDevice.ACTION_FOUND.equals(action)){
if(device.getBondState() != BluetoothDevice.BOND_BONDED){
//将设备名称和地址添加到数组
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){
//...搜索完成的 其他处理...
}
}
}
}
四、连接蓝牙设备
使用BluetoothSocket和BluetoothServerSocket实现。
服务器端和客户端通过不同的方式获得BluetoothSocket。当一个连接接受(accept)的时候服务器端接收BluetoothSocket。而客户端则通过打开服务器端的RFCOMM通道得到BluetoothSocket。
一种实现技术是,应用程序同时实现客户端和服务器端。因此,每一个服务器端的程序拥有一个server socket并监听连接。
另一种是在一个应用中实现服务器端的功能,而另一个应用中实现客户端的部分。
PS. 如果两个设备之前并没有配对过,那么Android的框架将会自动进行配对的请求通知。因此当尝试进行连接时,你的应用并不需要关心两台设备是否已经配对。你的RFCOMM连接将会被阻塞,直到用户成功配对,或因为用户拒绝配对而取消,或者配对失败以及超时等。
UUID说明
UUID(全局唯一标识符 Universally Unique Identifier)实际上是一个格式8-4-4-4-12的字符串
UUID相当于Socket的端口,蓝牙地址相当于Socket的IP
/* 定义需要的组件 */
private BluetoothAdapter myBluetoothAapter;
private List bluetoothDevice = new ArrayList();
private final UUID MY_UUID = UUID.fromString("a8a5f732-5fc5-49f6-b2e7-bc472e8797db");
// 字符串符合UUID的格式即可
private final String NAME = "Bluetooth_Socket";
private BluetoothSocket clientSocket;
private BluetoothDevice device;
private OutputStream os; //客户端往服务端的输出流
4.1. 客户端的实现
/* 客户端 列表单击事件为例 */
public void onItemClick(AdapterView> parent, View view, int position, long id){
String s = mArrayAdapter.getItem(position);
//解析蓝牙地址
String address = s.substring(s.indexOf(":") + 1).trim();
try{
// 依旧 如果正在搜索则取消搜索
if (myBluetoothAapter.isDiscovering()) {
myBluetoothAapter.cancelDiscovery();
}
try{
if(device == null){
device = myBluetoothAapter.getRemoteDevice(address);
}
if(clientSocket == null){
clientSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
// 开始连接
clientSocket.connect();
// 输出流
os = clientSocket.getOutputStream();
}
}catch(Exception e){
// 异常处理
}
if(os != null){
// 成功获取输出流
os.write("发送信息".getBytes("utf-8"));
}
}
}
4.2. 服务端的实现
/* 服务端 监听客户端的线程类 */
private Handler handler = new Handler(){
public void handleMessage(massage msg){
Toast.makeText(Main.this, String.valueOf(msg.obj), Toast.LENGTH_LONG).show();
super.handleMessage(msg);
}
};
private class AcceptThread extends Thread{
private BluetoothServerSocket serverSocket;
private BluetoothSocket socket;
private InputStream is;
private OutputStream os;
public AcceptThread(){
try{
serverSocket = myBluetoothAapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID)
}catch(Exception e){
// 异常处理
}
}
//在run()方法中具体截获蓝牙消息
public void run(){
try{
socket = serverSocket.accept();
is = socket.getInputStream();
os = socket.getOutputStream();
// 无限从客户端接受
while(true){
byte[] buffer = new byte[128];
int count = is.read(buffer);
Message msg = new Message();
msg.obj = new String(buffer, count, 0, "utf-8");
handler.sendMessage(msg);
}
}catch(Exception e){
// 异常处理
}
}
}
/* 在onCreat()里启动服务 */
acceptThread = new AcceptThread();
acceptThread.start();
四、问题总结
在实际项目里还是遇到了一些问题,记录如下:
4.1. 关于UUID
UUID的值必须是00001101-0000-1000-8000-00805F9B34FB。因为这个是android的API上面说明的,用于普通蓝牙适配器和android手机蓝牙模块连接。 一开始用了其他的,就连接不上了(┐「ε:)