一、蓝牙发展历程
蓝牙(Bluetooth):是一种无线技术标准,可实现设备间短距离数据交换。 蓝牙可以以一定的周期发送广播,手机端接收到广播后,解析广播包,可做设备识别、配对,事件通知以及指令控制等。低精度定位根据设备的信号强度,可以估算出大概方位和距离。
蓝牙发展至今经历了多个版本的更新,其中,将1.x~3.0之间的版本称之为经典蓝牙,4.x开始的蓝牙称之为低功耗蓝牙,也就是蓝牙ble。
根据应用、协议类型等,可以对蓝牙进行以下分类:
二、经典蓝牙API
1、BluetoothAdapter:代表了移动设备的本地的蓝牙适配器, 通过该蓝牙适配器可以对蓝牙进行基本操作, 例如 : 启动设备发现,获取已配对设备,通过mac蓝牙地址获取蓝牙设备等。
- enable():打开蓝牙,需要 BLUETOOTH_ADMIN权限。
- disable():关闭蓝牙,需要 BLUETOOTH_ADMIN权限。
- checkBluetoothAddress(String address):验证蓝牙设备MAC地址是否有效。
- getAddress():获取本地蓝牙适配器的硬件地址(MAC地址)
- getBondedDevices():获取与本机蓝牙所有绑定的远程蓝牙信息。
- getName():获取本地蓝牙适配器的蓝牙名称。
- setName(String name):设置本地蓝牙适配器的蓝牙名称。
- isEnabled():判断当前蓝牙适配器是否打开。
- isDiscovering():判断蓝牙适配器是否正在处于扫描过程中。
- startDiscovery():开始扫描周边蓝牙设备。
- cancelDiscovery():取消蓝牙搜索操作。
- getScanMode():获取本地蓝牙适配器的当前蓝牙扫描模式。
蓝牙扫描模式:
SCAN_MODE_NONE: 该设备不能扫描以及被扫描。
SCAN_MODE_CONNECTABLE:该设备可以扫描其他蓝牙设备。
SCAN_MODE_CONNECTABLE_DISCOVERABLE:该设备既可以扫描其他设备,也可以被其他设备扫描发现。
- getState():获取本地蓝牙适配器的当前状态。
蓝牙适配器状态:
STATE_OFF:表示本地蓝牙适配器已关闭。
STATE_TURNING_ON:表示本地蓝牙适配器正在打开。
STATE_ON:表示本地蓝牙适配器已开启,并可供使用。
STATE_TURNING_OFF:表示本地蓝牙适配器正在关闭。
- getRemoteDevice(String address):获取给定蓝牙硬件地址的BluetoothDevice对象。
如果address中的MAC无效无效,将抛出IllegalArgumentException异常。
- listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid):创建不安全的蓝牙服务套接字。
- listenUsingRfcommWithServiceRecord(String name, UUID uuid):创建一个正在监听的安全的带有服务记录的无线射频通信(RFCOMM)蓝牙端口。
2、BluetoothDevice:代表了一个远程的蓝牙设备, 通过这个类可以查询远程设备的物理地址, 名称, 连接状态等信息。这个类实际上只是一个蓝牙硬件地址的简单包装,这个类的对象是不可变的。对这个类的操作, 会执行在远程蓝牙设备的硬件上。
- getName():获取远程蓝牙设备的蓝牙名称。
- getAddress():获取远程蓝牙设备的硬件地址。
- createBond():开始与远程蓝牙设备的绑定过程。
- getBondState():获取远程蓝牙设备的绑定状态。
蓝牙绑定状态:
BOND_NONE:远程设备未绑定。
BOND_BONDING:正在与远程设备进行绑定。
BOND_BONDED:远程设备已绑定。
- createInsecureRfcommSocketToServiceRecord(UUID uuid):创建不安全的蓝牙套接字。
- createRfcommSocketToServiceRecord(UUID uuid):创建安全的蓝牙套接字。
3、BluetoothServerSocket:侦听蓝牙服务套接字。使用BluetoothServerSocket可以创建一个监听服务端口, 使用accept方法阻塞, 当该方法监测到连接的时候, 就会返回一个BluetoothSocket对象来管理这个连接。BluetoothServerSocket是线程安全的,close方法始终会立即中止正在进行的操作并关闭蓝牙服务套接字。需要BLUETOOTH权限。
- accept():阻塞直到建立连接,成功连接时连接的BluetoothSocket对象。
- accept(int timeout):阻塞直到建立连接或超时,成功连接时连接的BluetoothSocket对象。
- close():关闭该监听服务端口,并释放所有关联的资源。
4、BluetoothSocket:蓝牙套接口。在服务器端,使用BluetoothServerSocket创建侦听服务器套接字。当连接被BluetoothServerSocket接受时,它将返回一个新的BluetoothSocket来管理连接。 在客户端,使用单个BluetoothSocket来启动连接并管理连接。BluetoothSocket是线程安全的,close方法始终会立即中止正在进行的操作并关闭套接字。需要BLUETOOTH权限。
- connect():尝试连接到远程蓝牙服务器。
- isConnected():获取此套接字的连接状态,即是否与远程蓝牙服务连接。
- getRemoteDevice():获取此套接字连接的远程蓝牙设备。
- getInputStream():获取与此套接字关联的输入流。
即使套接字尚未连接,输入流也会返回,但对该流的操作将抛出IOException异常,直到关联的套接字连接。
- getOutputStream():获取与此套接字关联的输出流。
即使套接字尚未连接,输出流也会返回,但对该流的操作将抛出IOException异常,直到关联的套接字连接。
- close():关闭此流并释放与其关联的所有系统资源。如果流已经关闭,则调用此方法不起作用。
三、经典蓝牙开发
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有两种方式获取,方式二要求Android4.3以上才可以用,建议使用方式一,比较通用。
//第一种方式
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//第二种方式
BluetoothManager manager = (BluetoothManager) Context.getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter mBluetoothAdapter = manager.getAdapter();
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();
}
}
6、注册蓝牙广播
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);//蓝牙状态
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//发起扫描
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//结束扫描
mContext.registerReceiver(mBluetoothBroadcastReceiver,filter);
广播类:
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) {
Log.i(TAG,"已取消与设备" + btDevice.getName() + "的配对");
} else if (state == BluetoothDevice.BOND_BONDED) {
Log.i(TAG,"与设备" + btDevice.getName() + "配对成功");
}
}
} 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,"STATE_TURNING_ON 手机蓝牙正在开启");
break;
case BluetoothAdapter.STATE_ON:
Log.i(TAG,"STATE_ON 手机蓝牙开启");
break;
case BluetoothAdapter.STATE_TURNING_OFF:
Log.i(TAG,"STATE_TURNING_OFF 手机蓝牙正在关闭");
break;
case BluetoothAdapter.STATE_OFF:
Log.i(TAG,"STATE_OFF 手机蓝牙关闭");
break;
}
}
}
}
7、发起扫描
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
//搜索到的蓝牙设备通过广播接收
mBluetoothAdapter.startDiscovery();
8、与扫描到的设备进行配对
private void pair(BluetoothDevice device) {
//在配对之前,停止搜索
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {//没配对才配对
try {
Method createBondMethod = BluetoothDevice.class.getMethod("createBond");
Boolean returnValue = (Boolean) createBondMethod.invoke(device);
if (returnValue){
Log.d(TAG, "配对成功...");
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
public void unpair(BluetoothDevice device){
Log.d(TAG, "attemp to cancel bond:" + device.getName());
try {
Method removeBondMethod = device.getClass().getMethod("removeBond");
Boolean returnValue = (Boolean) removeBondMethod.invoke(device);
if (returnValue){
Log.d(TAG, "解配对成功...");
}
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "attemp to cancel bond fail!");
}
}
9、蓝牙连接
经典蓝牙连接相当于socket连接,是个非常耗时的操作,所以应该放到子线程中去完成。
9.1 新建ConnectBlueTask
public class ConnectBlueTask extends AsyncTask<BluetoothDevice, Integer, BluetoothSocket> {
private static final String TAG = ConnectBlueTask.class.getName();
private BluetoothDevice bluetoothDevice;
private ConnectBlueCallBack callBack;
public ConnectBlueTask(ConnectBlueCallBack callBack){
this.callBack = callBack;
}
@Override
protected BluetoothSocket doInBackground(BluetoothDevice... bluetoothDevices) {
bluetoothDevice = bluetoothDevices[0];
BluetoothSocket socket = null;
try{
Log.d(TAG,"开始连接socket,uuid:" + ClassicsBluetooth.UUID);
socket = bluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString(ClassicsBluetooth.UUID));
if (socket != null && !socket.isConnected()){
socket.connect();
}
}catch (IOException e){
Log.e(TAG,"socket连接失败");
try {
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
Log.e(TAG,"socket关闭失败");
}
}
return socket;
}
@Override
protected void onPreExecute() {
Log.d(TAG,"开始连接");
if (callBack != null) callBack.onStartConnect();
}
@Override
protected void onPostExecute(BluetoothSocket bluetoothSocket) {
if (bluetoothSocket != null && bluetoothSocket.isConnected()){
Log.d(TAG,"连接成功");
if (callBack != null) callBack.onConnectSuccess(bluetoothDevice, bluetoothSocket);
}else {
Log.d(TAG,"连接失败");
if (callBack != null) callBack.onConnectFail(bluetoothDevice, "连接失败");
}
}
}
9.2 启动连接线程
/**
* 连接 (在配对之后调用)
* @param device
*/
public void connect(BluetoothDevice device, ConnectBlueCallBack callBack){
if (device == null){
Log.d(TAG, "bond device null");
return;
}
if (!isBlueEnable()){
Log.e(TAG, "Bluetooth not enable!");
return;
}
//连接之前把扫描关闭
if (mBluetoothAdapter.isDiscovering()){
mBluetoothAdapter.cancelDiscovery();
}
new ConnectBlueTask(callBack).execute(device);
}
9.3 判断是否连接成功
/**
* 蓝牙是否连接
* @return
*/
public boolean isConnectBlue(){
return mBluetoothSocket != null && mBluetoothSocket.isConnected();
}
9.4 断开连接
/**
* 断开连接
* @return
*/
public boolean cancelConnect(){
if (mBluetoothSocket != null && mBluetoothSocket.isConnected()){
try {
mBluetoothSocket.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
mBluetoothSocket = null;
return true;
}
9.5 MAC地址连接
/**
* 输入mac地址进行自动配对
* 前提是系统保存了该地址的对象
* @param address
* @param callBack
*/
public void connectMAC(String address, ConnectBlueCallBack callBack) {
if (!isBlueEnable()){
return ;
}
BluetoothDevice btDev = mBluetoothAdapter.getRemoteDevice(address);
connect(btDev, callBack);
}
10、蓝牙通信
10.1 读取数据线程
public class ReadTask extends AsyncTask<String, Integer, String> {
private static final String TAG = ReadTask.class.getName();
private ReadCallBack callBack;
private BluetoothSocket socket;
public ReadTask(ReadCallBack callBack, BluetoothSocket socket){
this.callBack = callBack;
this.socket = socket;
}
@Override
protected String doInBackground(String... strings) {
BufferedInputStream in = null;
try {
StringBuffer sb = new StringBuffer();
in = new BufferedInputStream(socket.getInputStream());
int length = 0;
byte[] buf = new byte[1024];
while ((length = in.read()) != -1) {
sb.append(new String(buf,0,length));
}
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return "读取失败";
}
@Override
protected void onPreExecute() {
Log.d(TAG,"开始读取数据");
if (callBack != null) callBack.onStarted();
}
@Override
protected void onPostExecute(String s) {
Log.d(TAG,"完成读取数据");
if (callBack != null){
if ("读取失败".equals(s)){
callBack.onFinished(false, s);
}else {
callBack.onFinished(true, s);
}
}
}
}
10.2 写入数据线程
public class WriteTask extends AsyncTask<String, Integer, String>{
private static final String TAG = WriteTask.class.getName();
private WriteCallBack callBack;
private BluetoothSocket socket;
public WriteTask(WriteCallBack callBack, BluetoothSocket socket){
this.callBack = callBack;
this.socket = socket;
}
@Override
protected String doInBackground(String... strings) {
String string = strings[0];
OutputStream outputStream = null;
try{
outputStream = socket.getOutputStream();
outputStream.write(string.getBytes());
} catch (IOException e) {
Log.e("error", "ON RESUME: Exception during write.", e);
return "发送失败";
}finally {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return "发送成功";
}
@Override
protected void onPreExecute() {
if (callBack != null) callBack.onStarted();
}
@Override
protected void onPostExecute(String s) {
if (callBack != null){
if ("发送成功".equals(s)){
callBack.onFinished(true, s);
}else {
callBack.onFinished(false, s);
}
}
}
}