Bluetooth Chat

前阵子调通了google的蓝牙聊天例程,最近要研究源代码,于是在网上找了一篇文章,一段一段对着代码浏览下来

——尼玛,  这个安卓的范例不简单啊,光把文章看下来就不容易了,代码还是自己琢磨。

这是文章链接: http://www.aichengxu.com/view/28484

以下是全文引用,做了一些修改:


安卓自带示例BluetoothChat详解,有需要的朋友可以参考下。

安装好安卓SDK后,新建安卓项目会有一个Android Sample Project的选项,里面都是安卓自带的示例项目,其中BluetoothChat是利用蓝牙进行通信的实例。

程序的类结构

BluetoothChat项目src下共有三个类,分别为:

  • BluetoothChat;
  • BluetoothChatService;
  • DeviceListActivity;

运行时各类的动作

BluetoothChat是主窗体,通俗来讲就是程序刚刚运行时用户所见到的窗体。具体调用过程如下:

  • 软件运行后首先调用该类的onCreate()函数,进行主窗体的初始化,包括设置标题、设置窗体、初始化蓝牙适配器,并且判断本地蓝牙是否可用,若本机设备不支持蓝牙则退出程序,否则执行onStart()函数。
   1: public void onCreate(Bundle savedInstanceState)
   2: {
   3:     super.onCreate(savedInstanceState); 
   4:     if (D)
   5:         Log.e(TAG, "+++ ON CREATE +++");
   6:  
   7:     // 设置窗体
   8:     setContentView(R.layout.main);
   9:     // 初始化蓝牙适配器
  10:     mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
  11:     // 如果蓝牙适配器不存在,则退出程序
  12:     if (mBluetoothAdapter == null)
  13:     {
  14:         Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
  15:         finish();
  16:         return;
  17:     }
  18: }

 

  • onStart()函数首先判断本地蓝牙是否打开,若没有打开,则通过Intent发送REQUEST_ENABLE_BT消息给onActivityResult()函数,执行打开蓝牙的操作(Intent消息是安卓中不同窗体进行通信的桥梁),并且调用setupChat()函数;如果已经打开蓝牙,则直接执行setupChat()设置聊天的输入文本框、发送按钮以及输入键盘初始化、添加发送按钮的点击监听等工作。
   1: public void onStart()
   2: {
   3:     super.onStart();
   4:     if (D)
   5:     {
   6:         Log.e(TAG, "++ ON START ++");
   7:     }
   8:  
   9:     // If Bluetooth is not on, request that it be enabled.
  10:     // setupChat() will then be called during onActivityResult
  11:     // 判断蓝牙适配器是否打开
  12:     if (!mBluetoothAdapter.isEnabled())
  13:     {
  14:         // 如果蓝牙适配器没有打开,则创建一个Intent,将REQUEST_ENABLE_BT消息发送给
  15:         // onActiveResult函数
  16:         Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
  17:         startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
  18:  
  19:     }
  20:     else
  21:     // Otherwise, setup the chat session
  22:     {
  23:         // 如果蓝牙适配器未打开,则执行setupChat
  24:         if (mChatService == null)
  25:         {
  26:             setupChat();
  27:         }
  28:     }
  29: }

 

  • 若一切顺利,此时蓝牙处于打开状态,而想要与其他设备进行聊天通信必须进行设备配对。onOptionsItemSelected()函数用于实现设备配对选项,当用户点击手机的menu按钮时就会调用这一函数。该函数有两个分支,其一是scan,其二是discoverable。

    当用户选择搜索设备时进入scan分支,通过Intent发送 REQUEST_CONNECT_DEVICE消息给onActivityResult()函数,并且初始化DeviceListActivity类的对象,DeviceListActivity类即是src下的一个类,主要用于实现设备搜索窗体,显示已配对设备和搜索到的设备。

    onActivityResult()函数接下来会获得到已搜索到设备的MAC地址等信息,并且通过初始化BluetoothChatService类的对象mChatService来建立连接。

   1: public boolean onOptionsItemSelected(MenuItem item)
   2: {
   3:     Intent serverIntent = null;
   4:     switch (item.getItemId())
   5:     {
   6:         case R.id.secure_connect_scan:
   7:             // Launch the DeviceListActivity to see devices and do scan
   8:             serverIntent = new Intent(this, DeviceListActivity.class);
   9:             startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE_SECURE);
  10:             return true;
  11:         case R.id.insecure_connect_scan:
  12:             // Launch the DeviceListActivity to see devices and do scan
  13:             serverIntent = new Intent(this, DeviceListActivity.class);
  14:             startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE_INSECURE);
  15:             return true;
  16:         case R.id.discoverable:
  17:             // Ensure this device is discoverable by others
  18:             ensureDiscoverable();
  19:             return true;
  20:     }
  21:     return false;
  22: }

 

  • 建立连接过程首先通过mChatService调用BluetoothChatService的成员函数connect()开始,此时操作主要集中于BluetoothChatService类内。connect()函数会通过mConnectThread = new ConnectThread(device)来创建一个connect线程。
   1: public void onActivityResult(int requestCode, int resultCode, Intent data)
   2: {
   3:     if (D)
   4:         Log.d(TAG, "onActivityResult " + resultCode);
   5:     switch (requestCode)
   6:     {
   7:         case REQUEST_CONNECT_DEVICE_SECURE:
   8:             // When DeviceListActivity returns with a device to connect
   9:             // 当用户点击设备列表上的按钮时,触发下面的代码
  10:             if (resultCode == Activity.RESULT_OK)
  11:             {
  12:                 connectDevice(data, true);
  13:             }
  14:             break;
  15:         /** Other codes*/
  16:     }
  17: }
  18:  
  19: private void connectDevice(Intent data, boolean secure)
  20: {
  21:     // Get the device MAC address
  22:     String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
  23:     // Get the BluetoothDevice object
  24:     BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
  25:     // Attempt to connect to the device
  26:     mChatService.connect(device, secure);
  27: }
  28:  
  29: // BluetoothChatService类成员函数
  30: public synchronized void connect(BluetoothDevice device, boolean secure)
  31: {
  32:     if (D){Log.d(TAG, "connect to: " + device);}
  33:         
  34:     // Cancel any thread attempting to make a connection
  35:     if (mState == STATE_CONNECTING)
  36:     {
  37:         if (mConnectThread != null)
  38:         {
  39:             mConnectThread.cancel();
  40:             mConnectThread = null;
  41:         }
  42:     }
  43:  
  44:     // Cancel any thread currently running a connection
  45:     if (mConnectedThread != null)
  46:     {
  47:         mConnectedThread.cancel();
  48:         mConnectedThread = null;
  49:     }
  50:  
  51:     // Start the thread to connect with the given device
  52:     mConnectThread = new ConnectThread(device, secure);
  53:     mConnectThread.start();
  54:     setState(STATE_CONNECTING);
  55: }

 

  • 上一步骤会调用ConnectThread线程的构造函数,在其构造函数会创建一个客户端BluetoothSocket以便进行通信,并且在构造函数执行完返回后通过start()函数执行ConnectThread线程的run()函数。
   1: /** ConnectThread 构造函数*/
   2: public ConnectThread(BluetoothDevice device, boolean secure)
   3: {
   4:     mmDevice = device;
   5:     /** 蓝牙客户端socket*/
   6:     BluetoothSocket tmp = null;
   7:     mSocketType = secure ? "Secure" : "Insecure";
   8:  
   9:     // Get a BluetoothSocket for a connection with the given BluetoothDevice
  10:     try
  11:     {    
  12:         // 创建蓝牙客户端socket
  13:         if (secure)    {tmp = device.createRfcommSocketToServiceRecord(MY_UUID_SECURE);}
  14:         else    {tmp = device.createInsecureRfcommSocketToServiceRecord(MY_UUID_INSECURE);}
  15:     }
  16:     catch (IOException e)
  17:     {
  18:         Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
  19:     }
  20:     mmSocket = tmp;
  21: } // end ConnectThread
  • ConnectThread::run()函数会调用mmSocket.connect()发起连接请求,并且在之后调用BluetoothChatService::start()函数来开启AcceptThread线程。start()函数是 BluetoothChatService的成员函数,线程开启是通过mAcceptThread = new AcceptThread()实现的。
   1: // ConnectThread类run()函数
   2: public void run()
   3: {
   4:     Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType);
   5:     setName("ConnectThread" + mSocketType);
   6:  
   7:     // Always cancel discovery because it will slow down a connection
   8:     mAdapter.cancelDiscovery();
   9:  
  10:     // Make a connection to the BluetoothSocket
  11:     try
  12:     {
  13:         // This is a blocking call and will only return on a
  14:         // successful connection or an exception
  15:         // 连接目标手机,此函数将会阻塞,直至连接成功或异常抛出
  16:         mmSocket.connect();
  17:     }
  18:     catch (IOException e)
  19:     {
  20:         // Close the socket
  21:         try
  22:         {
  23:             mmSocket.close();
  24:         }
  25:         catch (IOException e2)
  26:         {
  27:             Log.e(TAG, "unable to close() " + mSocketType +
  28:                     " socket during connection failure", e2);
  29:         }
  30:         connectionFailed();
  31:         return;
  32:     } // end Run

 

   1: // BluetoothChatService类start()函数
   2: public synchronized void start()
   3: {
   4:     if (D)
   5:         Log.d(TAG, "start");
   6:  
   7:     // Cancel any thread attempting to make a connection
   8:     if (mConnectThread != null)
   9:     {
  10:         mConnectThread.cancel();
  11:         mConnectThread = null;
  12:     }
  13:  
  14:     // Cancel any thread currently running a connection
  15:     if (mConnectedThread != null)
  16:     {
  17:         mConnectedThread.cancel();
  18:         mConnectedThread = null;
  19:     }
  20:  
  21:     setState(STATE_LISTEN);
  22:  
  23:     // Start the thread to listen on a BluetoothServerSocket
  24:     if (mSecureAcceptThread == null)
  25:     {
  26:         mSecureAcceptThread = new AcceptThread(true);
  27:         mSecureAcceptThread.start();
  28:     }
  29:     if (mInsecureAcceptThread == null)
  30:     {
  31:         mInsecureAcceptThread = new AcceptThread(false);
  32:         mInsecureAcceptThread.start();
  33:     }
  34: } // end start

 

  • 上一步骤会调用AcceptThread的构造函数,创建一个服务器socket,并且在之后调用AcceptThread线程的run()函数。
   1: // AcceptThread构造函数
   2: public AcceptThread(boolean secure)
   3: {
   4:     BluetoothServerSocket tmp = null;
   5:     mSocketType = secure ? "Secure" : "Insecure";
   6:  
   7:     // Create a new listening server socket
   8:     try
   9:     {
  10:         // 创建服务器socket
  11:         if (secure) {tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE, MY_UUID_SECURE);}
  12:         else {tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(NAME_INSECURE, MY_UUID_INSECURE);}
  13:     }
  14:     catch (IOException e)
  15:     {
  16:         Log.e(TAG, "Socket Type: " + mSocketType + "listen() failed", e);
  17:     }
  18:     mmServerSocket = tmp;
  19: } // end AcceptThread

 

  • AcceptThread::run()函数通过一个while循环不断接受连接请求,且在有接受了一个连接后通过BluetoothChatService::connected()函数建立connected线程。connected()函数会启动一个ConnectedThread线程,该线程用于设置socket的输入输出流。
   1: // AcceptThread类run()函数
   2: public void run()
   3: {
   4:     if (D){ Log.d(TAG, "Socket Type: " + mSocketType + "BEGIN mAcceptThread" + this);}
   5:  
   6:     setName("AcceptThread" + mSocketType);
   7:     BluetoothSocket socket = null;
   8:  
   9:     // Listen to the server socket if we're not connected
  10:     while (mState != STATE_CONNECTED)
  11:     {
  12:         try
  13:         {
  14:             // This is a blocking call and will only return on a
  15:             // successful connection or an exception
  16:             socket = mmServerSocket.accept();
  17:         }
  18:         catch (IOException e)
  19:         {
  20:             Log.e(TAG, "Socket Type: " + mSocketType + "accept() failed", e);
  21:             break;
  22:         }
  23:  
  24:         // If a connection was accepted
  25:         if (socket != null)
  26:         {
  27:             synchronized (BluetoothChatService.this)
  28:             {
  29:                 switch (mState)
  30:                 {
  31:                     case STATE_LISTEN:
  32:                     case STATE_CONNECTING:
  33:                         // Situation normal. Start the connected thread.
  34:                         connected(socket, socket.getRemoteDevice(), mSocketType);
  35:                         break;
  36:                     case STATE_NONE:
  37:                     case STATE_CONNECTED:
  38:                         // Either not ready or already connected. Terminate new socket.
  39:                         try
  40:                         {
  41:                             socket.close();
  42:                         }
  43:                         catch (IOException e)
  44:                         {
  45:                             Log.e(TAG, "Could not close unwanted socket", e);
  46:                         }
  47:                         break;
  48:                 }
  49:             }
  50:         }
  51:     }
  52:     
  53:     if (D){Log.i(TAG, "END mAcceptThread, socket Type: " + mSocketType);}
  54:  
  55: } // end run

 

  • 上一步骤会调用ConnectedThread的构造函数,创建socket的输入输出流,并且接下来执行ConnectedThread::run()函数,run()函数通过一个while死循环不断从输入流中读取数据,用于接收配对设备发来的信息,并且通过Handler进行线程与主窗体之间的通信,将接受到的信息显示在主窗体上。
   1: public void run()
   2: {
   3:     Log.i(TAG, "BEGIN mConnectedThread");
   4:     byte[] buffer = new byte[1024];
   5:     int bytes;
   6:  
   7:     // Keep listening to the InputStream while connected
   8:     while (true)
   9:     {
  10:         try
  11:         {
  12:             // Read from the InputStream
  13:             bytes = mmInStream.read(buffer);
  14:  
  15:             // Send the obtained bytes to the UI Activity
  16:             mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer)
  17:                     .sendToTarget();
  18:         }
  19:         catch (IOException e)
  20:         {
  21:             Log.e(TAG, "disconnected", e);
  22:             connectionLost();
  23:             // Start the service over to restart listening mode
  24:             BluetoothChatService.this.start();
  25:             break;
  26:         }
  27:     }
  28: } // end run

消息发送过程

主要的数据通信过程已经分析完,另外还有点击发送按钮发送消息时的具体调用过程。

  • 在BluetoothChat类中的setupChat()函数中添加了点击发送按钮时的监听,当点击后会调用sendMessage()函数。sendMessage()函数首先检查连接是否建立,如果建立了连接,则通过BluetoothChatService的对象mChatService调用write()函数将消息写到输出流,相应的,由于对方设备在不断接收消息,该输出流的消息会被对方捕获并且显示。
   1: private void sendMessage(String message)
   2: {
   3:     // Check that we're actually connected before trying anything
   4:     if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED)
   5:     {
   6:         Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT).show();
   7:         return;
   8:     }
   9:  
  10:     // Check that there's actually something to send
  11:     if (message.length() > 0)
  12:     {
  13:         // Get the message bytes and tell the BluetoothChatService to write
  14:         byte[] send = message.getBytes();
  15:         mChatService.write(send);
  16:  
  17:         // Reset out string buffer to zero and clear the edit text field
  18:         mOutStringBuffer.setLength(0);
  19:         mOutEditText.setText(mOutStringBuffer);
  20:     }
  21: } // end of sendMessage(String message)

MARK:一边修改这篇文章,一边看代码,搞了一晚上没搞完~~~2015.12.19

MARK:历史的车轮滚滚向前,好不容易看完了文章,已经到了公元2015年12月20日~~