Android藍牙開發小結(轉)


學習之前先了解兩個基本概念:

一、RFCOMM協議: 

  一個基於歐洲電信標准協會ETSI07.10規程的串行線性仿真協議。此協議提供RS232控制和狀態信號,如基帶上的損壞,CTS以及數據信號等,為上層業務(如傳統的串行線纜應用)提供了傳送能力。 

  RFCOMM是一個簡單傳輸協議,其目的是針對如何在兩個不同設備上的應用之間保證一條完整的通信路徑,並在它們之間保持一通信段。

  RFCOMM是為了兼容傳統的串口應用,同時取代有線的通信方式,藍牙協議棧需要提供與有線串口一致的通信接口而開發出的協議。RFCOMM協議提供對基於L2CAP協議的串口仿真,基於ETSI07.10。可支持在兩個BT設備之間同時保持高達60路的通信連接。

  RFCOMM只針對直接互連設備之間的連接,或者是設備與網絡接入設備之間的互連。通信兩端設備必須兼容於RFCOMM協議,有兩類設備:DTE (Data Terminal Endpoint,通信終端,如PC,PRINTER)和DCE (Data Circuit Endpoint,通信段的一部分,如Modem)。此兩類設備不作區分。

 

二、MAC硬件地址

  MAC(Medium/MediaAccess Control, 介質訪問控制)MAC地址是燒錄在NetworkInterfaceCard(網卡,NIC)里的.MAC地址,也叫硬件地址,是由48比特長(6字節),16進制的數字組成.0-23位叫做組織唯一標志符(organizationally unique,是識別LAN(局域網)節點的標識.24-47位是由廠家自己分配。其中第40位是組播地址標志位。網卡的物理地址通常是由網卡生產廠家燒入網卡的EPROM(一種閃存芯片,通常可以通過程序擦寫),它存儲的是傳輸數據時真正賴以標識發出數據的電腦和接收數據的主機的地址。

 

  Android平台提供的藍牙API去實現藍牙設備之間的通信,藍牙設備之間的通信主要包括了四個步驟:設置藍牙設備、尋找局域網內可能或者匹配的設備、連接設備和設備之間的數據傳輸。以下是建立藍牙連接的所需要的一些基本類:

BluetoothAdapter類:代表了一個本地的藍牙適配器。它是所有藍牙交互的的入口點。利用它你可以發現其他藍牙設備,查詢綁定了的設備,使用已知的MAC地址實例化一個藍牙設備和建立一個BluetoothServerSocket(作為服務器端)來監聽來自其他設備的連接。

BluetoothDevice類:代表了一個遠端的藍牙設備,使用它請求遠端藍牙設備連接或者獲取遠端藍牙設備的名稱、地址、種類和綁定狀態。(其信息是封裝在bluetoothsocket中)。

Bluetoothsocket類:代表了一個藍牙套接字的接口(類似於tcp中的套接字),它是應用程序通過輸入、輸出流與其他藍牙設備通信的連接點。

Blueboothserversocket類:代表打開服務連接來監聽可能到來的連接請求(屬於server端),為了連接兩個藍牙設備必須有一個設備作為服務器打開一個服務套接字。當遠端設備發起連接連接請求的時候,並且已經連接到了的時候,Blueboothserversocket類將會返回一個bluetoothsocket

Bluetoothclass類:描述了一個藍牙設備的一般特點和能力。它的只讀屬性集定義了設備的主、次設備類和一些相關服務。然而,它並沒有准確地描述所有該設備所支持的藍牙文件和服務,而是作為對設備種類來說的一個小小暗示。

 

下面說說具體的編程實現

1.啟動藍牙功能:

首先通過調用靜態方法getDefaultAdapter()獲取藍牙適配器BluetoothAdapter,以后你就可以使用該對象了。如果返回為空,the story is over例如:

1 BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
2 if (mBluetoothAdapter == null) {
3 // Device does not support Bluetooth
4 }

其次,調用isEnabled()來查詢當前藍牙設備的狀態,如果返回為false,則表示藍牙設備沒有開啟,接下來你需要封裝一個ACTION_REQUEST_ENABLE請求到intent里面,調用startActivityForResult()方法使能藍牙設備,例如:

 

1 if (!mBluetoothAdapter.isEnabled()) {
2 Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
3 startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
4 //不做提示,強行打開
5 // mAdapter.enable();
6 }

 

2. 查找設備:

  使用BluetoothAdapter類里的方法,你可以查找遠端設備(大概十米以內)或者查詢在你手機上已經匹配(或者說綁定)的其他手機了。當然需要確定對方藍牙設備已經開啟或者已經開啟了“被發現使能”功能(對方設備是可以被發現的是你能夠發起連接的前提條件)。如果該設備是可以被發現的,會反饋回來一些對方的設備信息,比如名字、MAC地址等,利用這些信息,你的設備就可以選擇去向對方初始化一個連接。

  如果你是第一次與該設備連接,那么一個配對的請求就會自動的顯示給用戶。當設備配對好之后,他的一些基本信息(主要是名字和MAC)被保存下來並可以使用藍牙的API來讀取。使用已知的MAC地址就可以對遠端的藍牙設備發起連接請求。

  匹配好的設備和連接上的設備的不同點:匹配好只是說明對方設備發現了你的存在,並擁有一個共同的識別碼,並且可以連接。連接上:表示當前設備共享一個RFCOMM信道並且兩者之間可以交換數據。也就是是說藍牙設備在建立RFCOMM信道之前,必須是已經配對好了的。

 

3. 查詢匹配好的設備:

  在建立連接之前你必須先查詢配對好了的藍牙設備集(你周圍的藍牙設備可能不止一個),以便你選取哪一個設備進行通信,例如你可以你可以查詢所有配對的藍牙設備,並使用一個數組適配器將其打印顯示出來:

1 Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
2 // If there are paired devices
3 if (pairedDevices.size() > 0) {
4 //Loop through paired devices
5 for (BluetoothDevice device : pairedDevices) {
6 // Add the name and address to an array adapter to show in a ListView
7 mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
8 }
9 }

建立一個藍牙連接只需要MAC地址就已經足夠了。

 

4. 掃描設備:

  掃描設備,只需要簡單的調用startDiscovery()方法,這個掃描的過程大概持續是12秒,應用程序為了ACTION_FOUND動作需要注冊一個BroadcastReceiver來接受設備掃描到的信息。對於每一個設備,系統都會廣播ACTION_FOUND動作。例如:

 1 // Create a BroadcastReceiver for ACTION_FOUND
2 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
3 public void onReceive(Context context, Intent intent) {
4 String action = intent.getAction();
5 // When discovery finds a device
6 if (BluetoothDevice.ACTION_FOUND.equals(action)) {
7 // Get the BluetoothDevice object from the Intent
8 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
9 // Add the name and address to an array adapter to show in a ListView
10 mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
11 }
12 }
13 };
14
15 // Register the BroadcastReceiver
16 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
17 registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy

 

注意:掃描的過程是一個很耗費資源的過程,一旦你找到你需要的設備之后,在發起連接請求之前,確保你的程序調用cancelDiscovery()方法停止掃描。顯然,如果你已經連接上一個設備,啟動掃描會減少你的通信帶寬。

 

5. 使能被發現:Enabling discoverability

  如果你想使你的設備能夠被其他設備發現,將ACTION_REQUEST_DISCOVERABLE動作封裝在intent中並調用startActivityForResult(Intent, int)方法就可以了。他將在不使你應用程序退出的情況下使你的設備能夠被發現。缺省情況下的使能時間是120秒,當然你可以可以通過添加EXTRA_DISCOVERABLE_DURATION字段來改變使能時間(最大不超過300秒,這是出於對你設備上的信息安全考慮)。例如:

1 Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
2 discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
3 startActivity(discoverableIntent);

  運行該段代碼之后,系統會彈出一個對話框來提示你啟動設備使能被發現(此過程中如果你的藍牙功能沒有開啟,系統會幫你開啟),並且如果你准備對該遠端設備發現一個連接,你不需要開啟使能設備被發現功能,因為該功能只是在你的應用程序作為服務器端的時候才需要。

 

6. 連接設備:

  在應用程序中,想建立兩個藍牙設備之間的連接,必須實現客戶端和服務器端的代碼(因為任何一個設備都必須可以作為服務端或者客戶端)。一個開啟服務來監聽,一個發起連接請求(使用服務器端設備的MAC地址)。當他們都擁有一個藍牙套接字在同一RFECOMM信道上的時候,可以認為他們之間已經連接上了。服務端和客戶端通過不同的方式或其他們的藍牙套接字。當一個連接監聽到的時候,服務端獲取到藍牙套接字。當客戶可打開一個FRCOMM信道給服務器端的時候,客戶端獲取到藍牙套接字。

  注意:在此過程中,如果兩個藍牙設備還沒有配對好的,android系統會通過一個通知或者對話框的形式來通知用戶。RFCOMM連接請求會在用戶選擇之前阻塞。如下圖:                           

 

7. 服務端的連接:

  當你想要連接兩台設備時,一個必須作為服務端(通過持有一個打開的BluetoothServerSocket),目的是監聽外來連接請求,當監聽到以后提供一個連接上的BluetoothSocket給客戶端,當客戶端從BluetoothServerSocket得到BluetoothSocket以后就可以銷毀BluetoothServerSocket,除非你還想監聽更多的連接請求。

  建立服務套接字和監聽連接的基本步驟:

  首先通過調用listenUsingRfcommWithServiceRecord(String, UUID)方法來獲取BluetoothServerSocket對象,參數String代表了該服務的名稱,UUID代表了和客戶端連接的一個標識(128位格式的字符串ID,相當於PIN碼),UUID必須雙方匹配才可以建立連接。

  其次調用accept()方法來監聽可能到來的連接請求,當監聽到以后,返回一個連接上的藍牙套接字BluetoothSocket

  最后,在監聽到一個連接以后,需要調用close()方法來關閉監聽程序。(一般藍牙設備之間是點對點的傳輸)

  注意:accept()方法不應該放在主Acitvity里面,因為它是一種阻塞調用(在沒有監聽到連接請求之前程序就一直停在那里)。解決方法是新建一個線程來管理。例如:

 1 private class AcceptThread extends Thread {
2 private final BluetoothServerSocket mmServerSocket;
3 public AcceptThread() {
4 // Use a temporary object that is later assigned to mmServerSocket,
5         // because mmServerSocket is final
6 BluetoothServerSocket tmp = null;
7 try {
8 // MY_UUID is the app's UUID string, also used by theclient code
9 tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
10 } catch (IOException e) { }
11 mmServerSocket = tmp;
12 }
13
14 public void run() {
15 BluetoothSocket socket = null;
16 // Keep listening until exception occurs or a socket is returned
17 while (true) {
18 try {
19 socket = mmServerSocket.accept();
20 } catch (IOException e) {
21 break;
22 }
23 // If a connection was accepted
24 if (socket != null) {
25 // Do work to manage the connection (in a separate thread)
26 manageConnectedSocket(socket);
27 mmServerSocket.close();
28 break;
29 }
30 }
31 }
32
33 /** Will cancel the listening socket, and cause the thread to finish */
34 public void cancel() {
35 try {
36 mmServerSocket.close();
37 } catch (IOException e) { }
38 }
39 }

 

8. 客戶端的連接:

  為了初始化一個與遠端設備的連接,需要先獲取代表該設備的一個BluetoothDevice對象。通過BluetoothDevice對象來獲取BluetoothSocket並初始化連接,具體步驟:

  使用BluetoothDevice對象里的方法createRfcommSocketToServiceRecord(UUID)來獲取BluetoothSocket。UUID就是匹配碼。然后,調用connect()方法來。如果遠端設備接收了該連接,他們將在通信過程中共享RFFCOMM信道,並且connect()方法返回。例如:

 1 private class ConnectThread extends Thread {
2 private final BluetoothSocket mmSocket;
3 private final BluetoothDevice mmDevice;
4 public ConnectThread(BluetoothDevice device) {
5 // Use a temporary object that is later assigned to mmSocket,
6         // because mmSocket is final
7 BluetoothSocket tmp = null;
8 mmDevice = device;
9 // Get a BluetoothSocket to connect with the given BluetoothDevice
10 try {
11 // MY_UUID is the app's UUID string, also used by the server code
12 tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
13 } catch (IOException e) { }
14 mmSocket = tmp;
15 }
16
17
18 public void run() {
19 // Cancel discovery because it will slow down the connection
20 mAdapter.cancelDiscovery();
21 try {
22 // Connect the device through the socket. This will block
23 // until it succeeds or throws an exception
24 mmSocket.connect();
25 } catch (IOException connectException) {
26 // Unable to connect; close the socket and get out
27 try {
28 mmSocket.close();
29 } catch (IOException closeException) { }
30 return;
31 }
32 // Do work to manage the connection (in a separate thread)
33 manageConnectedSocket(mmSocket);
34 }
35 }

  注意:conncet()方法也是阻塞調用,一般建立一個獨立的線程中來調用該方法。在設備discover過程中不應該發起連接connect(),這樣會明顯減慢速度以至於連接失敗。且數據傳輸完成只有調用close()方法來關閉連接,這樣可以節省系統內部資源。

 

9. 管理連接(主要涉及數據的傳輸):

  當設備連接上以后,每個設備都擁有各自的BluetoothSocket。現在你就可以實現設備之間數據的共享了。

  1> 首先通過調用getInputStream()和getOutputStream()方法來獲取輸入輸出流。然后通過調用read(byte[]) 和write(byte[]).方法來讀取或者寫數據。

  2> 實現細節:以為讀取和寫操作都是阻塞調用,需要建立一個專用現成來管理。

  3> 

 1 private class ConnectedThread extends Thread {
2 private final BluetoothSocket mmSocket;
3 private final InputStream mmInStream;
4 private final OutputStream mmOutStream;
5
6 public ConnectedThread(BluetoothSocket socket) {
7 mmSocket = socket;
8 InputStream tmpIn = null;
9 OutputStream tmpOut = null;
10 // Get the input and output streams, using temp objects because
11         // member streams are final
12 try {
13 tmpIn = socket.getInputStream();
14 tmpOut = socket.getOutputStream();
15 } catch (IOException e) { }
16 mmInStream = tmpIn;
17 mmOutStream = tmpOut;
18 }
19
20 public void run() {
21 byte[] buffer = new byte[1024]; // buffer store for the stream
22 int bytes; // bytes returned from read()
23         // Keep listening to the InputStream until an exception occurs
24 while (true) {
25 try {
26 // Read from the InputStream
27 bytes = mmInStream.read(buffer);
28 // Send the obtained bytes to the UI Activity
29 mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer).sendToTarget();
30 } catch (IOException e) {
31 break;
32 }
33 }
34 }
35
36 /* Call this from the main Activity to send data to the remote device */
37 public void write(byte[] bytes) {
38 try {
39 mmOutStream.write(bytes);
40 } catch (IOException e) { }
41 }
42
43 /* Call this from the main Activity to shutdown the connection */
44 public void cancel() {
45 try {
46 mmSocket.close();
47 } catch (IOException e) { }
48 }
49 }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM