蓝牙的基本功能:
- 扫描其他蓝牙设备
- 为可配对蓝牙设备查询蓝牙适配器。
- 建立RFCOMM通道
- 通过服务搜索来连接其他设备。
- 与其他设备进行数据传输。
- 管理多个连接
使用蓝牙进行通信的必要四步:
- 打开蓝牙;
- 查找附近已配对或可用的设备;
- 连接设备;
- 设备间数据交换。
所有蓝牙API都在android.bluetooth
包下.下面有一些类和接口的摘要,可能需要它们来建立蓝牙连接:
BluetoothAdapter
代表本地蓝牙适配器(蓝牙无线电)。BluetoothAdapter是所有蓝牙交互的入口。使用这个你可以发现其他蓝牙设备,查询已配对的设备列表,使用一个已知的MAC地址来实例化一个BluetoothDevice,以及创建一个BluetoothServerSocket来为监听与其他设备的通信。
BluetoothDevice
代表一个远程蓝牙设备,使用这个来请求一个与远程设备的BluetoothSocket连接,或者查询关于设备名称、地址、类和连接状态等设备信息。
BluetoothSocket
代表一个蓝牙socket的接口(和TCP Socket类似)。这是一个连接点,它允许一个应用与其他蓝牙设备通过InputStream和OutputStream交换数据。
BluetoothServerSocket
代表一个开放的服务器socket,它监听接受的请求(与TCP ServerSocket类似)。为了连接两台Android设备,一个设备必须使用这个类开启一个服务器socket。当一个远程蓝牙设备开始一个和该设备的连接请求,BluetoothServerSocket将会返回一个已连接的BluetoothSocket,接受该连接。
BluetoothClass
描述一个蓝牙设备的基本特性和性能。这是一个只读的属性集合,它定义了设备的主要和次要的设备类以及它的服务。但是,它没有描述所有的蓝牙配置和设备支持的服务,它只是暗示了设备的类型。
BluetoothProfile
一个表示蓝牙配置文件的接口。一个Bluetooth profile是一个基于蓝牙的通信无线接口定义。一个例子是Hands-Free profile。更多的讨论请见Working with Profiles。
BluetoothHeadset
提供对移动手机使用的蓝牙耳机的支持。它包含了Headset and Hands-Free (v1.5)配置文件。
BluetoothA2dp
定义高品质的音频如何通过蓝牙连接从一个设备传输到另一个设备。”A2DP“是Advanced Audio Distribution Profile的缩写。
BluetoothHealth
表示一个Health Device Profile代理,它控制蓝牙服务。
BluetoothHealthCallback
一个抽象类,你可以使用它来实现BluetoothHealth的回调函数。你必须扩展这个类并实现回调函数方法来接收应用程序的注册状态改变以及蓝牙串口状态的更新。
BluetoothHealthAppConfiguration
表示一个应用程序配置,Bluetooth Health第三方应用程序注册和一个远程Bluetooth Health设备通信。
BluetoothProfile.ServiceListener
一个接口,当BluetoothProfile IPC客户端从服务器上建立连接或断开连接时,它负责通知它们(也就是,运行在特性配置的内部服务)
使用蓝牙需要在配置文件Androidmanifest.xml 中注册两种权限:
//获取蓝牙适配器
<uses-permission android:name="android.permission.BLUETOOTH" />
//蓝牙开启或关闭权限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
一、蓝牙搜索
1.获取蓝牙适配器:
private BluetoothAdapter mBluetoothAdapter;
mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
Attention:一定要在真机上调试,AndroidStudio上建立的AVD没有蓝牙模块,可以先进行一下判断:
if(mBluetoothAdapter == null){
Toast.makeText(this, "Bluetooth is not supported on the device", Toast.LENGTH_LONG).show();
return;
}
2.开启蓝牙:
//判断蓝牙是否开启,如未开启则强制开启
if (!mBluetoothAdapter.isEnabled()) {
//注释掉的方法为提示窗口开启,部分系统(如EMUI)开启蓝牙自带提示窗,故而用这种方法可能会有两个提示窗
/*Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(intent);
startActivityForResult(intent, REQUEST_CODE_BLUETOOTH_ON);*/
mBluetoothAdapter.enable();
}
蓝牙打开有三种方式:
- enable()直接强制打开
- 上面注释掉的方法为提示窗打开,本人亲测华为荣耀8这种方式会出现两次提示窗,故而弃用
- 打开蓝牙并设置可见时间,具体代码就不贴了,到处都是。
3.蓝牙广播
首先使用蓝牙广播要注册蓝牙广播接收者,我们用的是动态注册的方式,即在程序中使用Context.registerReceiver注册。还有一种静态注册方式是在AndroidManifest.xml文件中定义。
IntentFilter filter=new IntentFilter();
//发现设备
filter.addAction(BluetoothDevice.ACTION_FOUND);
//连接断开
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
//完成扫描
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
//设备连接状态改变
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
registerReceiver(mReceiver,filter);
注册完之后就是定义蓝牙广播接收者啦:
// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver=new BroadcastReceiver(){
private List<BluetoothDevice> bondedlist = new ArrayList <>();
private List<BluetoothDevice> surroundList = new ArrayList <>();
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()){
case BluetoothDevice.ACTION_FOUND:
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if(device.getBondState() != BluetoothDevice.BOND_BONDED){
if (!surroundList.contains(device)) {
surroundList.add(device);
}
System.out.println("設備列表:"+surroundList);
// 为listview设置字符换数组适配器,搜索附近未配对设备
SimpleAdapter simAdapter = new SimpleAdapter(
MainActivity.this, getDeviceList(surroundList), android.R.layout.simple_list_item_2,
new String[] { "name" , "address"}, new int[] {
android.R.id.text1, android.R.id.text2});
surroundDevices.setAdapter(simAdapter);
setListViewHeightBasedOnChildren(surroundDevices);
}
//已配对设备信息
bondedlist = new ArrayList <>(mBluetoothAdapter.getBondedDevices());
System.out.println("BondedList:"+bondedlist);
// 为listview设置字符换数组适配器
SimpleAdapter simAdapter = new SimpleAdapter(
MainActivity.this, getDeviceList(bondedlist), android.R.layout.simple_list_item_2,
new String[] { "name" , "address"}, new int[] {
android.R.id.text1, android.R.id.text2});
// 为listView绑定适配器
bondedDevices.setAdapter(simAdapter);
setListViewHeightBasedOnChildren(bondedDevices);
break;
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
statusText.setText("DISCOVERY_FINISHED");
break;
case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
BluetoothDevice device1 = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.e("STATE:","address:"+device1.getAddress()+"\nname:"+device1.getName());
switch (device1.getBondState()){
case BluetoothDevice.BOND_BONDING:
Log.i("STATE", "正在与" + device1.getName() + "__" + device1.getAddress() + "配对");
statusText.setText("正在与" + device1.getName() +"进行配对");
break;
case BluetoothDevice.BOND_BONDED:
Log.i("STATE", "与" + device1.getName() + "__" + device1.getAddress() + "完成配对");
statusText.setText("与" + device1.getName() + "配对完成");
break;
case BluetoothDevice.BOND_NONE:
Log.i("STATE", "取消与" + device1.getName() + "__" + device1.getAddress() + "配对");
statusText.setText("与" + device1.getName() + "配对取消");
default:
break;
}
break;
case BluetoothDevice.ACTION_ACL_DISCONNECTED:
statusText.setText("Disconnected");
break;
}
}
};
4.开始搜索,触发广播
通过 BluetoothAdapter中startDiscovery( )方法来开始广播。当广播的事件是我们刚刚注册的事件时就会触发广播接收器,并且触发广播接收器中的onReceiver()方法。
二、蓝牙通信
蓝牙配对我偷了个懒,直接在系统设置蓝牙里面配对的,因为做这个的时候主要实现的就是通信啦~
1.蓝牙连接和通信,我把它们写到了一起~
/**
* 发送信息到另一个蓝牙设备
*
* @param message 信息
*/
private void sendMessage(String message) {
if (address==null||"".equals(address)){
Toast.makeText(MainActivity.this,"未连接任何设备",Toast.LENGTH_SHORT).show();
return;
}
try {
mBluetoothAdapter.cancelDiscovery();
Log.e(TAG, "sendMessage: 1");
if (null == bluetoothDevice) {
bluetoothDevice = mBluetoothAdapter.getRemoteDevice(address);
}
Log.e(TAG, "sendMessage: 2");
if (bluetoothSocket == null) {
bluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(MY_UUID);
bluetoothSocket.connect();
outputStream = bluetoothSocket.getOutputStream();
Message msg1 = new Message();
msg1.obj = new String(bluetoothSocket.getRemoteDevice().getName().getBytes("utf-8"), "utf-8");
msg1.what = 3;
mHandler.sendMessage(msg1);
}
Log.e(TAG, "sendMessage: 3");
if (!bluetoothSocket.isConnected()) {
resetSocket();
}
Log.e(TAG, "sendMessage: 4");
if (outputStream != null) {
try {
outputStream.write((message.getBytes("utf-8")));
Log.e(TAG, "onItemClick: " + mBluetoothAdapter.getName() + ":" + message);
Message msg = new Message();
msg.obj = new String(message.getBytes("utf-8"), "utf-8");
msg.what = 0;
mHandler.sendMessage(msg);
} catch (Exception e) {
resetSocket();
sendMessage(message);
}
}
} catch (Exception e) {
Toast.makeText(MainActivity.this,"建立连接失败",Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
private void resetSocket() {
try {
bluetoothSocket.close();
bluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(MY_UUID);
bluetoothSocket.connect();
outputStream = bluetoothSocket.getOutputStream();
Message msg1 = new Message();
msg1.obj = new String(bluetoothSocket.getRemoteDevice().getName().getBytes("utf-8"), "utf-8");
msg1.what = 3;
mHandler.sendMessage(msg1);
} catch (IOException e) {
e.printStackTrace();
}
}
2.服务端接收线程:
private class AcceptThread extends Thread {
private BluetoothServerSocket mBluetoothServerSocket;
private BluetoothSocket bluetoothSocket;
private InputStream is;
private OutputStream os;
private boolean isContinue;
AcceptThread() {
try {
mBluetoothServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord("bluetooth_socket", MY_UUID);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
try {
if (mBluetoothServerSocket == null) {
return;
}
bluetoothSocket = mBluetoothServerSocket.accept();
serverBleName = bluetoothSocket.getRemoteDevice().getName();
Log.e(TAG, "run: accept");
is = bluetoothSocket.getInputStream();
os = bluetoothSocket.getOutputStream();
Message msg = new Message();
msg.obj = new String(serverBleName.getBytes("utf-8"), "utf-8");
msg.what = 4;
mHandler.sendMessage(msg);
isContinue = true;
while (isContinue) {
byte[] buffer = new byte[128];
int count = is.read(buffer);
Message message = new Message();
message.obj = new String(buffer, 0, count, "utf-8");
message.what = 1;
mHandler.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
isContinue = false;
} finally {
try {
if (bluetoothSocket != null) {
bluetoothSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.更新界面UI线程
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//Toast.makeText(MainActivity.this, String.valueOf(msg.obj), Toast.LENGTH_SHORT).show();
switch (msg.what){
//send message
case 0:
receiveText.append("Me : "+String.valueOf(msg.obj)+"\n");
break;
//receive message
case 1:
receiveText.append(serverBleName + " : "+String.valueOf(msg.obj)+"\n");
break;
//connect server
case 3:
statusText.setText("Connnected to "+String.valueOf(msg.obj));
break;
//connectclient
case 4:
statusText.setText("Connnected to "+String.valueOf(msg.obj));
break;
}
return false;
}
});
三、结果展示
下面是各个阶段的截图