一、蓝牙通信的步骤
1.开启蓝牙
2.搜索可用设备
3.创建蓝牙socket
4.获取输入输出流
5.断开连接关闭蓝牙
二、几个重要的类
实现蓝牙通信的关键类和接口
android.bluetooth 包中提供所有 Bluetooth API。以下概要列出了创建蓝牙连接所需的类和接口:
BluetoothAdapter
表示本地蓝牙适配器(蓝牙无线装置)。BluetoothAdapter 是所有蓝牙交互的入口点。借助该类,您可以发现其他蓝牙设备、查询已绑定(已配对)设备的列表、使用已知的 MAC 地址实例化 BluetoothDevice,以及通过创建 BluetoothServerSocket 侦听来自其他设备的通信。
BluetoothDevice
表示远程蓝牙设备。借助该类,您可以通过 BluetoothSocket 请求与某个远程设备建立连接,或查询有关该设备的信息,例如设备的名称、地址、类和绑定状态等。
BluetoothSocket
表示蓝牙套接字接口(类似于 TCP Socket)。这是允许应用使用 InputStream 和 OutputStream 与其他蓝牙设备交换数据的连接点。
BluetoothServerSocket
表示用于侦听传入请求的开放服务器套接字(类似于 TCP ServerSocket)。如要连接两台 Android 设备,其中一台设备必须使用此类开放一个服务器套接字。当远程蓝牙设备向此设备发出连接请求时,该设备接受连接,然后返回已连接的 BluetoothSocket。
BluetoothClass
描述蓝牙设备的一般特征和功能。这是一组只读属性,用于定义设备的类和服务。虽然这些信息会提供关于设备类型的有用提示,但该类的属性未必描述设备支持的所有蓝牙配置文件和服务。
BluetoothProfile
表示蓝牙配置文件的接口。蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。举个例子:免提配置文件。如需了解有关配置文件的详细讨论,请参阅使用配置文件。
BluetoothHeadset
提供蓝牙耳机支持,以便与手机配合使用。这包括蓝牙耳机配置文件和免提 (v1.5) 配置文件。
BluetoothA2dp
定义如何使用蓝牙立体声音频传输配置文件 (A2DP),通过蓝牙连接将高质量音频从一个设备流式传输至另一个设备。
BluetoothHealth
表示用于控制蓝牙服务的健康设备配置文件代理。
BluetoothHealthCallback
用于实现 BluetoothHealth 回调的抽象类。您必须扩展此类并实现回调方法,以接收关于应用注册状态和蓝牙通道状态变化的更新内容。
BluetoothHealthAppConfiguration
表示第三方蓝牙健康应用注册的应用配置,该配置旨在实现与远程蓝牙健康设备的通信。
BluetoothProfile.ServiceListener
当 BluetoothProfile 进程间通信 (IPC) 客户端连接到运行特定配置文件的内部服务或断开该服务连接时,向该客户端发送通知的接口。
3.2 客户端socket
创建客户端蓝牙Socket
创建连接
读写数据
关闭
3.3 服务端Socket
创建服务端蓝牙Socket
绑定端口号(蓝牙忽略)
创建监听listen(蓝牙忽略, 蓝牙没有此监听,而是通过while(true)死循环来一直监听的)
public class ConnectThread extends Thread{
private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);
/** 客户端socket*/
private final BluetoothSocket mmSoket;
/** 要连接的设备*/
private final BluetoothDevice mmDevice;
private BluetoothAdapter mBluetoothAdapter;
/** 主线程通信的Handler*/
private final Handler mHandler;
/** 发送和接收数据的处理类*/
private ConnectedThread mConnectedThread;
public ConnectThread(BluetoothDevice device, BluetoothAdapter bluetoothAdapter, Handler mUIhandler) {
mmDevice = device;
mBluetoothAdapter = bluetoothAdapter;
mHandler = mUIhandler;
BluetoothSocket tmp = null;
try {
// 创建客户端Socket
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
e.printStackTrace();
}
mmSoket = tmp;
}
@Override
public void run() {
super.run();
// 关闭正在发现设备.(如果此时又在查找设备,又在发送数据,会有冲突,影响传输效率)
mBluetoothAdapter.cancelDiscovery();
try {
// 连接服务器
mmSoket.connect();
} catch (IOException e) {
// 连接异常就关闭
try {
mmSoket.close();
} catch (IOException e1) {
}
return;
}
manageConnectedSocket(mmSoket);
}
private void manageConnectedSocket(BluetoothSocket mmSoket) {
// 通知主线程连接上了服务端socket,更新UI
mHandler.sendEmptyMessage(Constant.MSG_CONNECTED_TO_SERVER);
// 新建一个线程进行通讯,不然会发现线程堵塞
mConnectedThread = new ConnectedThread(mmSoket,mHandler);
mConnectedThread.start();
}
/**
* 关闭当前客户端
*/
public void cancle() {
try {
mmSoket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发送数据
* @param data
*/
public void sendData(byte[] data) {
if(mConnectedThread != null) {
mConnectedThread.write(data);
}
}
}
通过accept(),如果有客户端连接,会创建一个新的Socket,体现出并发性,可以同时与多个socket通讯)
读写数据
关闭
四、客户端代码
在这里插入代码片
五、服务端代码
public class AccepThread extends Thread {
/** 连接的名称*/
private static final String NAME = "BluetoothClass";
/** UUID*/
private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);
/** 服务端蓝牙Sokcet*/
private final BluetoothServerSocket mmServerSocket;
private final BluetoothAdapter mBluetoothAdapter;
/** 线程中通信的更新UI的Handler*/
private final Handler mHandler;
/** 监听到有客户端连接,新建一个线程单独处理,不然在此线程中会堵塞*/
private ConnectedThread mConnectedThread;
public AccepThread(BluetoothAdapter adapter, Handler handler) throws IOException {
mBluetoothAdapter = adapter;
this.mHandler = handler;
// 获取服务端蓝牙socket
mmServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
}
@Override
public void run() {
super.run();
// 连接的客户端soacket
BluetoothSocket socket = null;
// 服务端是不退出的,要一直监听连接进来的客户端,所以是死循环
while (true){
// 通知主线程更新UI,客户端开始监听
mHandler.sendEmptyMessage(Constant.MSG_START_LISTENING);
try {
// 获取连接的客户端socket
socket = mmServerSocket.accept();
} catch (IOException e) {
// 通知主线程更新UI, 获取异常
mHandler.sendEmptyMessage(Constant.MSG_ERROR);
e.printStackTrace();
// 服务端退出一直监听线程
break;
}
if(socket != null) {
// 管理连接的客户端socket
manageConnectSocket(socket);
// 这里应该是手动断开,案例应该是只保证连接一个客户端,所以连接完以后,关闭了服务端socket
// try {
// mmServerSocket.close();
// mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
// } catch (IOException e) {
// e.printStackTrace();
// }
}
}
}
/**
* 管理连接的客户端socket
* @param socket
*/
private void manageConnectSocket(BluetoothSocket socket) {
// 只支持同时处理一个连接
// mConnectedThread不为空,踢掉之前的客户端
if(mConnectedThread != null) {
mConnectedThread.cancle();
}
// 主线程更新UI,连接到了一个客户端
mHandler.sendEmptyMessage(Constant.MSG_GOT_A_CLINET);
// 新建一个线程,处理客户端发来的数据
mConnectedThread = new ConnectedThread(socket, mHandler);
mConnectedThread.start();
}
/**
* 断开服务端,结束监听
*/
public void cancle() {
try {
mmServerSocket.close();
mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发送数据
* @param data
*/
public void sendData(byte[] data){
if(mConnectedThread != null) {
mConnectedThread.write(data);
}
}
}
六、共同通讯处理类
public class ConnectedThread extends Thread{
/** 当前连接的客户端BluetoothSocket*/
private final BluetoothSocket mmSokcet;
/** 读取数据流*/
private final InputStream mmInputStream;
/** 发送数据流*/
private final OutputStream mmOutputStream;
/** 与主线程通信Handler*/
private Handler mHandler;
private String TAG = "ConnectedThread";
public ConnectedThread(BluetoothSocket socket,Handler handler) {
mmSokcet = socket;
mHandler = handler;
InputStream tmpIn = null;
OutputStream tmpOut = null;
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
mmInputStream = tmpIn;
mmOutputStream = tmpOut;
}
@Override
public void run() {
super.run();
byte[] buffer = new byte[1024];
while (true) {
try {
// 读取数据
int bytes = mmInputStream.read(buffer);
if(bytes > 0) {
String data = new String(buffer,0,bytes,"utf-8");
// 把数据发送到主线程, 此处还可以用广播
Message message = mHandler.obtainMessage(Constant.MSG_GOT_DATA,data);
mHandler.sendMessage(message);
}
Log.d(TAG, "messge size :" + bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 踢掉当前客户端
public void cancle() {
try {
mmSokcet.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 服务端发送数据
* @param data
*/
public void write(byte[] data) {
try {
mmOutputStream.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
核心代码
package com.example.bluetoothdemo;
import android.Manifest;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.sql.SQLOutput;
import java.util.UUID;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
public static final int REQUEST_BT_ENABLE_CODE = 200;
public static final String BT_UUID = "00001101-0000-1000-8000-00805F9B34FB";//uuid
private BluetoothAdapter mBluetoothAdapter;//蓝牙适配器
private BlueToothStateReceiver mReceiver;//广播接收器
private ConnectThread mConnectThread; //客户端线程
private AcceptThread mAcceptThread; //服务端线程
private RvAdapter mRvAdapter;
@SuppressLint("StaticFieldLeak")
private static MsgAdapter mMessageAdapter;
private EditText inputEt;
@SuppressLint("HandlerLeak")
private static final Handler mHandler = new Handler() {
@Override
public void dispatchMessage(Message msg) {
mMessageAdapter.addMessage((String) msg.obj);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//这是我是为了6.0以上的设备能搜索到结果,动态申请了位置权限。但是没有处理结果,因为我测试肯定点同意~- -
requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, 1001);
initUI();
registerRec();
}
private void initUI() {
findViewById(R.id.open).setOnClickListener(this);
findViewById(R.id.close).setOnClickListener(this);
findViewById(R.id.start).setOnClickListener(this);
findViewById(R.id.stop).setOnClickListener(this);
findViewById(R.id.send).setOnClickListener(this);
inputEt = (EditText) findViewById(R.id.input);
RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.devices);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRvAdapter = new RvAdapter(this);
mRecyclerView.setAdapter(mRvAdapter);
mRvAdapter.setOnItemClickListener(new RvAdapter.OnItemClickListener() {
@Override
public void onClick(BluetoothDevice device) {
mConnectThread = new ConnectThread(device);
mConnectThread.start();
}
});
RecyclerView mMessageView = (RecyclerView) findViewById(R.id.msglist);
mMessageView.setLayoutManager(new LinearLayoutManager(this));
mMessageAdapter = new MsgAdapter(this);
mMessageView.setAdapter(mMessageAdapter);
}
private void openBT() {
if (mBluetoothAdapter == null) {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
//1.设备不支持蓝牙,结束应用
if (mBluetoothAdapter == null) {
finish();
return;
}
//2.判断蓝牙是否打开
try {
if (!mBluetoothAdapter.enable()) {
//没打开请求打开
Intent btEnable = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(btEnable, REQUEST_BT_ENABLE_CODE);
}
}
catch (Exception EX) {
System.out.println("异常");
}
}
private void registerRec() {
//3.注册蓝牙广播
mReceiver = new BlueToothStateReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);//搜多到蓝牙
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//搜索结束
registerReceiver(mReceiver, filter);
}
@Override
protected void onDestroy() {
if (mReceiver != null) {
unregisterReceiver(mReceiver);
}
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_BT_ENABLE_CODE) {
if (resultCode == RESULT_OK) {
//用户允许打开蓝牙
mMessageAdapter.addMessage("用户同意打开蓝牙");
} else if (resultCode == RESULT_CANCELED) {
//用户取消打开蓝牙
mMessageAdapter.addMessage("用户拒绝打开蓝牙");
}
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.open:
openBT();
mMessageAdapter.addMessage("打开蓝牙");
if (mAcceptThread == null && mBluetoothAdapter != null) {
mAcceptThread = new AcceptThread();
mAcceptThread.start();
mMessageAdapter.addMessage("启动服务线程");
}
break;
case R.id.close:
mBluetoothAdapter.disable();
break;
case R.id.start:
if (mBluetoothAdapter != null) {
mRvAdapter.clearDevices();//开始搜索前清空上一次的列表
mBluetoothAdapter.startDiscovery();
mMessageAdapter.addMessage("开始搜索蓝牙");
} else {
openBT();
if (mBluetoothAdapter != null) {
mRvAdapter.clearDevices();//开始搜索前清空上一次的列表
mBluetoothAdapter.startDiscovery();
mMessageAdapter.addMessage("开始搜索蓝牙");
}
}
break;
case R.id.stop:
if (mBluetoothAdapter != null && mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
break;
case R.id.send:
String msg = inputEt.getText().toString();
if (TextUtils.isEmpty(msg)) {
Toast.makeText(this, "消息为空", Toast.LENGTH_SHORT).show();
return;
}
if (mConnectThread != null) {//证明我主动去链接别人了
mConnectThread.write(msg);
} else if (mAcceptThread != null) {
mAcceptThread.write(msg);
}
mMessageAdapter.addMessage("发送消息:" + msg);
break;
}
}
class BlueToothStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(MainActivity.this, "触发广播", Toast.LENGTH_SHORT).show();
String action = intent.getAction();
switch (action) {
case BluetoothDevice.ACTION_FOUND:
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Toast.makeText(MainActivity.this, "找到设备" + device.getName(), Toast.LENGTH_SHORT).show();
if (mRvAdapter != null) {
mRvAdapter.addDevice(device);
}
break;
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
mMessageAdapter.addMessage("搜索结束");
break;
}
}
}
class ConnectThread extends Thread {
private BluetoothDevice mDevice;
private BluetoothSocket mSocket;
private InputStream btIs;
private OutputStream btOs;
private boolean canRecv;
private PrintWriter writer;
public ConnectThread(BluetoothDevice device) {
mDevice = device;
canRecv = true;
}
@Override
public void run() {
if (mDevice != null) {
try {
//获取套接字
BluetoothSocket temp = mDevice.createInsecureRfcommSocketToServiceRecord(UUID.fromString(BT_UUID));
//mDevice.createRfcommSocketToServiceRecord(UUID.fromString(BT_UUID));//sdk 2.3以下使用
mSocket = temp;
//发起连接请求
if (mSocket != null) {
mSocket.connect();
}
sendHandlerMsg("连接 " + mDevice.getName() + "成功!");
//获取输入输出流
btIs = mSocket.getInputStream();
btOs = mSocket.getOutputStream();
//通讯-接收消息
BufferedReader reader = new BufferedReader(new InputStreamReader(btIs, "UTF-8"));
String content = null;
while (canRecv) {
content = reader.readLine();
sendHandlerMsg("收到消息:" + content);
}
} catch (IOException e) {
e.printStackTrace();
sendHandlerMsg("错误:" + e.getMessage());
} finally {
try {
if (mSocket != null) {
mSocket.close();
}
//btIs.close();//两个输出流都依赖socket,关闭socket即可
//btOs.close();
} catch (IOException e) {
e.printStackTrace();
sendHandlerMsg("错误:" + e.getMessage());
}
}
}
}
private void sendHandlerMsg(String content) {
Message msg = mHandler.obtainMessage();
msg.what = 1001;
msg.obj = content;
mHandler.sendMessage(msg);
}
public void write(String msg) {
if (btOs != null) {
try {
if (writer == null) {
writer = new PrintWriter(new OutputStreamWriter(btOs, "UTF-8"), true);
}
writer.println(msg);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
writer.close();
sendHandlerMsg("错误:" + e.getMessage());
}
}
}
}
class AcceptThread extends Thread {
private BluetoothServerSocket mServerSocket;
private BluetoothSocket mSocket;
private InputStream btIs;
private OutputStream btOs;
private PrintWriter writer;
private boolean canAccept;
private boolean canRecv;
public AcceptThread() {
canAccept = true;
canRecv = true;
}
@Override
public void run() {
try {
//获取套接字
try {
BluetoothServerSocket temp = mBluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord("TEST", UUID.fromString(BT_UUID));
mServerSocket = temp;
}
catch (Exception exception) {
System.out.println("异常");
}
//监听连接请求 -- 作为测试,只允许连接一个设备
if (mServerSocket != null) {
// while (canAccept) {
mSocket = mServerSocket.accept();
sendHandlerMsg("有客户端连接");
// }
}
//获取输入输出流
btIs = mSocket.getInputStream();
btOs = mSocket.getOutputStream();
//通讯-接收消息
BufferedReader reader = new BufferedReader(new InputStreamReader(btIs, "UTF-8"));
String content = null;
while (canRecv) {
content = reader.readLine();
sendHandlerMsg("收到消息:" + content);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (mSocket != null) {
mSocket.close();
}
// btIs.close();//两个输出流都依赖socket,关闭socket即可
// btOs.close();
} catch (IOException e) {
e.printStackTrace();
sendHandlerMsg("错误:" + e.getMessage());
}
}
}
private void sendHandlerMsg(String content) {
Message msg = mHandler.obtainMessage();
msg.what = 1001;
msg.obj = content;
mHandler.sendMessage(msg);
}
public void write(String msg) {
if (btOs != null) {
try {
if (writer == null) {
writer = new PrintWriter(new OutputStreamWriter(btOs, "UTF-8"), true);
}
writer.println(msg);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
writer.close();
sendHandlerMsg("错误:" + e.getMessage());
}
}
}
}
}