十五、BluetoothChat之BluetoothChatService.java

BluetoothChatService.java

  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)

同样我们先熟悉一下该类的成员变量的作用,定义如下:

  1. // Debugging  
  2.   private staticfinal String TAG ="BluetoothChatService";  
  3.   private static finalboolean D =true;  
  4.  
  5.   //当创建socket服务时的SDP名称  
  6.   private staticfinal String NAME ="BluetoothChat";  
  7.  
  8.   // 应用程序的唯一UUID  
  9.   private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");  
  10.  
  11.   // 本地蓝牙适配器  
  12.   private final BluetoothAdapter mAdapter;  
  13.   //Handler  
  14.   private final Handler mHandler;  
  15.   //请求链接的监听线程  
  16.   private AcceptThread mAcceptThread;  
  17.   //链接一个设备的线程  
  18.   private ConnectThread mConnectThread;  
  19.   //已经链接之后的管理线程  
  20.   private ConnectedThread mConnectedThread;  
  21.   //当前的状态  
  22.   private int mState;  
  23.  
  24.   // 各种状态  
  25.   public staticfinalint STATE_NONE =0;    
  26.   public staticfinalint STATE_LISTEN =1;   
  27.   public staticfinalint STATE_CONNECTING =2;   
  28.   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)_构造函数

首先是初始化操作,即构造函数,代码如下:

  1. public BluetoothChatService(Context context, Handler handler) {  
  2.    //得到本地蓝牙适配器  
  3.   mAdapter = BluetoothAdapter.getDefaultAdapter();  
  4.   //设置状态  
  5.   mState = STATE_NONE;  
  6.   //设置Handler  
  7.   mHandler = handler;  
  8.   } 
public BluetoothChatService(Context context, Handler handler) {    //得到本地蓝牙适配器   mAdapter = BluetoothAdapter.getDefaultAdapter();   //设置状态   mState = STATE_NONE;   //设置Handler   mHandler = handler;   }

  取得本地蓝牙适配器、设置状态为STATE_NONE,设置传递进来的mHandler。接下来需要控制当状态改变之后,我们需要通知UI界面也同时更改状态,下面是得到状态和设置状态的实现部分,如下:

synchronizedvoid setState(int state)

  1. private synchronizedvoid setState(int state) {  
  2.   if (D) Log.d(TAG, "setState() " + mState +" -> " + state);  
  3.   mState = state;  
  4.  
  5.   // 状态更新之后UI Activity也需要更新  
  6.   mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();  
  7.   }  
  8.  
  9.   public synchronizedint getState() {  
  10.   return mState;  
  11.   } 
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线程处理消息

  1. case MESSAGE_STATE_CHANGE:  
  2.   if(D) Log.i(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1);  
  3.   switch (msg.arg1) {  
  4.   case BluetoothChatService.STATE_CONNECTED:  
  5.    //设置状态为已经链接  
  6.   mTitle.setText(R.string.title_connected_to);  
  7.   //添加设备名称  
  8.   mTitle.append(mConnectedDeviceName);  
  9.   //清理聊天记录  
  10.   mConversationArrayAdapter.clear();  
  11.   break;  
  12.   case BluetoothChatService.STATE_CONNECTING:  
  13.    //设置正在链接  
  14.   mTitle.setText(R.string.title_connecting);  
  15.   break;  
  16.   case BluetoothChatService.STATE_LISTEN:  
  17.   case BluetoothChatService.STATE_NONE:  
  18.    //处于监听状态或者没有准备状态,则显示没有链接  
  19.   mTitle.setText(R.string.title_not_connected);  
  20.   break;  
  21.   }  
  22.   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操作,其具体实现如下:

  1. public synchronizedvoid start() {  
  2.   if (D) Log.d(TAG, "start");  
  3.  
  4.   // 取消任何线程视图建立一个连接  
  5.   if (mConnectThread !=null) {mConnectThread.cancel(); mConnectThread =null;}  
  6.  
  7.   // 取消任何正在运行的链接  
  8.   if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread =null;}  
  9.  
  10.   // 启动AcceptThread线程来监听BluetoothServerSocket 
  11.   if (mAcceptThread ==null) {  
  12.   mAcceptThread = new AcceptThread();  
  13.   mAcceptThread.start();  
  14.   }  
  15.   //设置状态为监听,,等待链接  
  16.   setState(STATE_LISTEN);  
  17.   } 
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进程

  1. private class AcceptThreadextends Thread {  
  2.   // 本地socket服务  
  3.   private final BluetoothServerSocket mmServerSocket;  
  4.  
  5.   public AcceptThread() {  
  6.   BluetoothServerSocket tmp = null;  
  7.  
  8.   // 创建一个新的socket服务监听  
  9.   try {  
  10.   tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);  
  11.   } catch (IOException e) {  
  12.   Log.e(TAG, "listen() failed", e);  
  13.   }  
  14.   mmServerSocket = tmp;  
  15.   }  
  16.  
  17.   public void run() {  
  18.   if (D) Log.d(TAG, "BEGIN mAcceptThread" +this);  
  19.   setName("AcceptThread");  
  20.   BluetoothSocket socket = null;  
  21.  
  22.   // 如果当前没有链接则一直监听socket服务  
  23.   while (mState != STATE_CONNECTED) {  
  24.   try {  
  25.    //如果有请求链接,则接受  
  26.    //这是一个阻塞调用,将之返回链接成功和一个异常  
  27.   socket = mmServerSocket.accept();  
  28.   } catch (IOException e) {  
  29.   Log.e(TAG, "accept() failed", e);  
  30.   break;  
  31.   }  
  32.  
  33.   // 如果接受了一个链接  
  34.   if (socket != null) {  
  35.   synchronized (BluetoothChatService.this) {  
  36.   switch (mState) {  
  37.   case STATE_LISTEN:  
  38.   case STATE_CONNECTING:  
  39.   // 如果状态为监听或者正在链接中,,则调用connected来链接 
  40.   connected(socket, socket.getRemoteDevice());  
  41.   break;  
  42.   case STATE_NONE:  
  43.   case STATE_CONNECTED:  
  44.   // 如果为没有准备或者已经链接,这终止该socket  
  45.   try {  
  46.   socket.close();  
  47.   } catch (IOException e) {  
  48.   Log.e(TAG, "Could not close unwanted socket", e);  
  49.   }  
  50.   break;  
  51.   }  
  52.   }  
  53.   }  
  54.   }  
  55.   if (D) Log.i(TAG,"END mAcceptThread");  
  56.   }  
  57.   //关闭BluetoothServerSocket  
  58.   public void cancel() {  
  59.   if (D) Log.d(TAG,"cancel " +this);  
  60.   try {  
  61.   mmServerSocket.close();  
  62.   } catch (IOException e) {  
  63.   Log.e(TAG, "close() of server failed", e);  
  64.   }  
  65.   }  
  66.   } 
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) _连接

 

  1. public synchronizedvoid connect(BluetoothDevice device) {  
  2.   if (D) Log.d(TAG, "connect to: " + device);  
  3.  
  4.   // 取消任何链接线程,视图建立一个链接  
  5.   if (mState == STATE_CONNECTING) {  
  6.   if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread =null;}  
  7.   }  
  8.  
  9.   // 取消任何正在运行的线程  
  10.   if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread =null;}  
  11.  
  12.   // 启动一个链接线程链接指定的设备  
  13.   mConnectThread = new ConnectThread(device);  
  14.   mConnectThread.start();  
  15.   setState(STATE_CONNECTING);  
  16.   } 
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

 

  1. private class ConnectThreadextends Thread {  
  2.    //蓝牙Socket  
  3.   private final BluetoothSocket mmSocket;  
  4.   //蓝牙设备  
  5.   private final BluetoothDevice mmDevice;  
  6.  
  7.   public ConnectThread(BluetoothDevice device) {  
  8.   mmDevice = device;  
  9.   BluetoothSocket tmp = null;  
  10.  
  11.   //得到一个给定的蓝牙设备的BluetoothSocket 
  12.   try {  
  13.   tmp = device.createRfcommSocketToServiceRecord(MY_UUID);  
  14.   } catch (IOException e) {  
  15.   Log.e(TAG, "create() failed", e);  
  16.   }  
  17.   mmSocket = tmp;  
  18.   }  
  19.  
  20.   public void run() {  
  21.   Log.i(TAG, "BEGIN mConnectThread");  
  22.   setName("ConnectThread");  
  23.  
  24.   // 取消可见状态,将会进行链接  
  25.   mAdapter.cancelDiscovery();  
  26.  
  27.   // 创建一个BluetoothSocket链接  
  28.   try {  
  29.   //同样是一个阻塞调用,返回成功和异常  
  30.   mmSocket.connect();  
  31.   } catch (IOException e) {  
  32.    //链接失败  
  33.   connectionFailed();  
  34.   // 如果异常则关闭socket  
  35.   try {  
  36.   mmSocket.close();  
  37.   } catch (IOException e2) {  
  38.   Log.e(TAG, "unable to close() socket during connection failure", e2);  
  39.   }  
  40.   // 重新启动监听服务状态  
  41.   BluetoothChatService.this.start();  
  42.   return;  
  43.   }  
  44.  
  45.   // 完成则重置ConnectThread  
  46.   synchronized (BluetoothChatService.this) {  
  47.   mConnectThread = null;  
  48.   }  
  49.  
  50.   // 开启ConnectedThread(正在运行中...)线程  
  51.   connected(mmSocket, mmDevice);  
  52.   }  
  53.   //取消链接线程ConnectThread  
  54.   public void cancel() {  
  55.   try {  
  56.   mmSocket.close();  
  57.   } catch (IOException e) {  
  58.   Log.e(TAG, "close() of connect socket failed", e);  
  59.   }  
  60.   }  
  61.   } 
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() _链接失败

 
  1. private void connectionFailed() {  
  2.   setState(STATE_LISTEN);  
  3.  
  4.   // 发送链接失败的消息到UI界面  
  5.   Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);  
  6.   Bundle bundle = new Bundle();  
  7.   bundle.putString(BluetoothChat.TOAST, "Unable to connect device");  
  8.   msg.setData(bundle);  
  9.   mHandler.sendMessage(msg);  
  10.   } 
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) _成功则进入连接管理进程

  1. public synchronizedvoid connected(BluetoothSocket socket, BluetoothDevice device) {  
  2.   if (D) Log.d(TAG, "connected");  
  3.  
  4.   // 取消ConnectThread链接线程  
  5.   if (mConnectThread !=null) {mConnectThread.cancel(); mConnectThread =null;}  
  6.  
  7.   // 取消所有正在链接的线程  
  8.   if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread =null;}  
  9.  
  10.   // 取消所有的监听线程,因为我们已经链接了一个设备  
  11.   if (mAcceptThread !=null) {mAcceptThread.cancel(); mAcceptThread =null;}  
  12.  
  13.   // 启动ConnectedThread线程来管理链接和执行翻译 
  14.   mConnectedThread = new ConnectedThread(socket);  
  15.   mConnectedThread.start();  
  16.  
  17.   // 发送链接的设备名称到UI Activity界面  
  18.   Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME);  
  19.   Bundle bundle = new Bundle();  
  20.   bundle.putString(BluetoothChat.DEVICE_NAME, device.getName());  
  21.   msg.setData(bundle);  
  22.   mHandler.sendMessage(msg);  
  23.   //状态变为已经链接,即正在运行中  
  24.   setState(STATE_CONNECTED);  
  25.   } 
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_性息进出流的管理

  1. private class ConnectedThread extends Thread {  
  2.    //BluetoothSocket  
  3.   private final BluetoothSocket mmSocket;  
  4.   //输入输出流  
  5.   private final InputStream mmInStream;  
  6.   private final OutputStream mmOutStream;  
  7.  
  8.   public ConnectedThread(BluetoothSocket socket) {  
  9.   Log.d(TAG, "create ConnectedThread");  
  10.   mmSocket = socket;  
  11.   InputStream tmpIn = null;  
  12.   OutputStream tmpOut = null;  
  13.  
  14.   // 得到BluetoothSocket的输入输出流  
  15.   try {  
  16.   tmpIn = socket.getInputStream();  
  17.   tmpOut = socket.getOutputStream();  
  18.   } catch (IOException e) {  
  19.   Log.e(TAG, "temp sockets not created", e);  
  20.   }  
  21.  
  22.   mmInStream = tmpIn;  
  23.   mmOutStream = tmpOut;  
  24.   }  
  25.  
  26.   public void run() {  
  27.   Log.i(TAG, "BEGIN mConnectedThread");  
  28.   byte[] buffer = newbyte[1024];  
  29.   int bytes;  
  30.  
  31.   // 监听输入流  
  32.   while (true) {  
  33.   try {  
  34.   // 从输入流中读取数据  
  35.   bytes = mmInStream.read(buffer);  
  36.  
  37.   // 发送一个消息到UI线程进行更新  
  38.   mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer)  
  39.   .sendToTarget();  
  40.   } catch (IOException e) {  
  41.    //出现异常,则链接丢失  
  42.   Log.e(TAG, "disconnected", e);  
  43.   connectionLost();  
  44.   break;  
  45.   }  
  46.   }  
  47.   }  
  48.  
  49.   /**
  50.   * 写入药发送的消息
  51.   * @param buffer  The bytes to write
  52.   */ 
  53.   public void write(byte[] buffer) {  
  54.   try {  
  55.   mmOutStream.write(buffer);  
  56.  
  57.   // 将写的消息同时传递给UI界面  
  58.   mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer)  
  59.   .sendToTarget();  
  60.   } catch (IOException e) {  
  61.   Log.e(TAG, "Exception during write", e);  
  62.   }  
  63.   }  
  64.   //取消ConnectedThread链接管理线程  
  65.   public void cancel() {  
  66.   try {  
  67.   mmSocket.close();  
  68.   } catch (IOException e) {  
  69.   Log.e(TAG, "close() of connect socket failed", e);  
  70.   }  
  71.   }  
  72.   } 
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函数_处理连接丢失

  1. private void connectionLost() {  
  2.   setState(STATE_LISTEN);  
  3.  
  4.   // 发送失败消息到UI界面  
  5.   Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);  
  6.   Bundle bundle = new Bundle();  
  7.   bundle.putString(BluetoothChat.TOAST, "Device connection was lost");  
  8.   msg.setData(bundle);  
  9.   mHandler.sendMessage(msg);  
  10.   } 
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[] _写操作

  1. //写入自己要发送出来的消息  
  2.   public void write(byte[] out) {  
  3.   // Create temporary object  
  4.   ConnectedThread r;  
  5.   // Synchronize a copy of the ConnectedThread 
  6.   synchronized (this) {  
  7.    //判断是否处于已经链接状态  
  8.   if (mState != STATE_CONNECTED)return;  
  9.   r = mConnectedThread;  
  10.   }  
  11.   // 执行写  
  12.   r.write(out);  
  13.   } 
//写入自己要发送出来的消息   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_发送数据

  1. private void sendMessage(String message) {  
  2.   // 检查是否处于连接状态  
  3.   if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {  
  4.   Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT).show();  
  5.   return;  
  6.   }  
  7.  
  8.   // 如果输入的消息不为空才发送,否则不发送  
  9.   if (message.length() >0) {  
  10.   // Get the message bytes and tell the BluetoothChatService to write 
  11.   byte[] send = message.getBytes();  
  12.   mChatService.write(send);  
  13.  
  14.   // Reset out string buffer to zero and clear the edit text field 
  15.   mOutStringBuffer.setLength(0);  
  16.   mOutEditText.setText(mOutStringBuffer);  
  17.   }  
  18.   } 
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_读写消息的处理

  1. case MESSAGE_WRITE:  
  2.   byte[] writeBuf = (byte[]) msg.obj;  
  3.   // 将自己写入的消息也显示到会话列表中  
  4.   String writeMessage = new String(writeBuf);  
  5.   mConversationArrayAdapter.add("Me:  " + writeMessage);  
  6.   break;  
  7.   case MESSAGE_READ:  
  8.   byte[] readBuf = (byte[]) msg.obj;  
  9.   // 取得内容并添加到聊天对话列表中  
  10.   String readMessage = new String(readBuf,0, msg.arg1);  
  11.   mConversationArrayAdapter.add(mConnectedDeviceName+":  " + readMessage);  
  12.   break;  
  13.   case MESSAGE_DEVICE_NAME:  
  14.   // 保存链接的设备名称,并显示一个toast提示  
  15.   mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);  
  16.   Toast.makeText(getApplicationContext(), "Connected to " 
  17.   + mConnectedDeviceName, Toast.LENGTH_SHORT).show();  
  18.   break;  
  19.   case MESSAGE_TOAST:  
  20.    //处理链接(发送)失败的消息  
  21.   Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST),  
  22.   Toast.LENGTH_SHORT).show();  
  23.   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即可,具体实现如下:

 

停止所有的线程

  1. //停止所有的线程  
  2.   public synchronizedvoid stop() {  
  3.   if (D) Log.d(TAG,"stop");  
  4.   if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread =null;}  
  5.   if (mConnectedThread !=null) {mConnectedThread.cancel(); mConnectedThread =null;}  
  6.   if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread =null;}  
  7.   //状态设置为准备状态  
  8.   setState(STATE_NONE);  
  9.   } 
//停止所有的线程   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平台上进行蓝牙编程,同时将蓝牙技术运用到其他应用程序中实现应用程序的网络化,联机性。或许你有更多的用处。

 

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值