前几天一个项目的一个功能是用蓝牙来交互数据,然后下载了一篇高分的demo,然后我自己花了四个小时全部敲了一遍和改造。然后今天终于空下来好好研究里面的代码,特地写这篇blog当做总结和记录本。
蓝牙与蓝牙的连接其实是有两个步骤,第一步是先搜索附近蓝牙然后主动去配对,配对成功之后再去连接。现在很多手机没有连接这个操作了,都用默认的去连接,这样如果我们不自己组织代码来管理的话就会遇到很多问题。比如我现在手边有一个手机,有两个蓝牙设备,并且这个手机跟两个设备都可以连接。如果我蓝牙开启之后,两个设备我都配对过,那么手机自动连上了其中一个设备了,我却想连的其实是另外一个设备。怎么办?其实可以把那个设备取消配对或者直接把那个设备之直接关机。。。。但是您不觉得这样,,心很累么?所以如果用到蓝牙的相关知识的话,最好是熟悉一下蓝牙相关的基本代码,最好是写个demo,以后用起来比较方便。
我将总共分两到三篇blog来讲解,当前这篇只讲一些基本知识和一些方法的应用场景和方法,然后第二篇讲蓝牙数据交互,第三篇附上demo讲解。
开始了!
(1)先检查手机是否支持蓝牙并且判断蓝牙是否已经开启,如果支持蓝牙却没有开启就跳转界面让用户自己开启。
/**
* 先通过系统服务获取BluetoothManager(蓝牙管理器),在通过管理器拿到BluetoothAdapter(蓝牙
* 适配器),然后判断手机是否支持蓝牙,如果支持蓝牙再进一步判断是否可用并且是否已经开启,没有
* 开启就跳转到让用户是否开启蓝牙的界面,我用mate8测试的时候,这个界面十分的“像提示框一样”,
* 值得注意的是,如果蓝牙已经开启了,那么调用下面的方法会没有反应
*/
BluetoothManager mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager != null) {
BluetoothAdapter mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter != null) {
//说明手机支持蓝牙
if (!mBluetoothAdapter.isEnabled()) {
//mBluetoothAdapter.isEnabled() 当蓝牙可用并且已经开启的时候返回true,
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
enableBtIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(enableBtIntent);
}
} else {
//手机不支持蓝牙
}
}
上面是跳转界面让用户来手动开启蓝牙,直接开关本机蓝牙的代码是:
mBluetoothAdapter.enable();//启用
mBluetoothAdapter.disable();//禁用
(2)当蓝牙开启之后就,进行监听蓝牙扫描附近设备的回调广播。自己写一个动态注册来接收这些事件发射出来的
广播。
下面是receiver类:
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
public class SearchBltBroadcastReceiver extends BroadcastReceiver {
private BluetoothDevice mDevice;
private OnBltReceiver mBlyReceiver;
private static final String TAG = "SearchBltReceiver";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Bundle bundle = intent.getExtras();
Object[] lstName = bundle.keySet().toArray();
for (int i = 0; i < lstName.length; i++) {
String keyName = lstName[i].toString();
Log.e(TAG, keyName + "》》》" + String.valueOf(bundle.get(keyName)));
}
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
//收到搜索到蓝牙的广播啦,经过测试发现同一台设备可能会被重复搜索到
mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (mBlyReceiver != null) {
mBlyReceiver.onSearchedNewDevice(mDevice);
}
} else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
//接收到当前手机的蓝牙状态改变时发出的广播
mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//下面的mBlyReceiver是对事件响应的接口回调的
switch (mDevice.getBondState()) {
case BluetoothDevice.BOND_BONDING:
Log.i(TAG, "收到正在配对的广播啦");
mBlyReceiver.onConnecting(mDevice);
break;
case BluetoothDevice.BOND_BONDED:
Log.i(TAG, "收到完成配对的广播啦");
mBlyReceiver.onConnected(mDevice);
break;
case BluetoothDevice.BOND_NONE:
Log.i(TAG, "收到取消配对的广播啦");
mBlyReceiver.onUnConnected(mDevice);
default:
break;
}
}
}
public void setOnBltReceiver(OnBltReceiver bltReceiver) {
this.mBlyReceiver = bltReceiver;
}
}
下面是一个接口,专门把广播的回调独立出来。其实开发中也是这样,能抽取的尽量抽取,让类里面的代码更加的少,然后更下清爽和清晰:
import android.bluetooth.BluetoothDevice;
/**
* @author ChenYe
* 蓝牙广播回调响应接口
*/
public interface OnBltReceiver {
/**
* 搜索到了新的蓝牙设备
*
* @param device 入参是搜索到的新设备
*/
void onSearchedNewDevice(BluetoothDevice device);
/**
* 配对中
*
* @param device 入参是正在配对的设备
*/
void onConnecting(BluetoothDevice device);
/**
* 刚刚配对成功或者刚刚连接成功
*
* @param device 入参是刚刚配对成功或者刚刚连接成功的设备
*/
void onConnected(BluetoothDevice device);
/**
* 未配对
*
* @param device 入参是未配对的设备
*/
void onUnConnected(BluetoothDevice device);
}
//动态监听:
SearchBltBroadcastReceiver receiver = new SearchBltBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
//搜索发现设备
intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
//状态改变
intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
//行动扫描模式改变了
intentFilter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
//动作状态发生了变化
intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(receiver, intentFilter);
//接的在关闭界面的时候取消监听广播
unregisterReceiver(receiver);
(3)蓝牙开启了、监听广播工作也做好了,开始启动搜索附近蓝牙了,搜索附近蓝牙十分的消耗电量,所以应该自己定义一个时间长度,在规定时间内 一直搜索设备,向外提供一个接口,用户可以在搜索到自己想要的设备之后可以主动的停止继续搜索,如果在指定时间长度内没有被主动停止,那么时间到了之后也主动停止搜索。此外最好是在界面上某个地方提示用户最好主动停止搜索。还有如果正在配对中或者正在连接中,启动搜索有可能导致 配对或者链接失败。
伪代码:
(3.1)检查手机是否支持蓝牙,如果支持的话再判断蓝牙是否已经开启了。可以把上面第一点的代码进行抽取。
(3.2)检查是否已经处于正在搜索的状态了,如果是就return或者关闭。
if(mBluetoothAdapter.isDiscovering()){
//说明已经是在搜索中了
}
(3.3)如果满足3.1 和3.2 就开始启动搜索
mBluetoothAdapter.startDiscovery();
4)当3操作完毕之后,就会陆续的接到本机发出的广播(搜索到设备),然后加载到集合,然后显示到列表上。然后点击item就开始配对,如果之前就已经是配对好的,那么当前点击事件就执行连接操作。
(4.1)执行配对操作:
首先要获取设备的状态,然后看是否是执行配对操作:
if(device.getBondState() == BluetoothDevice.BOND_NONE){
//上面的device是BluetoothDevice的对象
//走到这里说明是之前不是已经配对的
device.createBond();//配对
}
执行的时候会自己调用本机的蓝牙配对框,然后用户输入秘钥,配对成功的话就发出配对成功的广播,然后刷新当前的列表就行。当然刷新列表的时候需要自己去获取每一个BluetoothDevice对象然后去判断一下是否已经配对的方法。目前让人比较不能明确的是,已配对和已连接。我看源码里面没有给出状态。目前就给了10(未配对)、11(配对中)、12(已配对)。我测过已经连接的设备返回的状态也是12,他们居然是公用同一个值。要知道蓝牙连接其实是分两步的,第一步是配对、第二步是连接。一个设备可以跟多个设备配对,但是我觉得只能在一个时间点上跟一个设配对行连接。
(4.2)执行连接操作:
首先也是获取设备状态,然后看是不是已经处于已配对状态,并且连接操作是一个耗时操作,会不断的去请求另一个设备然后进行连接,连接成功之后就发射连接成功的广播,如果连接失败不会发射广播,但是如果你的代码写的比较仔细的话就可以在catch哪里当做连接失败。此外提醒一下:
A:执行连接之前必须要判断是不是还在执行搜索附近设备的操作,如果还在搜索就停止搜索,因为同时搜索和同时连接就很有可能导致连接失败。
B:要判断本机蓝牙是不是已经跟其他的设备已经连接了,如果已经连接就判断正在连接的设备是不是就是马上就要连接的对象,如果是就提示“两者已经是连接状态,是否要断开重新连接”之类,如果不要提示那就是直接干了,先断开再连接了。这个就看需求了。
if(device.getBondState() == BluetoothDevice.BOND_BONDED){
//上面的device是BluetoothDevice的对象
//走到这里说明是之前是已经配对的
伪代码:
try {
//通过和服务器协商的uuid来进行连接,下面的device是BluetoothDevice 对象
BluetoothSocket mBltSocket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-
00805F9B34FB"));
Log.d(TAG, "开始连接...");
//下面的mBluetoothAdapter是BluetoothAdapter的对象
if (mBluetoothAdapter.isDiscovering()) {
//如果还在搜索蓝牙中,就停止搜索,因为同时执行搜索和连接操作会有可能导致连接失败
mBluetoothAdapter.cancelDiscovery();
}
//如果当前socket处于非连接状态则调用连接
if (!mBltSocket.isConnected()) {
mBltSocket.connect();
} else {
//已经是连接状态了,获取正在连接的设备,然后校验是不是同一个设备,是与不是就看需求了
BluetoothDevice device = mBltSocket.getRemoteDevice();
}
Log.i(TAG, "连接成功");
} catch (Exception e) {
Log.i(TAG, "连接失败");
try {
getBluetoothSocket().close();
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
(3)主动断开连接: mBltSocket.close();
(4)主动取消配对:这个我暂时不知道怎么实现。
(5)输入mac地址来执行配对和连接:
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
拿到设备之后操作就跟4.2一样了。
(6)设置本机蓝牙是否可以被发现,模仿很多手机的设置,只在一定时间段内是可以被查询到状态。
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
context.startActivity(discoverableIntent);
(7)获取本机已经配对过的设备列表,并主动去配对:
先判断有没有拿到蓝牙适配器(mBluetoothAdapter),记得判断非空
//获得已配对的远程蓝牙设备的集合
Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices();
if (devices.size() > 0) {
for (Iterator<BluetoothDevice> it = devices.iterator(); it.hasNext(); ) {
BluetoothDevice device = it.next();
//自动连接已有蓝牙设备
执行4.1操作
}
}