public synchronized void start() :
开启 mAcceptThread 线程,由于样例程序是仅 2 人的聊天过程,故之前先检测 mConnectThread 和 mConnectedThread 是否运行,运行则先退出这些线程。
public synchronized void connect(BluetoothDevice device) :
取消 CONNECTING 和 CONNECTED 状态下的相关线程,然后运行新的 mConnectThread 线程。
public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) :
开启一个 ConnectedThread 来管理对应的当前连接。之前先取消任意现存的 mConnectThread 、 mConnectedThread 、 mAcceptThread 线程,然后开启新 mConnectedThread ,传入当前刚刚接受的 socket 连接。最后通过 Handler 来通知 UI 连接 OK 。
public synchronized void stop() :
停止所有相关线程,设当前状态为 NONE 。
public void write(byte[] out) :
在 STATE_CONNECTED 状态下,调用 mConnectedThread 里的 write 方法,写入 byte 。
private void connectionFailed() :
连接失败的时候处理,通知 ui ,并设为 STATE_LISTEN 状态。
private void connectionLost() :
当连接失去的时候,设为 STATE_LISTEN 状态并通知 ui 。
内部类:
private class AcceptThread extends Thread :
创建监听线程,准备接受新连接。使用阻塞方式,调用 BluetoothServerSocket.accept() 。提供 cancel 方法关闭 socket 。
private class ConnectThread extends Thread :
这是定义的连接线程,专门用来对外发出连接对方蓝牙的请求和处理流程。构造函数里通过 BluetoothDevice.createRfcommSocketToServiceRecord() ,从待连接的 device 产生 BluetoothSocket. 然后在 run 方法中 connect ,成功后调用 BluetoothChatSevice 的 connected() 方法。定义 cancel() 在关闭线程时能够关闭相关 socket 。
private class ConnectedThread extends Thread :
这个是双方蓝牙连接后一直运行的线程。构造函数中设置输入输出流。 Run 方法中使用阻塞模式的 InputStream.read() 循环读取输入流, 然后 post 到 UI 线程中更新聊天消息。也提供了 write() 将聊天消息写入输出流传输至对方,传输成功后回写入 UI 线程。最后 cancel() 关闭连接的 socket 。
BluetoothChatService
1、常量变量
对于设备的监听,连接管理都将由REQUEST_CONNECT_DEVICE来实现,其中又包括三个主要部分,三个进程分别是:
请求连接的监听线程(AcceptThread)
连接一个设备的进程(ConnectThread)
连接之后的管理进程(ConnectedThread)
同样我们先熟悉一下该类的成员变量的作用,定义如下:
- // Debugging
- private staticfinal String TAG ="BluetoothChatService";
- private static finalboolean D =true;
- //当创建socket服务时的SDP名称
- private staticfinal String NAME ="BluetoothChat";
- // 应用程序的唯一UUID
- private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");
- // 本地蓝牙适配器
- private final BluetoothAdapter mAdapter;
- //Handler
- private final Handler mHandler;
- //请求链接的监听线程
- private AcceptThread mAcceptThread;
- //链接一个设备的线程
- private ConnectThread mConnectThread;
- //已经链接之后的管理线程
- private ConnectedThread mConnectedThread;
- //当前的状态
- private int mState;
- // 各种状态
- public staticfinalint STATE_NONE =0;
- public staticfinalint STATE_LISTEN =1;
- public staticfinalint STATE_CONNECTING =2;
- public static finalint STATE_CONNECTED =3;
// Debugging private static final String TAG = "BluetoothChatService"; private static final boolean D = true; //当创建socket服务时的SDP名称 private static final String NAME = "BluetoothChat"; // 应用程序的唯一UUID private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66"); // 本地蓝牙适配器 private final BluetoothAdapter mAdapter; //Handler private final Handler mHandler; //请求链接的监听线程 private AcceptThread mAcceptThread; //链接一个设备的线程 private ConnectThread mConnectThread; //已经链接之后的管理线程 private ConnectedThread mConnectedThread; //当前的状态 private int mState; // 各种状态 public static final int STATE_NONE = 0; public static final int STATE_LISTEN = 1; public static final int STATE_CONNECTING = 2; public static final int STATE_CONNECTED = 3;
Debugging为调试相关,NAME 是当我们在创建一个socket监听服务时的一个SDP名称,另外还包括一个状态变量mState,其值则分别是下面的"各种状态"部分,另外还有一个本地蓝牙适配器和三个不同的进程对象,由此可见,本地蓝牙适配器的确是任何蓝牙操作的基础对象,下面我们会分别介绍这些进程的实现。
BluetoothChatService(Context context, Handler handler)_构造函数
首先是初始化操作,即构造函数,代码如下:
- public BluetoothChatService(Context context, Handler handler) {
- //得到本地蓝牙适配器
- mAdapter = BluetoothAdapter.getDefaultAdapter();
- //设置状态
- mState = STATE_NONE;
- //设置Handler
- mHandler = handler;
- }
public BluetoothChatService(Context context, Handler handler) { //得到本地蓝牙适配器 mAdapter = BluetoothAdapter.getDefaultAdapter(); //设置状态 mState = STATE_NONE; //设置Handler mHandler = handler; }
取得本地蓝牙适配器、设置状态为STATE_NONE,设置传递进来的mHandler。接下来需要控制当状态改变之后,我们需要通知UI界面也同时更改状态,下面是得到状态和设置状态的实现部分,如下:
synchronizedvoid setState(int state)
- private synchronizedvoid setState(int state) {
- if (D) Log.d(TAG, "setState() " + mState +" -> " + state);
- mState = state;
- // 状态更新之后UI Activity也需要更新
- mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
- }
- public synchronizedint getState() {
- return mState;
- }
private synchronized void setState(int state) { if (D) Log.d(TAG, "setState() " + mState + " -> " + state); mState = state; // 状态更新之后UI Activity也需要更新 mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state, -1).sendToTarget(); } public synchronized int getState() { return mState; }
得到状态没有什么特别的,关键在于设置状态之后需要通过obtainMessage来发送一个消息到Handler,通知UI界面也同时更新其状态,对应的Handler的实现则位于BluetoothChat中的private final Handler mHandler = new Handler()部分,从上面的代码中,我们可以看到关于状态更改的之后会发送一个BluetoothChat.MESSAGE_STATE_CHANGE消息到UI线程中,下面我们看一下UI线程中如何处理这些消息的,代码如下:
UI线程处理消息
- case MESSAGE_STATE_CHANGE:
- if(D) Log.i(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1);
- switch (msg.arg1) {
- case BluetoothChatService.STATE_CONNECTED:
- //设置状态为已经链接
- mTitle.setText(R.string.title_connected_to);
- //添加设备名称
- mTitle.append(mConnectedDeviceName);
- //清理聊天记录
- mConversationArrayAdapter.clear();
- break;
- case BluetoothChatService.STATE_CONNECTING:
- //设置正在链接
- mTitle.setText(R.string.title_connecting);
- break;
- case BluetoothChatService.STATE_LISTEN:
- case BluetoothChatService.STATE_NONE:
- //处于监听状态或者没有准备状态,则显示没有链接
- mTitle.setText(R.string.title_not_connected);
- break;
- }
- break;
case MESSAGE_STATE_CHANGE: if(D) Log.i(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1); switch (msg.arg1) { case BluetoothChatService.STATE_CONNECTED: //设置状态为已经链接 mTitle.setText(R.string.title_connected_to); //添加设备名称 mTitle.append(mConnectedDeviceName); //清理聊天记录 mConversationArrayAdapter.clear(); break; case BluetoothChatService.STATE_CONNECTING: //设置正在链接 mTitle.setText(R.string.title_connecting); break; case BluetoothChatService.STATE_LISTEN: case BluetoothChatService.STATE_NONE: //处于监听状态或者没有准备状态,则显示没有链接 mTitle.setText(R.string.title_not_connected); break; } break;
可以看出,当不同的状态在更改之后会进行不同的设置,但是大多数都是根据不同的状态设置显示了不同的title,当已经链接(STATE_CONNECTED)之后,设置了标题为链接的设备名,并同时还mConversationArrayAdapter进行了清除操作,即清除聊天记录。
start函数来开启一个服务进程_onResume函数调用start操作
现在,初始化操作已经完成了,下面我们可以调用start函数来开启一个服务进程了,也即是在BluetoothChat中的onResume函数中所调用的start操作,其具体实现如下:
- public synchronizedvoid start() {
- if (D) Log.d(TAG, "start");
- // 取消任何线程视图建立一个连接
- if (mConnectThread !=null) {mConnectThread.cancel(); mConnectThread =null;}
- // 取消任何正在运行的链接
- if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread =null;}
- // 启动AcceptThread线程来监听BluetoothServerSocket
- if (mAcceptThread ==null) {
- mAcceptThread = new AcceptThread();
- mAcceptThread.start();
- }
- //设置状态为监听,,等待链接
- setState(STATE_LISTEN);
- }
public synchronized void start() { if (D) Log.d(TAG, "start"); // 取消任何线程视图建立一个连接 if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} // 取消任何正在运行的链接 if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} // 启动AcceptThread线程来监听BluetoothServerSocket if (mAcceptThread == null) { mAcceptThread = new AcceptThread(); mAcceptThread.start(); } //设置状态为监听,,等待链接 setState(STATE_LISTEN); }
操作过程很简单,首先取消另外两个进程,新建一个AcceptThread进程,并启动AcceptThread进程,最后设置状态变为监听(STATE_LISTEN),这时UI界面的title也将更新为监听状态,即等待设备的连接。关于AcceptThread的具体实现如下所示。
新建一个AcceptThread进程
- private class AcceptThreadextends Thread {
- // 本地socket服务
- private final BluetoothServerSocket mmServerSocket;
- public AcceptThread() {
- BluetoothServerSocket tmp = null;
- // 创建一个新的socket服务监听
- try {
- tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
- } catch (IOException e) {
- Log.e(TAG, "listen() failed", e);
- }
- mmServerSocket = tmp;
- }
- public void run() {
- if (D) Log.d(TAG, "BEGIN mAcceptThread" +this);
- setName("AcceptThread");
- BluetoothSocket socket = null;
- // 如果当前没有链接则一直监听socket服务
- while (mState != STATE_CONNECTED) {
- try {
- //如果有请求链接,则接受
- //这是一个阻塞调用,将之返回链接成功和一个异常
- socket = mmServerSocket.accept();
- } catch (IOException e) {
- Log.e(TAG, "accept() failed", e);
- break;
- }
- // 如果接受了一个链接
- if (socket != null) {
- synchronized (BluetoothChatService.this) {
- switch (mState) {
- case STATE_LISTEN:
- case STATE_CONNECTING:
- // 如果状态为监听或者正在链接中,,则调用connected来链接
- connected(socket, socket.getRemoteDevice());
- break;
- case STATE_NONE:
- case STATE_CONNECTED:
- // 如果为没有准备或者已经链接,这终止该socket
- try {
- socket.close();
- } catch (IOException e) {
- Log.e(TAG, "Could not close unwanted socket", e);
- }
- break;
- }
- }
- }
- }
- if (D) Log.i(TAG,"END mAcceptThread");
- }
- //关闭BluetoothServerSocket
- public void cancel() {
- if (D) Log.d(TAG,"cancel " +this);
- try {
- mmServerSocket.close();
- } catch (IOException e) {
- Log.e(TAG, "close() of server failed", e);
- }
- }
- }
private class AcceptThread extends Thread { // 本地socket服务 private final BluetoothServerSocket mmServerSocket; public AcceptThread() { BluetoothServerSocket tmp = null; // 创建一个新的socket服务监听 try { tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID); } catch (IOException e) { Log.e(TAG, "listen() failed", e); } mmServerSocket = tmp; } public void run() { if (D) Log.d(TAG, "BEGIN mAcceptThread" + this); setName("AcceptThread"); BluetoothSocket socket = null; // 如果当前没有链接则一直监听socket服务 while (mState != STATE_CONNECTED) { try { //如果有请求链接,则接受 //这是一个阻塞调用,将之返回链接成功和一个异常 socket = mmServerSocket.accept(); } catch (IOException e) { Log.e(TAG, "accept() failed", e); break; } // 如果接受了一个链接 if (socket != null) { synchronized (BluetoothChatService.this) { switch (mState) { case STATE_LISTEN: case STATE_CONNECTING: // 如果状态为监听或者正在链接中,,则调用connected来链接 connected(socket, socket.getRemoteDevice()); break; case STATE_NONE: case STATE_CONNECTED: // 如果为没有准备或者已经链接,这终止该socket try { socket.close(); } catch (IOException e) { Log.e(TAG, "Could not close unwanted socket", e); } break; } } } } if (D) Log.i(TAG, "END mAcceptThread"); } //关闭BluetoothServerSocket public void cancel() { if (D) Log.d(TAG, "cancel " + this); try { mmServerSocket.close(); } catch (IOException e) { Log.e(TAG, "close() of server failed", e); } } }
首先通过listenUsingRfcommWithServiceRecord创建一个socket服务,用来监听设备的连接,当进程启动之后直到有设备连接时,这段时间都将通过accept来监听和接收一个连接请求,如果连接无效则调用close来关闭即可,如果连接有效则调用connected进入连接进程,进入连接进程之后会取消当前的监听进程,取消过程则直接调用cancel通过mmServerSocket.close()来关闭即可。下面我们分析连接函数connect的实现,如下:
connect(BluetoothDevice device) _连接
- public synchronizedvoid connect(BluetoothDevice device) {
- if (D) Log.d(TAG, "connect to: " + device);
- // 取消任何链接线程,视图建立一个链接
- if (mState == STATE_CONNECTING) {
- if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread =null;}
- }
- // 取消任何正在运行的线程
- if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread =null;}
- // 启动一个链接线程链接指定的设备
- mConnectThread = new ConnectThread(device);
- mConnectThread.start();
- setState(STATE_CONNECTING);
- }
public synchronized void connect(BluetoothDevice device) { if (D) Log.d(TAG, "connect to: " + device); // 取消任何链接线程,视图建立一个链接 if (mState == STATE_CONNECTING) { if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} } // 取消任何正在运行的线程 if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} // 启动一个链接线程链接指定的设备 mConnectThread = new ConnectThread(device); mConnectThread.start(); setState(STATE_CONNECTING); }
同样,首先关闭其他两个进程,然后新建一个ConnectThread进程,并启动,通知UI界面状态更改为正在连接的状态(STATE_CONNECTING)。具体的连接进程由ConnectThread来实现,如下:
ConnectThread
- private class ConnectThreadextends Thread {
- //蓝牙Socket
- private final BluetoothSocket mmSocket;
- //蓝牙设备
- private final BluetoothDevice mmDevice;
- public ConnectThread(BluetoothDevice device) {
- mmDevice = device;
- BluetoothSocket tmp = null;
- //得到一个给定的蓝牙设备的BluetoothSocket
- try {
- tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
- } catch (IOException e) {
- Log.e(TAG, "create() failed", e);
- }
- mmSocket = tmp;
- }
- public void run() {
- Log.i(TAG, "BEGIN mConnectThread");
- setName("ConnectThread");
- // 取消可见状态,将会进行链接
- mAdapter.cancelDiscovery();
- // 创建一个BluetoothSocket链接
- try {
- //同样是一个阻塞调用,返回成功和异常
- mmSocket.connect();
- } catch (IOException e) {
- //链接失败
- connectionFailed();
- // 如果异常则关闭socket
- try {
- mmSocket.close();
- } catch (IOException e2) {
- Log.e(TAG, "unable to close() socket during connection failure", e2);
- }
- // 重新启动监听服务状态
- BluetoothChatService.this.start();
- return;
- }
- // 完成则重置ConnectThread
- synchronized (BluetoothChatService.this) {
- mConnectThread = null;
- }
- // 开启ConnectedThread(正在运行中...)线程
- connected(mmSocket, mmDevice);
- }
- //取消链接线程ConnectThread
- public void cancel() {
- try {
- mmSocket.close();
- } catch (IOException e) {
- Log.e(TAG, "close() of connect socket failed", e);
- }
- }
- }
private class ConnectThread extends Thread { //蓝牙Socket private final BluetoothSocket mmSocket; //蓝牙设备 private final BluetoothDevice mmDevice; public ConnectThread(BluetoothDevice device) { mmDevice = device; BluetoothSocket tmp = null; //得到一个给定的蓝牙设备的BluetoothSocket try { tmp = device.createRfcommSocketToServiceRecord(MY_UUID); } catch (IOException e) { Log.e(TAG, "create() failed", e); } mmSocket = tmp; } public void run() { Log.i(TAG, "BEGIN mConnectThread"); setName("ConnectThread"); // 取消可见状态,将会进行链接 mAdapter.cancelDiscovery(); // 创建一个BluetoothSocket链接 try { //同样是一个阻塞调用,返回成功和异常 mmSocket.connect(); } catch (IOException e) { //链接失败 connectionFailed(); // 如果异常则关闭socket try { mmSocket.close(); } catch (IOException e2) { Log.e(TAG, "unable to close() socket during connection failure", e2); } // 重新启动监听服务状态 BluetoothChatService.this.start(); return; } // 完成则重置ConnectThread synchronized (BluetoothChatService.this) { mConnectThread = null; } // 开启ConnectedThread(正在运行中...)线程 connected(mmSocket, mmDevice); } //取消链接线程ConnectThread public void cancel() { try { mmSocket.close(); } catch (IOException e) { Log.e(TAG, "close() of connect socket failed", e); } } }
在创建该进程时,就已经知道当前需要被连接的蓝牙设备,然后通过createRfcommSocketToServiceRecord可以构建一个蓝牙设备的BluetoothSocket对象,当进入连接状态时,就可以调用cancelDiscovery来取消蓝牙的可见状态,然后通过调用connect函数进行链接操作,如果出现异常则表示链接失败,则调用connectionFailed函数通知UI进程更新界面的显示为链接失败状态,然后关闭BluetoothSocket,调用start函数重新开启一个监听服务AcceptThread,对于链接失败的处理实现如下:
connectionFailed() _链接失败
- private void connectionFailed() {
- setState(STATE_LISTEN);
- // 发送链接失败的消息到UI界面
- Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);
- Bundle bundle = new Bundle();
- bundle.putString(BluetoothChat.TOAST, "Unable to connect device");
- msg.setData(bundle);
- mHandler.sendMessage(msg);
- }
private void connectionFailed() { setState(STATE_LISTEN); // 发送链接失败的消息到UI界面 Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString(BluetoothChat.TOAST, "Unable to connect device"); msg.setData(bundle); mHandler.sendMessage(msg); }
首先更改状态为STATE_LISTEN,然后发送一个Message带UI界面,通知UI更新,显示一个Toast告知用户,当BluetoothChat中的mHandler接收到BluetoothChat.TOAST消息时,就会直接更新UI界面的显示,如果连接成功则将调用connected函数进入连接管理进程,其实现如下:
connected(BluetoothSocket socket, BluetoothDevice device) _成功则进入连接管理进程
- public synchronizedvoid connected(BluetoothSocket socket, BluetoothDevice device) {
- if (D) Log.d(TAG, "connected");
- // 取消ConnectThread链接线程
- if (mConnectThread !=null) {mConnectThread.cancel(); mConnectThread =null;}
- // 取消所有正在链接的线程
- if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread =null;}
- // 取消所有的监听线程,因为我们已经链接了一个设备
- if (mAcceptThread !=null) {mAcceptThread.cancel(); mAcceptThread =null;}
- // 启动ConnectedThread线程来管理链接和执行翻译
- mConnectedThread = new ConnectedThread(socket);
- mConnectedThread.start();
- // 发送链接的设备名称到UI Activity界面
- Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME);
- Bundle bundle = new Bundle();
- bundle.putString(BluetoothChat.DEVICE_NAME, device.getName());
- msg.setData(bundle);
- mHandler.sendMessage(msg);
- //状态变为已经链接,即正在运行中
- setState(STATE_CONNECTED);
- }
public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) { if (D) Log.d(TAG, "connected"); // 取消ConnectThread链接线程 if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} // 取消所有正在链接的线程 if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} // 取消所有的监听线程,因为我们已经链接了一个设备 if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;} // 启动ConnectedThread线程来管理链接和执行翻译 mConnectedThread = new ConnectedThread(socket); mConnectedThread.start(); // 发送链接的设备名称到UI Activity界面 Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME); Bundle bundle = new Bundle(); bundle.putString(BluetoothChat.DEVICE_NAME, device.getName()); msg.setData(bundle); mHandler.sendMessage(msg); //状态变为已经链接,即正在运行中 setState(STATE_CONNECTED); }
首先,关闭所有的进程,构建一个ConnectedThread进程,并准备一个Message消息,就设备名称(BluetoothChat.DEVICE_NAME)也发送到UI进程,因为UI进程需要显示当前连接的设备名称,当UI进程收到BluetoothChat.MESSAGE_DEVICE_NAME消息时就会更新相应的UI界面,就是设置窗口的title,这里我们就不贴出代码了,下面我们分析一下ConnectedThread的实现,代码如下:
ConnectedThread_性息进出流的管理
- private class ConnectedThread extends Thread {
- //BluetoothSocket
- private final BluetoothSocket mmSocket;
- //输入输出流
- private final InputStream mmInStream;
- private final OutputStream mmOutStream;
- public ConnectedThread(BluetoothSocket socket) {
- Log.d(TAG, "create ConnectedThread");
- mmSocket = socket;
- InputStream tmpIn = null;
- OutputStream tmpOut = null;
- // 得到BluetoothSocket的输入输出流
- try {
- tmpIn = socket.getInputStream();
- tmpOut = socket.getOutputStream();
- } catch (IOException e) {
- Log.e(TAG, "temp sockets not created", e);
- }
- mmInStream = tmpIn;
- mmOutStream = tmpOut;
- }
- public void run() {
- Log.i(TAG, "BEGIN mConnectedThread");
- byte[] buffer = newbyte[1024];
- int bytes;
- // 监听输入流
- while (true) {
- try {
- // 从输入流中读取数据
- bytes = mmInStream.read(buffer);
- // 发送一个消息到UI线程进行更新
- mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer)
- .sendToTarget();
- } catch (IOException e) {
- //出现异常,则链接丢失
- Log.e(TAG, "disconnected", e);
- connectionLost();
- break;
- }
- }
- }
- /**
- * 写入药发送的消息
- * @param buffer The bytes to write
- */
- public void write(byte[] buffer) {
- try {
- mmOutStream.write(buffer);
- // 将写的消息同时传递给UI界面
- mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer)
- .sendToTarget();
- } catch (IOException e) {
- Log.e(TAG, "Exception during write", e);
- }
- }
- //取消ConnectedThread链接管理线程
- public void cancel() {
- try {
- mmSocket.close();
- } catch (IOException e) {
- Log.e(TAG, "close() of connect socket failed", e);
- }
- }
- }
private class ConnectedThread extends Thread { //BluetoothSocket private final BluetoothSocket mmSocket; //输入输出流 private final InputStream mmInStream; private final OutputStream mmOutStream; public ConnectedThread(BluetoothSocket socket) { Log.d(TAG, "create ConnectedThread"); mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; // 得到BluetoothSocket的输入输出流 try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { Log.e(TAG, "temp sockets not created", e); } mmInStream = tmpIn; mmOutStream = tmpOut; } public void run() { Log.i(TAG, "BEGIN mConnectedThread"); byte[] buffer = new byte[1024]; int bytes; // 监听输入流 while (true) { try { // 从输入流中读取数据 bytes = mmInStream.read(buffer); // 发送一个消息到UI线程进行更新 mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer) .sendToTarget(); } catch (IOException e) { //出现异常,则链接丢失 Log.e(TAG, "disconnected", e); connectionLost(); break; } } } /** * 写入药发送的消息 * @param buffer The bytes to write */ public void write(byte[] buffer) { try { mmOutStream.write(buffer); // 将写的消息同时传递给UI界面 mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer) .sendToTarget(); } catch (IOException e) { Log.e(TAG, "Exception during write", e); } } //取消ConnectedThread链接管理线程 public void cancel() { try { mmSocket.close(); } catch (IOException e) { Log.e(TAG, "close() of connect socket failed", e); } } }
连接之后的主要操作就是发送和接收聊天消息了,因为需要通过其输入(出)流来操作具体信息,进程会一直从输入流中读取信息,并通过obtainMessage函数将读取的信息以BluetoothChat.MESSAGE_READ命令发送到UI进程,到UI进程收到是,就需要将其显示到消息列表之中,同时对于发送消息,需要实行写操作write,其操作就是将要发送的消息写入到输出流mmOutStream中,并且以BluetoothChat.MESSAGE_WRITE命令的方式发送到UI进程中,进行同步更新,如果在读取消息时失败或者产生了异常,则表示连接丢失,这是就调用connectionLost函数来处理连接丢失,代码如下:
connectionLost函数_处理连接丢失
- private void connectionLost() {
- setState(STATE_LISTEN);
- // 发送失败消息到UI界面
- Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);
- Bundle bundle = new Bundle();
- bundle.putString(BluetoothChat.TOAST, "Device connection was lost");
- msg.setData(bundle);
- mHandler.sendMessage(msg);
- }
private void connectionLost() { setState(STATE_LISTEN); // 发送失败消息到UI界面 Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString(BluetoothChat.TOAST, "Device connection was lost"); msg.setData(bundle); mHandler.sendMessage(msg); }
操作同样简单,首先改变状态为STATE_LISTEN,然后BluetoothChat.MESSAGE_TOAST命令发送一个消息Message到UI进程,通知UI进程更新显示画面即可。对于写操作,是调用了BluetoothChatService.write来实现,其实现代码如下:
write(byte[] _写操作
- //写入自己要发送出来的消息
- public void write(byte[] out) {
- // Create temporary object
- ConnectedThread r;
- // Synchronize a copy of the ConnectedThread
- synchronized (this) {
- //判断是否处于已经链接状态
- if (mState != STATE_CONNECTED)return;
- r = mConnectedThread;
- }
- // 执行写
- r.write(out);
- }
//写入自己要发送出来的消息 public void write(byte[] out) { // Create temporary object ConnectedThread r; // Synchronize a copy of the ConnectedThread synchronized (this) { //判断是否处于已经链接状态 if (mState != STATE_CONNECTED) return; r = mConnectedThread; } // 执行写 r.write(out); }
其实就是检测,当前的状态是否处于已经链接状态STATE_CONNECTED,然后调用ConnectedThread 进程中的write操作,来完成消息的发送。因此这时我们可以回过头来看BluetoothChat中的sendMessage的实现了,如下所示:
sendMessage_发送数据
- private void sendMessage(String message) {
- // 检查是否处于连接状态
- if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {
- Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT).show();
- return;
- }
- // 如果输入的消息不为空才发送,否则不发送
- if (message.length() >0) {
- // Get the message bytes and tell the BluetoothChatService to write
- byte[] send = message.getBytes();
- mChatService.write(send);
- // Reset out string buffer to zero and clear the edit text field
- mOutStringBuffer.setLength(0);
- mOutEditText.setText(mOutStringBuffer);
- }
- }
private void sendMessage(String message) { // 检查是否处于连接状态 if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) { Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT).show(); return; } // 如果输入的消息不为空才发送,否则不发送 if (message.length() > 0) { // Get the message bytes and tell the BluetoothChatService to write byte[] send = message.getBytes(); mChatService.write(send); // Reset out string buffer to zero and clear the edit text field mOutStringBuffer.setLength(0); mOutEditText.setText(mOutStringBuffer); } }
同样首先检测了当前的状态是否为已经连接状态,然后对要发送的消息是否为null进行了判断,如果为空则不需要发送,否则调用mChatService.write(即上面所说的ConnectedThread 中的wirte操作)来发送消息。然后一个小的细节就是设置编辑框的内容为null即可。最后我们可以看一下在BluetoothChat中如何处理这些接收到的消息,主要位于mHandler中的handleMessage函数中,对于状态改变的消息我们已经分析过了,下面是其他几个消息的处理:
MESSAGE_WRITE_读写消息的处理
- case MESSAGE_WRITE:
- byte[] writeBuf = (byte[]) msg.obj;
- // 将自己写入的消息也显示到会话列表中
- String writeMessage = new String(writeBuf);
- mConversationArrayAdapter.add("Me: " + writeMessage);
- break;
- case MESSAGE_READ:
- byte[] readBuf = (byte[]) msg.obj;
- // 取得内容并添加到聊天对话列表中
- String readMessage = new String(readBuf,0, msg.arg1);
- mConversationArrayAdapter.add(mConnectedDeviceName+": " + readMessage);
- break;
- case MESSAGE_DEVICE_NAME:
- // 保存链接的设备名称,并显示一个toast提示
- mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);
- Toast.makeText(getApplicationContext(), "Connected to "
- + mConnectedDeviceName, Toast.LENGTH_SHORT).show();
- break;
- case MESSAGE_TOAST:
- //处理链接(发送)失败的消息
- Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST),
- Toast.LENGTH_SHORT).show();
- break;
case MESSAGE_WRITE: byte[] writeBuf = (byte[]) msg.obj; // 将自己写入的消息也显示到会话列表中 String writeMessage = new String(writeBuf); mConversationArrayAdapter.add("Me: " + writeMessage); break; case MESSAGE_READ: byte[] readBuf = (byte[]) msg.obj; // 取得内容并添加到聊天对话列表中 String readMessage = new String(readBuf, 0, msg.arg1); mConversationArrayAdapter.add(mConnectedDeviceName+": " + readMessage); break; case MESSAGE_DEVICE_NAME: // 保存链接的设备名称,并显示一个toast提示 mConnectedDeviceName = msg.getData().getString(DEVICE_NAME); Toast.makeText(getApplicationContext(), "Connected to " + mConnectedDeviceName, Toast.LENGTH_SHORT).show(); break; case MESSAGE_TOAST: //处理链接(发送)失败的消息 Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST), Toast.LENGTH_SHORT).show(); break;
分别是读取消息和写消息(发送消息),对于一些信息提示消息MESSAGE_TOAST,则通过Toast显示出来即可。如果消息是设备名称MESSAGE_DEVICE_NAME,则提示用户当前连接的设备的名称。对于写消息(MESSAGE_WRITE)和读消息(MESSAGE_READ)我们就不重复了,大家看看代码都已经加入了详细的注释了。
最后当我们在需要停止这些进程时就看有直接调用stop即可,具体实现如下:
停止所有的线程
- //停止所有的线程
- public synchronizedvoid stop() {
- if (D) Log.d(TAG,"stop");
- if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread =null;}
- if (mConnectedThread !=null) {mConnectedThread.cancel(); mConnectedThread =null;}
- if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread =null;}
- //状态设置为准备状态
- setState(STATE_NONE);
- }
//停止所有的线程 public synchronized void stop() { if (D) Log.d(TAG, "stop"); if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;} //状态设置为准备状态 setState(STATE_NONE); }
分别检测三个进程是否为null,然后调用各自的cancel函数来取消进程,最后不要忘记将状态恢复到STATE_NONE即可。
总结
终于完成了对蓝牙聊天程序的实现和分析,该示例程序比较全面,基本上包括了蓝牙编程的各个方面,希望通过这几篇文章的问题,能够帮助大家理解在Ophone平台上进行蓝牙编程,同时将蓝牙技术运用到其他应用程序中实现应用程序的网络化,联机性。或许你有更多的用处。