close
一、前言

Bluetooth藍芽裝置在手機界已經存在很久了,
也早就成為連低階Android手機都有的基本配備。
官方也提供了一個利用藍芽連線互相對話的範例程式
讓我們能快速地了解藍芽在Android中的使用方式。 
  
二、文章開始 
首先,
我們先建立一個觀念︰ 
藍芽一定分成2個端點,
分別為被動的Server端主動的Client端

底下是BluetoothChat的Sample Code程式流程︰

=========現在在BluetoothChat.java底下==============

1.在onCreate()時,呼叫

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();



一起來看看官方文件裡怎麼說BluetoothAdapter

BluetoothAdapter represents the local Bluetooth adapter (Bluetooth radio).
The BluetoothAdapter is the entry-point for all Bluetooth interaction.
Using this, you can discover other Bluetooth devices, query a list of bonded (paired) devices, instantiate a BluetoothDevice using a known MAC address, and create a BluetoothServerSocket to listen for communications from other devices.

BluetoothAdapter是區域藍芽接口(藍芽廣播)。BluetoothAdapter也是所有藍芽交易互動的啟始點。用這個接口,我們可以偵 測區域內有哪些其它的藍芽裝置、查詢已配對過的藍芽列表、用已知的MAC地址建立一個BluetoothDevice實體、建立一個 BluetoothServerSocket來監聽是否有其它藍芽裝置傳來的通訊…等。

2-1.得到一個BluetoothAdapter實體之後,
在onStart()裡, 
如果沒有啟動藍芽,則要求使用者開啟藍芽。
指令是︰

 
if (!mBluetoothAdapter.isEnabled()) {
    Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
// Otherwise, setup the chat session
}else {
    if (mChatService == null) setupChat();
}


2-2.透過setupChat()建立起基本的對話視窗和BluetoothChatService背景服務,並把主Thread的Handler傳給Service以供日後傳回message。

mChatService = new BluetoothChatService(this, mHandler);



3.在onResume()裡,也做一樣的事,如果檢查沒有開啟藍芽BluetoothChatService背景服務,則再次開始該服務。 

 
if (mChatService.getState() == BluetoothChatService.STATE_NONE) {
// Start the Bluetooth chat services
mChatService.start();
}


=========現在進入BluetoothChatService.java裡==============

程式碼才剛開出來,
馬上就看到這個Service的程式架構中,塞了3個執行緒, 
分別為︰
(1)AcceptThread
(2)ConnectThread
(3)ConnectedThread

馬上來看看它們在Service裡,分別擔任什麼樣的任務︰


// Cancel any thread attempting to make a connection
if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
 
// Cancel any thread currently running a connection
if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
 
// Start the thread to listen on a BluetoothServerSocket
if (mAcceptThread == null) {
mAcceptThread = new AcceptThread();
mAcceptThread.start();
}


1.在onStart()中,
檢查如果ConnectThread和ConnectedThread存在,則將他們關掉。
2.啟動一個AcceptThread(現在的流程是在藍芽開啟中的狀態,開啟了一個AcceptThread待命)。

這個AcceptThread存在的目的,是因為程式先假設每臺裝置都有可能想要跟它做藍芽連線。


來看一下這個程式一啟動後就執行的AcceptThread裡面做了些什麼︰

 
/**
   * This thread runs while listening for incoming connections. It behaves
   * like a server-side client. It runs until a connection is accepted
   * (or until cancelled).
   */
  private class AcceptThread extends Thread {
      // The local server socket
      private final BluetoothServerSocket mmServerSocket;
 
      public AcceptThread() {
          BluetoothServerSocket tmp = null;
 
          // Create a new listening server 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;
 
          // 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, "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());
                          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;
                      }
                  }
              }
          }
          if (D) Log.i(TAG, "END mAcceptThread");
      }
 
      public void cancel() {
          if (D) Log.d(TAG, "cancel " + this);
          try {
              mmServerSocket.close();
          } catch (IOException e) {
              Log.e(TAG, "close() of server failed", e);
          }
      }
  }


我們看到了在AcceptThread裡面,埋入了一個BluetoothServerSocket。
看一下Bluetooth Android官方對它的解釋︰

BluetoothServerSocket represents an open server socket that listens for incoming requests (similar to a TCP ServerSocket). In order to connect two Android devices, one device must open a server socket with this class. When a remote Bluetooth device makes a connection request to the this device, the BluetoothServerSocket will return a connectedBluetoothSocket when the connection is accepted.

BluetoothServerSocket是一個開放式的server socket,用來監聽任何傳進來的請求(原理類似TCP ServerSocket)。為了讓2隻Android devices能夠連線,其中一隻裝置必須開啟server socket。當遠端的藍芽裝置向手上這隻裝備請求連線後,這隻裝置上的BluetoothServerSocket會回傳一個accepted的 BluetoothSocket給呼叫那一方。

因此我們知道,上面程式碼中

BluetoothSocket  socket = mmServerSocket.accept();



就是應證了BluetoothServerSocket會吐BluetoothSocket出來這件事。

回到一開始呼叫AcceptThread.start()的那個時間點,
也就是說,
程式在一啟動時,
都先要求使用者開啟藍芽,
然後隨時準備接收別臺藍芽裝置會傳送連線請求的事件。


我們取到了BluetoothSocket後,
看看這個BluetoothSocket能做些什麼。

首先,
在官方技術文件提到︰

BluetoothSocket represents the interface for a Bluetooth socket (similar to a TCP Socket). This is the connection point that allows an application to exchange data with another Bluetooth device via InputStream and OutputStream.

BluetoothSocket是一個Bluetooth socket的接口(原理類似TCP Socket)。這個連結點允許APP透過InputStream和OutpusStream互相交換資料。

因此我們得知,
BluetoothSocket可以讓我們做到資料交換的功能。

因為在Service onStart()呼叫AcceptThread.start()後,
馬上將藍芽狀態設定成setState(STATE_LISTEN);
因此,在switch迴圈中,
程式執行了connected()函式。

這段程式碼如下︰

 
case STATE_LISTEN:
case STATE_CONNECTING:
    // Situation normal. Start the connected thread.
    connected(socket, socket.getRemoteDevice());
    break;


馬上來看看connected()函式做了哪些事

 
/**
 * Start the ConnectedThread to begin managing a Bluetooth connection
 * @param socket  The BluetoothSocket on which the connection was made
 * @param device  The BluetoothDevice that has been connected
 */
public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
    if (D) Log.d(TAG, "connected");
 
    // 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 (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}
 
    // Start the thread to manage the connection and perform transmissions
    mConnectedThread = new ConnectedThread(socket);
    mConnectedThread.start();
 
    // Send the name of the connected device back to the 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);
}


為了避免重覆連線,
先檢查有沒有已存在的ConectThread、ConnectedThrad和AcceptThread。
如果有,一律先關掉。
然後,啟動ConnectedThread,
並將MESSAGE_DEVICE_NAME用handler(mHandler,還記得我們前面有提到在BluetoothChat.java傳了一個主Thread的Handler給Service嗎?)傳訊的方式,
將Client端的裝置資料傳回BluetoothChat.java,
讓Server端知道是誰在跟它做連結。

前面提到,
一旦取得了BluetoothSocket之後,
就可以開始執行互相傳遞資料的工作了 。
這個被啟動的ConnectedThread就是在做資料互傳的監聽工作
我們看看ConnectedThread做了些什麼

 
/**
  * This thread runs during a connection with a remote device.
  * It handles all incoming and outgoing transmissions.
  */
 private class ConnectedThread extends Thread {
     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;
 
         // Get the BluetoothSocket input and output streams
         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;
 
         // Keep listening to the InputStream while connected
         while (true) {
             try {
                 // Read from the InputStream
                 bytes = mmInStream.read(buffer);
 
                 // Send the obtained bytes to the UI Activity
                 mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer)
                         .sendToTarget();
             } catch (IOException e) {
                 Log.e(TAG, "disconnected", e);
                 connectionLost();
                 break;
             }
         }
     }
 
     /**
      * Write to the connected OutStream.
      * @param buffer  The bytes to write
      */
     public void write(byte[] buffer) {
         try {
             mmOutStream.write(buffer);
 
             // Share the sent message back to the UI Activity
             mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer)
                     .sendToTarget();
         } catch (IOException e) {
             Log.e(TAG, "Exception during write", e);
         }
     }
 
     public void cancel() {
         try {
             mmSocket.close();
         } catch (IOException e) {
             Log.e(TAG, "close() of connect socket failed", e);
         }
     }
 }


是的!
有看到嗎?
ConnectedThread正在用BluetoothSocket取得InputStream和OutputStream,
並透過旗下的write()和read()在做2隻藍芽裝置的溝通!

現在剩下ConnectThread還沒有去理解了,
查了一下ConnectThread被start的時間是發生在一開始對話頁面的menu鍵中!

原來ConnectThread的目的是要主動連接其它已開啟藍芽的裝置。 
當使用者點擊Connect a device時,
會啟動ConnectThread,
開始尋找附近的藍芽裝置,
並對對方發出連線訊號,
對方監聽到你的配對要求後,
對方手機裡原本程式就開啟中的AcceptThread便答應你的請求,
然後開啟ConnectedThread, 
並利用連結成功後得到的BluetoothSocket和你做藍芽傳輸溝通。

主動連線端是Client端,被動接收端是Server端,
就好像精子與受精卵… 

三、總結

在這裡我把整個程式流程重覆敍述一次︰

在連線的一開始,兩隻手機的程式一開始都先建立一個AcceptThread

(因為誰都不知道誰最後會成為被動接收的Server端,誰又是主動的Client端),
然後都跟RFCOMM頻道索取這隻app專屬的BluetoothServerSocket實體。

Server方做了些什麼︰ 
用BluetoothServerSocket這個實體去等待Client端用ConnectThread發出的請求連線事件
連線若成功會得到這次藍芽溝通專用的BluetoothSocket。

Client方做了些什麼︰ 
Client端執行ConnectThread
1.Client端在與Server方連線(Connect a deivce)之前,
會先取得到Server端的身份證MAC address,
並用該address得到Server端的BluetoothDevice實體。
2.Client端藉由自己的MY_UUID和Server端的BluetoothDevice實體,
從RFCOMM頻道拿到這次藍芽溝通專用的BluetoothSocket。

兩方在這個時候都拿到這次藍芽溝通專用的BluetootheSocket, 
也都在此時知道了對方的BluetoothDevice實體(知道對方的身份)。
這時候雙方都同時開啟ConnectedThread, 
彼此利用BluetoothSocket互相做資料傳輸。

註︰資料傳輸利用 InputStream和OutputStream。 

轉載:小鰻的Android學習筆記






 

















其它文章

arrow
arrow

    PG Levin Li 發表在 痞客邦 留言(0) 人氣()