前言
原来蓝牙现在还分经典蓝牙、低功耗蓝牙和双模蓝牙,技术的发展真的超过个人的认知速度,不学习意味退步。本来写着低功耗蓝牙和智能蓝牙音箱的交互,但写到最后,因为蓝牙音箱还没有做好,没办法给本文的结果做个保障,故最后改成蓝牙聊天。蓝牙聊天可能适合在搭飞机和高铁这种没有网络或者网络不好等特殊情况下使用。本文的Demo可以正常使用。
本文总体流程:发现蓝牙->配对蓝牙->连接蓝牙->数据交互
在这个流程,主要是一些细节和异常的处理,如何更好的体现用户体验。
声明权限
在项目的配置文件AndroidManifest.xml加入如下代码即可,让APP具有蓝牙访问权限和发现周边蓝牙权限。
//使用蓝牙需要该权限
<uses-permission android:name="android.permission.BLUETOOTH"/>
//使用扫描和设置需要权限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
//Android 6.0以上声明一下两个权限之一即可。声明位置权限,不然扫描或者发现蓝牙功能用不了哦
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
复制代码
Android 6.0以上动态申请权限位置权限,否则默认是禁止的,无法获取到蓝牙列表。
/**
* Android 6.0 动态申请授权定位信息权限,否则扫描蓝牙列表为空
*/
private void requestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_COARSE_LOCATION)) {
Toast.makeText(this, "使用蓝牙需要授权定位信息", Toast.LENGTH_LONG).show();
}
//请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
REQUEST_ACCESS_COARSE_LOCATION_PERMISSION);
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_ACCESS_COARSE_LOCATION_PERMISSION) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//用户授权
} else {
finish();
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
复制代码
这里处理授权的用户体验比较不好,授权则继续,不授权则退出。可以根据自己的需求对授权逻辑另外处理。
设备是否支持蓝牙
并不是所有的Android 设备都支持蓝牙,所以在使用之前,检测当前设备是否支持蓝牙。
private void isSupportBluetooth() {
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null
|| !getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
showNotSupportBluetoothDialog();
Log.e(TAG, "not support bluetooth");
} else {
Log.e(TAG, " support bluetooth");
}
}
复制代码
类BluetoothAdapter代表着当前设备的蓝牙,可以通过BluetoothAdapter获取所有已绑定的蓝牙,扫描蓝牙,自身的名字和地址,通过地址可以获取周边蓝牙设备。可以通过两种方式获得BluetoothAdapter对象。
//方式一
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//方式二
BluetoothManager manager= (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter= manager.getAdapter();
复制代码
开启蓝牙
先检测蓝牙是否打开。没有打开则提示用户打开,或者直接打开。
//提示用户开启蓝牙,会有弹出框让用户选择是否同意打开
private void enableBLE() {
if (!bluetoothAdapter.isEnabled()) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, REQUEST_BLE_OPEN);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
if (resultCode == RESULT_OK) {
scanBle();
}
}
}
//直接设置代码开启
private void enableBLE() {
if (!bluetoothAdapter.isEnabled()) {
bluetoothAdapter.enable();
}
}
复制代码
设置可被发现
蓝牙打开后,要将自身设置为可以被周边蓝牙搜索到,以便可以进行下一步操作。蓝牙默认可被周边设备在120秒内搜索到,最长设置不过300秒。听说设置0可以永久被搜索哦。
/**
* 设置蓝牙可以被其他设备搜索到
*/
private void beDiscovered() {
if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0);
startActivity(discoverableIntent);
}
}
复制代码
收集周边蓝牙设备
通过注册广播监听,对发现的蓝牙设备添加到集合中,在listview进行展示。
private void registerBluetoothReceiver() {
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(bluetoothReceiver, filter);
}
BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BluetoothDevice.ACTION_FOUND)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
list.add(device);
Log.e(TAG, "discovery:" + device.getName());
bleAdapter.notifyDataSetChanged();
} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
// Toast.makeText(MainActivity.this, "discovery finished", Toast.LENGTH_LONG).show();
btnSearch.setText("重新搜索");
mBluetoothAdapter.cancelDiscovery();
}
}
};
复制代码
搜索结果如下,可以看到,部分蓝牙设备只有Mac地址,没有名称。这里没有进一步处理,如果需要处理,参考蓝牙名为空处理方案
设备详情
获取蓝牙列表之后,点击对应的蓝牙,进入蓝牙详情。一个BluetoothDeviced对象代表着一个周边蓝牙设备,通过该对象,可以获取该蓝牙设备的名称,绑定状态,Mac地址,uuid等。
mDevice.createBond();
进行配对。
聊天框实现
配对成功后,通过BluetoothDevice的createRfcommSocketToServiceRecord()
方法和蓝牙设备连接,连接成功会返回BluetoothSocket
对象,进而获得输入输出流,进行数据交互。
mSocket = device.createRfcommSocketToServiceRecord(UUID.fromString(uuid));
mSocket.connect();
mOutputStream = mSocket.getOutputStream();
mInputStream = mSocket.getInputStream();
复制代码
连接过程会堵塞线程,请在子线程执行。uuid和服务端的一致且唯一就可以(这里自己定义),通过uuid和服务端绑定BluetoothSocket。我们将输入输出的内容同步到聊天框,就达到了聊天效果。
服务端的BluetoothSocket
对象的实现和此类似,具体的话可以看源码哦。
总结
本文简单描述蓝牙连接到实现聊天框的流程,源代码可打开GitHub下载运行,有用就star一下。
由于BluetoothSocket的关闭或者读写异常,还有一些未能同步到,各位客官根据自己需要进行处理。另外一些耗时的操作,例如连接蓝牙,没有做进度条反馈,可以根据自己需求进行定制。Demo可以正常食用。