BluetoothChat(蓝牙聊天室),对官方Demo的解读
2020-04-11 本文已影响0人
仕明同学
image.png
这个应用程序允许两个Android设备进行双向文本聊天蓝牙。它演示了所有基本的蓝牙API功能,例如:
(1) 正在扫描其他蓝牙设备
(2) 为配对的蓝牙设备查询本地蓝牙适配器
(3) 建立RFCOMM信道/套接字
(4) 连接到远程设备
(5) 通过蓝牙传输数据
- 权限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
扫描其他蓝牙设备
- 通过系统的类获取蓝牙设备的列表
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
- 如果正在扫描 就取消
if (mBtAdapter.isDiscovering()) {
mBtAdapter.cancelDiscovery();
}
mBtAdapter.startDiscovery();
- 最主要的就是获取17为的MAC地址
String info = ((TextView) v).getText().toString();
String address = info.substring(info.length() - 17);
- 同时注册广播,发现了设备
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, filter);
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, filter);
- 获取到mac地址就可以获取蓝牙的设备信息
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
首先开启一个子线程监听Socket中的消息 AcceptThread 获取安全和安全的Socket ,我觉得可以理解一个应答的线程
private final BluetoothServerSocket mmServerSocket;
private String mSocketType;
public AcceptThread(boolean secure) {
BluetoothServerSocket tmp = null;
mSocketType = secure ? "Secure" : "Insecure";
// Create a new listening server socket
try {
if (secure) {
tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE,
MY_UUID_SECURE);
} else {
tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(
NAME_INSECURE, MY_UUID_INSECURE);
}
} catch (IOException e) {
Log.e(TAG, "Socket Type: " + mSocketType + "listen() failed", e);
}
mmServerSocket = tmp;
mState = STATE_LISTEN;
}
- run的方法 关键是 connected(socket, socket.getRemoteDevice(),mSocketType);
public void run() {
Log.d(TAG, "Socket Type: " + mSocketType +
"BEGIN mAcceptThread" + this);
setName("AcceptThread" + mSocketType);
BluetoothSocket socket;
// Listen to the server socket if we're not connected
while (mState != STATE_CONNECTED) {
try {
// This is a blocking call and will only return on a
// successful connection or an exception
socket = mmServerSocket.accept();
} catch (IOException e) {
Log.e(TAG, "Socket Type: " + mSocketType + "accept() failed", e);
break;
}
// If a connection was accepted
if (socket != null) {
synchronized (BluetoothChatService.this) {
switch (mState) {
case STATE_LISTEN:
case STATE_CONNECTING:
// Situation normal. Start the connected thread.
connected(socket, socket.getRemoteDevice(),
mSocketType);
break;
case STATE_NONE:
case STATE_CONNECTED:
// Either not ready or already connected. Terminate new socket.
try {
socket.close();
} catch (IOException e) {
Log.e(TAG, "Could not close unwanted socket", e);
}
break;
}
}
}
}
Log.i(TAG, "END mAcceptThread, socket Type: " + mSocketType);
}
- connect的方法 ,直接和 ConnectedThread 产生关系,这样就可以发送消息了
public synchronized void connected(BluetoothSocket socket, BluetoothDevice
device, final String socketType) {
Log.d(TAG, "connected, Socket Type:" + socketType);
// Cancel the thread that completed the connection
if (mConnectThread != null) {
mConnectThread.cancel();
mConnectThread = null;
}
// Cancel any thread currently running a connection
if (mConnectedThread != null) {
mConnectedThread.cancel();
mConnectedThread = null;
}
// Cancel the accept thread because we only want to connect to one device
if (mSecureAcceptThread != null) {
mSecureAcceptThread.cancel();
mSecureAcceptThread = null;
}
if (mInsecureAcceptThread != null) {
mInsecureAcceptThread.cancel();
mInsecureAcceptThread = null;
}
// Start the thread to manage the connection and perform transmissions
mConnectedThread = new ConnectedThread(socket, socketType);
mConnectedThread.start();
// Send the name of the connected device back to the UI Activity
Message msg = mHandler.obtainMessage(Constants.MESSAGE_DEVICE_NAME);
Bundle bundle = new Bundle();
bundle.putString(Constants.DEVICE_NAME, device.getName());
msg.setData(bundle);
mHandler.sendMessage(msg);
// Update UI title
updateUserInterfaceTitle();
}
如何发送消息 ?
1、获取字节数组 ,通过ChatService的方式
byte[] send = message.getBytes();
mChatService.write(send);
2、通过在子线程中写入到 OutputStream 流中
private final OutputStream mmOutStream;
3、将发送的消息共享回UI活动 在子线程共享到主线程 mmOutStream
public void write(byte[] buffer) {
try {
mmOutStream.write(buffer);
// 将发送的消息共享回UI活动 在子线程共享到主线程
mHandler.obtainMessage(Constants.MESSAGE_WRITE, -1, -1, buffer)
.sendToTarget();
} catch (IOException e) {
Log.e(TAG, "Exception during write", e);
}
}
4、outputStream其实是通过BluetoothSocket 获取而来
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
5、读取到的消息回调到主线程中, 这就完成信息的读取和输入
public void run() {
Log.i(TAG, "BEGIN mConnectedThread");
byte[] buffer = new byte[1024];
int bytes;
// Keep listening to the InputStream while connected
while (mState == STATE_CONNECTED) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// 把读取的消息回调到主线程中
mHandler.obtainMessage(Constants.MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
Log.e(TAG, "disconnected", e);
connectionLost();
break;
}
}
}
- 对官方Demo的解读
- GitHub有些自己的注释
- 底层就是一个Socket,如果用Java做过,就很好理解,关键是如何使用子线程去避免ANR?这个需要去思考
- Socket是一个好性能的东西,所以需要思考如何关闭
- Socket只有一个实例,所以当某一步发生了失败的话,都需要把它 close
mmServerSocket.close();
- 使用BluetoothAdapter类的listenUsingRfcommWithServiceRecord方法来新建一个ServerSocket。在listenUsingRfcommWithServiceRecord中有一个参数叫做UUID,UUID(Universally Unique Identifier)是一个128位的字符串ID,被用于唯一标识我们的蓝牙服务
- 线程取消还要使用java上null回收标志
if (mInsecureAcceptThread != null) { mInsecureAcceptThread.cancel(); mInsecureAcceptThread = null; }
- UUID可以在网上去申请
- 如果我要发语音呢? 那是不是有点像对讲机了 但是作为安卓应用好像有点大材小用了 。。。是吧!自己感觉 哈哈