1. 权限
关于蓝牙的权限主要涉及到下面三个:
BLUETOOTH:允许配对的设备进行连接
BLUETOOTH_ADMIN:允许搜索和配对设备
ACCESS_COARSE_LOCATION:广播接收器接收BluetoothDevice.ACTION_FOUND广播需要改权限
在后面还会提到在Android6.0及以上的版本中关于ACCESS_COARSE_LOCATION权限的申请。
2. 开启蓝牙
建立蓝牙通信之前需要验证是否有蓝牙设备,以及蓝牙设备是否已经开启。对于一个Android系统而言只有一个蓝牙适配器,通过getDefaultAdapter()方法可以返回其一个实例,如果返回为null,则说明该设备不支持蓝牙。
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// device doesn't support Bluetooth
}
接下来是检查蓝牙设备是否已经开启,如果没有开启,可以调用startActivityForResult()方法来弹出对话框让用户选择开启,这种方式不会停止当前的应用。
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
3. 搜索设备
搜索设备可以分成两部分,一是查找已经与本机配对的设备,通过getBondedDevices()方法返回已经配对的设备信息:
Set
if (pariedDevices.size > 0) {
for (BluetoothDevice device: pairedDevices) {
String deviceName = device.getName();
String deviceMACAddress = device.getAddress();
}
}
二是搜索周围可用的但是还未配对的设备。
系统在发现蓝牙设备会通过广播的形式通知app,所以在搜索设备之前需要注册广播接收器来接收发现蓝牙设备的消息,在销毁Activity时注销广播接收器。
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceiver(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevie device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// Register for broadcasts when a device is discovered.
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
// Don't forget to unregister the ACTION_FOUND receiver.
unregisterReceiver(mReceiver);
}
BluetoothDevice.ACTION_FOUND广播需要ACCESS_COARSE_LOCATION权限,该权限是个危险权限,在Android 6.0及以上,除了在manifest中声明还需要在java代码中申请。获取了该权限之后,在搜索蓝牙设备时才能收到系统发出的蓝牙设备发现的广播。搜索设备调用startDiscovery()方法,当周围有可用设备时,系统会通过广播的形式通知应用。
//检查ACCESS_COARSE_LOCATION权限
if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION)
== PackageManager.PERMISSION_GRANTED){
Toast.makeText(MainActivity.this,"搜索回调权限已开启",Toast.LENGTH_SHORT).show();
if(mBluetoothAdapter.isDiscovering()){
mBluetoothAdapter.cancelDiscovery();
}
mBluetoothAdapter.startDiscovery();
}else{
Toast.makeText(MainActivity.this,"搜索回调权限未开启",Toast.LENGTH_SHORT).show();
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},REQUEST_ACCESS_COARSE_LOCATION);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode==REQUEST_ACCESS_COARSE_LOCATION){
if(grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){
mBluetoothAdapter.startDiscovery();
if(mBluetoothAdapter.isDiscovering()){
mBluetoothAdapter.cancelDiscovery();
}
mBluetoothAdapter.startDiscovery();
} else {
Toast.makeText(MainActivity.this,"action found is not granted.",Toast.LENGTH_LONG).show();
}
}
}
做的这个应用主要是与HC-06模块进行蓝牙通信,HC-06是一个串口透传模块,其对应的UUID是“00001101-0000-1000-8000-00805F9B34FB”。手机端是作为客户端与HC-06蓝牙模块进行连接的。在蓝牙socket进行connect之前,一定要调用BluetoothAdapter的cancelDiscovery()方法。连接的第一步是通过调用BluetoothDevice的createRfcommSocketToServiceRecord(UUID)获取BluetoothSocket.第二步是调用BluetoothSocket的connect()方法发起连接。由于connect()为阻塞调用,因此该连接过程应该在主线程之外的线程中执行。
在调用connect()时,应始终确保设备未在执行设备发现。如果正在进行发现操作,则会大幅降低连接尝试的速度,并增加连接失败的可能性。
示例:
String macAddr = "20:15:05:25:02:43";
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(macAddr);
UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
try {
mSocket = device.createRfcommSocketToServiceRecord(uuid);
} catch (IOException e) {
e.printStackTrace();
}
new Thread(){
@Override
public void run() {
mBluetoothAdapter.cancelDiscovery();
try {
mSocket.connect();
} catch (IOException e) {
try {
mSocket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
super.run();
}
}.start();
确保在建立连接之前始终调用cancelDiscovery(),而且调用时无需实际检查其是否正在运行,如果确实想要执行检查,请调用isDiscovering()。