Android平台支持藍牙網絡協議棧,實現藍牙設備之間數據的無線傳輸。
本文檔描述了怎樣利用android平台提供的藍牙API去實現藍牙設備之間的通信,藍牙設備之間的通信主要包括了四個步驟:設置藍牙設備、尋找局域網內可能或者匹配的設備、連接設備和設備之間的數據傳輸。以下是建立藍牙連接的所需要的一些基本類:
BluetoothAdapter類:代表了一個本地的藍牙適配器。他是所有藍牙交互的的入口點。利用它你可以發現其他藍牙設備,查詢綁定了的設備,使用已知的MAC地址實例化一個藍牙設備和建立一個BluetoothServerSocket(作為服務器端)來監聽來自其他設備的連接。
BluetoothDevice類:代表了一個遠端的藍牙設備,使用它請求遠端藍牙設備連接或者獲取遠端藍牙設備的名稱、地址、種類和綁定狀態。(其信息是封裝在bluetoothsocket中)。
Bluetoothsocket類:代表了一個藍牙套接字的接口(類似於tcp中的套接字),他是應用程序通過輸入、輸出流與其他藍牙設備通信的連接點。
Blueboothserversocket類:代表打開服務連接來監聽可能到來的連接請求(屬於server端),為了連接兩個藍牙設備必須有一個設備作為服務器打開一個服務套接字。當遠端設備發起連接連接請求的時候,並且已經連接到了的時候,Blueboothserversocket類將會返回一個bluetoothsocket。
Bluetoothclass類:描述了一個藍牙設備的一般特點和能力。他的只讀屬性集定義了設備的主、次設備類和一些相關服務。然而,他並沒有准確的描述所有該設備所支持的藍牙文件和服務,而是作為對設備種類來說的一個小小暗示。
下面說說具體的編程實現:
必須確定你的設備支持藍牙,並保證他可以用。如果你的設備支持藍牙,將它使能。當然,有兩種方法,一種是在你的系統設置里開啟藍牙,另外一中是在你的應用程序里啟動藍牙功能,第一種方法就不講了,具體講一個第二種方法:
首先通過調用靜態方法getDefaultAdapter()獲取藍牙適配器bluetoothadapter,以后你就可以使用該對象了。如果返回為空,the story is over。
Eg:BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter == null) { // Device does not support Bluetooth }
其次,調用isEnabled()來查詢當前藍牙設備的狀態,如果返回為false,則表示藍牙設備沒有開啟,接下來你需要封裝一個ACTION_REQUEST_ENABLE請求到intent里面,調用startActivityForResult()方法使能藍牙設備,例如:
if (!mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
至此,如不出意外,恭喜你的藍牙設備已經開啟了,接下來需要查找周邊可能存在的藍牙設備了。
查找設備:
使用bluetoothadapter類里的方法,你可以查找遠端設備(不過藍牙查找的范圍好像是在十米以內吧)或者查詢在你手機上已經匹配(或者說綁定)的其他手機了。當然需要確定對方藍牙設備已經開啟或者已經開啟了“被發現使能“功能(對方設備是可以被發現的是你能夠發起連接的前提條件)。如果該設備是可以被發現的,會反饋回來一些對方的設備信息,比如名字、MAC地址等,利用這些信息,你的設備就可以選擇去向對方初始化一個連接。
如果你是第一次與該設備連接,那么一個配對的請求就會自動的顯示給用戶。當設備配對好之后,他的一些基本信息(主要是名字和MAC)被保存下來並可以使用藍牙的API來讀取。使用已知的MAC地址就可以對遠端的藍牙設備發起連接請求。
匹配好的設備和連接上的設備的不同點:匹配好只是說明對方設備發現了你的存在,並擁有一個共同的識別碼,並且可以連接。連接上:表示當前設備共享一個RFCOMM信道並且兩者之間可以交換數據。也就是是說藍牙設備在建立RFCOMM信道之前,必須是已經配對好了的。
怎么查詢匹配好的設備:
在建立連接之前你必須先查詢配對好了的藍牙設備集(你周圍的藍牙設備可能不止一個),以便你選取哪一個設備進行通信,例如你可以你可以查詢所有配對的藍牙設備,並使用一個數組適配器將其打印顯示出來:
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); // If there are paired devices if (pairedDevices.size() > 0) { // Loop through paired devices for (BluetoothDevice device : pairedDevices) { // Add the name and address to an array adapter to show in a ListView mArrayAdapter.add(device.getName() + "/n" + device.getAddress()); }
建立一個藍牙連接只需要MAC地址就已經足夠了。
掃描設備:
掃描設備,只需要簡單的調用startDiscovery()方法,這個掃描的過程大概持續是12秒,應用程序為了ACTION_FOUND動作需要注冊一個BroadcastReceiver來接受設備掃描到的信息。對於每一個設備,系統都會廣播ACTION_FOUND動作。例如:
// Create a BroadcastReceiver for ACTION_FOUND private final BroadcastReceiver mReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // When discovery finds a device if (BluetoothDevice.ACTION_FOUND.equals(action)) { // Get the BluetoothDevice object from the Intent BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // Add the name and address to an array adapter to show in a ListView mArrayAdapter.add(device.getName() + "/n" + device.getAddress()); } } }; // Register the BroadcastReceiver IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
注意:掃描的過程是一個很耗費資源的過程,一旦你找到你需要的設備之后,在發起連接請求之前,確保你的程序調用cancelDiscovery()方法停止掃描。顯然,如果你已經連接上一個設備,啟動掃描會減少你的通信帶寬。
使能被發現:Enabling discoverability
如果你想使你的設備能夠被其他設備發現,將ACTION_REQUEST_DISCOVERABLE動作封裝在intent中並調用startActivityForResult(Intent, int)方法就可以了。他將在不使你應用程序退出的情況下使你的設備能夠被發現。缺省情況下的使能時間是120秒,當然你可以可以通過添加EXTRA_DISCOVERABLE_DURATION字段來改變使能時間(最大不超過300秒,這是出於對你設備上的信息安全考慮)。例如:
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); startActivity(discoverableIntent);
運行該段代碼之后,系統會彈出一個對話框來提示你啟動設備使能被發現(次過程中如果你的藍牙功能沒有開啟,系統會幫你開啟),並且如果你准備對該遠端設備發現一個連接,你不需要開啟使能設備被發現功能,以為該功能只是在你的應用程序作為服務器端的時候才需要。
連接設備:
在你的應用程序中,想建立兩個藍牙設備之間的連接,你必須實現客戶端和服務器端的代碼(因為任何一個設備都必須可以作為服務端或者客戶端)。一個開啟服務來監聽,一個發起連接請求(使用服務器端設備的MAC地址)。當他們都擁有一個藍牙套接字在同一RFECOMM信道上的時候,可以認為他們之間已經連接上了。服務端和客戶端通過不同的方式或其他們的藍牙套接字。當一個連接監聽到的時候,服務端獲取到藍牙套接字。當客戶可打開一個FRCOMM信道給服務器端的時候,客戶端獲取到藍牙套接字。
注意:在此過程中,如果兩個藍牙設備還沒有配對好的,android系統會通過一個通知或者對話框的形式來通知用戶。RFCOMM連接請求會在用戶選擇之前阻塞。如下圖:
服務端的連接:
當你想要連接兩台設備時,一個必須作為服務端(通過持有一個打開的bluetoothserversocket),目的是監聽外來連接請求,當監聽到以后提供一個連接上的bluetoothsocket給客戶端,當客戶端從bluetoothserversocket得到bluetoothsocket以后就可以銷毀bluetoothserversocket,除非你還想監聽更多的連接請求。
建立服務套接字和監聽連接的基本步驟:
首先通過調用listenUsingRfcommWithServiceRecord(String, UUID)方法來獲取bluetoothserversocket對象,參數string代表了該服務的名稱,UUID代表了和客戶端連接的一個標識(128位格式的字符串ID,相當於pin碼),UUID必須雙方匹配才可以建立連接。其次調用accept()方法來監聽可能到來的連接請求,當監聽到以后,返回一個連接上的藍牙套接字bluetoothsocket。最后,在監聽到一個連接以后,需要調用close()方法來關閉監聽程序。(一般藍牙設備之間是點對點的傳輸)
注意:accept()方法不應該放在主Acitvity里面,因為他是一種阻塞調用(在沒有監聽到連接請求之間程序就一直停在那里)。解決方法是新建一個線程來管理。例如:
private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; public AcceptThread() { // Use a temporary object that is later assigned to mmServerSocket, // because mmServerSocket is final BluetoothServerSocket tmp = null; try { // MY_UUID is the app's UUID string, also used by the client code tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID); } catch (IOException e) { } mmServerSocket = tmp; } public void run() { BluetoothSocket socket = null; // Keep listening until exception occurs or a socket is returned while (true) { try { socket = mmServerSocket.accept(); } catch (IOException e) { break; } // If a connection was accepted if (socket != null) { // Do work to manage the connection (in a separate thread) manageConnectedSocket(socket); mmServerSocket.close(); break; } } } /** Will cancel the listening socket, and cause the thread to finish */ public void cancel() { try { mmServerSocket.close(); } catch (IOException e) { } } }
客戶端的連接:
為了初始化一個與遠端設備的連接,需要先獲取代表該設備的一個bluetoothdevice對象。通過bluetoothdevice對象來獲取bluetoothsocket並初始化連接:
具體步驟:
使用bluetoothdevice對象里的方法createRfcommSocketToServiceRecord(UUID)來獲取bluetoothsocket。UUID就是匹配碼。然后,調用connect()方法來。如果遠端設備接收了該連接,他們將在通信過程中共享RFFCOMM信道,並且connect()方法返回。例如:
private class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; public ConnectThread(BluetoothDevice device) { // Use a temporary object that is later assigned to mmSocket, // because mmSocket is final BluetoothSocket tmp = null; mmDevice = device; // Get a BluetoothSocket to connect with the given BluetoothDevice try { // MY_UUID is the app's UUID string, also used by the server code tmp = device.createRfcommSocketToServiceRecord(MY_UUID); } catch (IOException e) { } mmSocket = tmp; } public void run() { // Cancel discovery because it will slow down the connection mAdapter.cancelDiscovery(); try { // Connect the device through the socket. This will block // until it succeeds or throws an exception mmSocket.connect(); } catch (IOException connectException) { // Unable to connect; close the socket and get out try { mmSocket.close(); } catch (IOException closeException) { } return; } // Do work to manage the connection (in a separate thread) manageConnectedSocket(mmSocket); } /** Will cancel an in-progress connection, and close the socket */ public void cancel() { try { mmSocket.close(); } catch (IOException e) { } }
注意:conncet()方法也是阻塞調用,一般建立一個獨立的線程中來調用該方法。在設備discover過程中不應該發起連接connect(),這樣會明顯減慢速度以至於連接失敗。且數據傳輸完成只有調用close()方法來關閉連接,這樣可以節省系統內部資源。
管理連接(主要涉及數據的傳輸):
當設備連接上以后,每個設備都擁有各自的bluetoothsocket。現在你就可以實現設備之間數據的共享了。
1. 首先通過調用getInputStream()和getOutputStream()方法來獲取輸入輸出流。然后通過調用read(byte[]) 和 write(byte[]).方法來讀取或者寫數據。
2. 實現細節:以為讀取和寫操作都是阻塞調用,需要建立一個專用現成來管理。
private class ConnectedThread extends Thread { private final BluetoothSocket mmSocket; private final InputStream mmInStream; private final OutputStream mmOutStream; public ConnectedThread(BluetoothSocket socket) { mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; // Get the input and output streams, using temp objects because // member streams are final try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { } mmInStream = tmpIn; mmOutStream = tmpOut; } public void run() { byte[] buffer = new byte[1024]; // buffer store for the stream int bytes; // bytes returned from read() // Keep listening to the InputStream until an exception occurs while (true) { try { // Read from the InputStream bytes = mmInStream.read(buffer); // Send the obtained bytes to the UI Activity mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer) .sendToTarget(); } catch (IOException e) { break; } } } /* Call this from the main Activity to send data to the remote device */ public void write(byte[] bytes) { try { mmOutStream.write(bytes); } catch (IOException e) { } } /* Call this from the main Activity to shutdown the connection */ public void cancel() { try { mmSocket.close(); } catch (IOException e) { } } }