Android APP 藍芽範例說明 -- BluetoothChat

        第一次寫 Android 藍牙連線程式還不知該如何開始 (新手上路),於是先找一本 APP 開發的書吧!也許從書中能獲得一些訊息。之前筆者主要使用的程式語言是 C/C++,所以初次看到 APP 的每個範例都不是很面熟。經過幾周的研讀後,給後進初學者的建議是先準備下列的書和開發環境:
  1. Java 8.0 程式語言 ,如果對 Java 語法不熟悉,最好有一本工具書能快速查詢。
  2. Android APP 程式設計,學習 UI 如何設計的概念。
  3. 開發 IDE 環境 Android Studio 1.4 以上
        Android 官方的開發網站提供不少範例程式,由於先前開發藍牙相關的設備,因此我們從網站下載一個 BluetoothChat 程式來研究,藉此熟悉 Android SDK 來了解如何使用藍牙介面。首先了解一下這個範例有什麼功能,它的功能其實很簡單 (範例就是要簡單才夠力),即透過藍牙通訊在手機或平板上互傳訊息的程式。圖一是範例程式的專案打開後列出相關的檔案,包含設定檔、java程式檔、資源檔。然而主要的程式檔為MainActivityBluetoothChatFragmentBluetoothChatServiceDeviceActivity 這四個,另外還有一些程式檔案是 debug log 的用途,方便我們在 debug 模式下顯示於 IDE 的訊息。不過,初學者可以直接從上述的四個檔案來研究,log 以後有興趣再說。

圖一:範例 BluetoothChat 專案


        下圖二是範例 BluetoothChat 的流程圖,主要是由上述的四個 java 檔案組成的類別。APP 程式一開始先呼叫 MainActivity,這是初學者必須懂的常識,如果不清楚的話,趕緊找本書K一K。當系統呼叫 MainActivity 類別時,該類別會生成一個新的 BluetoothChatFragment,這是一個 Android Fragment 所衍生的類別,關於 fragment 是甚麼?它跟 UI 畫面有關,請參考官方網站的詳細解說。此外,生成 BluetoothChatFragment 的同時也會新生成 BluetoothChatService 類別,這個類別負責藍牙服務的程式模組,也就是從藍牙設備連線到取得通訊 socket 都是由這個類別來負責處理。

        在研究藍牙連線服務之前,要先搞清楚藍牙搜尋和設備配對吧!該範例程式的畫面右上方有個 option 選單,操作者按下選單的動作後,BluetoothChatFragment 會透過 Intent 方式呼叫 DeviceListActivity 帶出另一個畫面。這裡的 Intent 是設計 APP 常見的概念,初學者要花點時間研讀。DeviceListActivity 會列出手機內已經配對過的設備,還有新搜尋到的藍牙設備,操作者可以從這個畫面選擇想要建立藍牙連線的設備。當操作的過程結束後,DeviceListActivity 把結果 (那個連線的藍牙設備位址) 傳回 BluetoothChatFragment 類別,同時關閉該畫面,返回主畫面。

圖二:範例 BluetoothChat 流程圖 


        在取得配對藍牙設備的位置後,接下來,整個藍牙服務便是通訊的核心。如果過去有設計過網路通訊程式的經驗,就會發現開發 Android 藍牙和網路通訊滿類似的。注意藍牙通訊有三個重要的介面,如下:
  1. BluetoothAdapter:這個藍牙適配器在藍芽建立連線之前都要使用到它。
  2. Server / Client socket:好比網路通訊的 socket,當兩個藍牙裝置配對連線時,我們從系統取得藍牙通訊的 socket。
  3. Read / Write stream:這好比網路通訊的 read / write buffer (或 recv / send)。從連線的藍牙 socket 取得通訊 steam buffer。

<< 藍牙適配器 >>


        這是系統所提供的一個藍牙接口,讓我們透取這個接口操作藍牙。有哪些動作的實現需要透過這個接口呢?比如:檢查手機藍牙是否開啟、開始搜尋藍牙裝置、進入可搜尋狀態....等功能。於是,第一步我們要取得這個接口

                     BluetoothAdapter mBtAdapter = BluetoothAdapter.getDefaultAdapter( );

如果 mBtAdapter 返回是 null 的話,表示該手機沒有藍牙裝置,也不用後續的藍牙操作了。如果有藍牙裝置但是沒有開啟,透過下面的程式碼檢查:

                    // 檢查藍牙是否開啟
                    if( !mBtAdapter.isEnabled( ) ) {
                         Intent enableIntent = new Intent( BluetoothAdapter.ACTION_REQUEST_ENABLE );
                           startActivityForResult( enableIntent, REQUEST_ENABLE_BT ); }

當藍牙未開啟時,透過 Intent 方式要求系統啟用藍牙,並將結果返回。開啟藍牙裝置後,假設尚未與任何設備進行配對,本機藍牙有兩種動作:一種是主動搜尋,另一種是進入被搜尋狀態。主動搜尋的程式碼很簡單,透過適配器的標準函式,如下:

                    mBtAdapter.startDiscovery( );

不過,有個問題是系統幫我們執行搜尋任務後,搜尋的結果該如何通知我們呢?這問題很簡單,只要給系統一個類似 callback function 的處理函式,自然能解決這問題。因此,在 DeviceListActivity 類別建立時,必須向系統註冊一個 mReceiver (我們自己設計的 callback),如下:

                   // 搜尋到藍牙裝置時,呼叫我們的函式
                   IntentFilter filter = new IntentFilter( BluetoothDevice.ACTION_FOUND );
                   this.registerReceiver( mReceiver, filter );

                   // 搜尋的過程結束後,也呼叫我們的函式
                   filter = new IntentFilter( BluetoothAdapter.ACTION_DISCOVERY_FINISHED );
                   this.registerReceiver( mReceiver, filter );

上面是主動搜尋的設計方式,對應的另一種是藍牙進入被搜尋的狀態,先檢查藍牙是否已經處在被搜尋狀態,如果不是,透過 Intent 方式通知系統執行該動作,程式碼如下:

                   // 檢查藍牙狀態是否為可被搜尋
                   if( mBtAdapter.getScanMode( ) != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE ) {
                       Intent discoverableIntent = new Intent( BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE );
                       discoverableIntent.putExtra( BluetoothAdapter.EXTRA_DISCIVERABLE_DURATION, 300 );
                       startActivity( discoverableIntent ); }

        這個藍牙適配器協助我們完成藍牙的基本操作後,欲建立藍牙連線前要取得對方的藍牙位址,這和網路連線一樣,必須有對方的 IP 位址才能連線。藍牙配適器提供下面的函式

                    getBondedDevices( );  // 取得目前手機內已配對過的裝置
                    getRemoteDevice( address );  // 取得對方的藍牙位址


<< 藍牙連線 Socket >>


        如同網路連線一樣,藍牙的連線 socket 分成兩類:一個是 server 端,一個是 client 端。對於 TCP/IP 網路設計,做為 server 的一方處在 listening 狀態等待 client 連線進來。一旦有連線,server 端就呼叫 accept。相反地,做為 client 的一方主動發出建立連線的要求,等待 server 端接受。在這個藍牙連線範例中,雙方藍牙裝置屬於對等的角色,既可當 server 也可當 client,任一方都可以當 client 發起連線,只要另一方當 server 就好。因此,藍牙程式必須建立一個監聽執行緒,BluetoothChatService 生成一個 AcceptThread 類別做為 server 的監聽執行緒,如圖三所示。底下是 server 監聽的程式碼,先要取得系統監聽的 socket 接口,然後透過這個接口呼叫 accept,這是一個 block call (執行緒會停在這個函式內,等待對方連線)。一旦有任一連線進入後,便會取得該連線的 socket,這個 socket 就做為接下來的通訊之用。

        // 取得 server 監聽的 socket 接口
        BluetoothServerSocket  serverSocket = mBtAdapter.listenUsingRfcommWithServiceRecord( );
        BluetoothSocket  mmSocket = serverSocket.accept( );  // blocking call

反之,如果是 client 發起的連線 (已經有對方的藍牙位址),則進入 ConnectThread 執行緒,在執行緒裡等待對方接受。底下是 client 的程式碼,取得連線的 socket 後,必須透過這個接口呼叫 connect,這也是一個 block call (執行緒會停在這個函式內,確認對方接受連線)。成功後,這個 socket 就做為接下來的通訊之用。

        // 取得藍牙連線的 socket 接口
        BluetoothSocket  mmSocket = device.createRfcommSocketToServiceRecord( );
        mmSocket.connect( );  // blocking call



圖三:藍牙服務的流程圖 

<< 連線資料互傳 >>


        來到這一步表示連線已經成功,而且拿到通訊的 socket 了。有別於 TCP/IP 網路設計,我們必須從連線 socket 取得資料的 stream,再透過它來互傳資料,如下:

        InputStream  mmInStream = mmSocket.getInputStream( );
       OutputStream  mmOutStream = mmSocket.getOutputStream( );

        // 讀取資料
        mmInStream.read( buffer );

        // 寫入資料
        mmOutStream.write( buffer );

寫到這裡,主要說明從藍牙的搜尋到建立連線,並且取得連線 socket 和資料傳送的方法。至於切斷藍牙連線的方法,可參考官方網站的說明。

這個網誌中的熱門文章

Android APP BLE範例程式 -- BluetoothLeGatt

三軸重力加速計 Accelerometer