1. 程式人生 > >Android 藍芽及藍芽通訊講解

Android 藍芽及藍芽通訊講解

一、藍芽介紹:

(一)、Bluetooth的由來及現狀

藍芽一詞源於公元十世紀丹麥國王HaraldBlatand名字中的Blatand。Blatand的英文之意就是Blue tooth。這是因為這位讓丹麥人引以為傲的國王酷愛吃藍莓以至於牙齦都被染成藍色。由於Blatand統一了丹麥和挪威,所以,作為無線通訊技術的一種,藍芽技術之所以取名Bluetooth可謂志向遠大。
不過,在以Android為代表的智慧機出現以前,藍芽在早期智慧機甚至功能機中一直扮演著“雞肋”的角色。那麼,隨著無線通訊技術的快速發展以及Android的普及,藍芽技術在我們生活中的應用也越來越多,包括藍芽耳機和滑鼠,及藍芽區域網應用(聊天、遊戲等)。

(二)、藍芽規範介紹

Core Specification(核心規範)
  作用:用於規定藍芽裝置必須實現的通用功能和協議層次。它由軟體和硬體模組組成,兩個模組之間的資訊和資料通過主機控制介面(HCI)的解釋才能進行傳遞
  核心規範是藍芽協議家族的基礎,自藍芽技術聯盟(Bluetooth SIG,Special Interest Group)在1999年頒佈藍芽核心規範1.0版本以來,到目前為止藍芽SIG一共釋出了七個重要版本。每一個版本都促使藍芽技術朝著更快、更安全、更省電的方向發展。

二、藍芽的用法

(一)、藍芽API

Android所有關於藍芽開發的類都在android.bluetooth包下,只有8個類:

BluetoothAdapter     本地藍芽介面卡 
BluetoothClass      藍芽類(主要包括服務和裝置) 
BluetoothClass.Device    藍芽裝置類 
BluetoothClass.Device.Major    藍芽裝置管理 
BluetoothClass.Service   藍芽服務類 
BluetoothDevice     藍芽裝置(遠端藍芽裝置) 
BluetoothServiceSocket    監聽藍芽連線的類 
BluetoothSocket     藍芽連線類

1、BluetoothAdapter :

表示本地的藍芽介面卡 (藍芽射頻)。BluetoothAdapter 是為所有藍芽互動的入口點。它可以發現其他藍芽裝置、 查詢繫結 (配對) 裝置的列表、 例項化已知的 MAC 地址的BluetoothDevice(藍芽裝置) 和建立 BluetoothServerSocket 用於偵聽來自其他裝置的通訊。直到我們建立bluetoothSocket連線之前,都要不斷操作它 。BluetoothAdapter裡的方法很多,常用的有以下幾個:

cancelDiscovery() //根據字面意思,是取消發現,也就是說當我們正在搜尋裝置的時候呼叫這個方法將不再繼續搜尋; 
disable()//關閉藍芽;
enable()//開啟藍芽;這個方法開啟藍芽不會彈出提示,更多的時候我們需要問下使用者是否開啟,以下兩行程式碼同樣是開啟藍芽,不過會提示使用者:
Intemtenabler=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enabler,reCode);//同startActivity(enabler);
getAddress()//獲取本地藍芽地址
getDefaultAdapter()//獲取預設BluetoothAdapter,實際上,也只有這一種方法獲取BluetoothAdapter
getName()//獲取本地藍芽名稱
getRemoteDevice(String address)//根據藍芽地址獲取遠端藍芽裝置
getState()//獲取本地藍芽介面卡當前狀態(感覺可能除錯的時候更需要)
isDiscovering()//判斷當前是否正在查詢裝置,是返回true
isEnabled()//判斷藍芽是否開啟,已開啟返回true,否則,返回false
listenUsingRfcommWithServiceRecord(String name,UUIDuuid)//根據名稱,UUID建立並返回BluetoothServerSocket物件,這是建立BluetoothSocket伺服器端的第一步 。第一個引數表示藍芽服務的名稱,可以是任意字串,第二個引數是UUID。
startDiscovery()//開始搜尋,這是搜尋的第一步

2、BluetoothDevice

表示遠端藍芽裝置。使用此類並通過BluetoothSocket類可以請求連線遠端裝置,或查詢這臺裝置的資訊如其名稱、 地址、 類和繫結狀態。
createRfcommSocketToServiceRecord(UUIDuuid)
根據UUID建立並返回一個BluetoothSocket,這個方法也是我們獲取BluetoothDevice的目的——建立BluetoothSocket。這個類其他的方法,如getAddress()getName(),同BluetoothAdapter

【備註:】藍芽—RFCOMM協議

串列埠模擬協議(RFCOMM),RFCOMM是一個簡單的協議,其中針對9針RS-232串列埠模擬附加了部分條款.可支援在兩個藍芽裝置之間同時保持高達60路的通訊連線.RFCOMM的目的是針對如何在兩個不同裝置上的應用之間保證一條完整的通訊路徑。

3、BluetoothServerSocket

表示開啟伺服器套接字偵聽傳入的請求 (類似於 TCP ServerSocket)。為了連線兩臺 Android 裝置,一臺裝置必須用此類開啟一個伺服器套接字。當遠端藍芽裝置向此裝置發出連線請求時,而且當連線被接收時,BluetoothServerSocket 將返回連線的 BluetoothSocket。這個類有三個方法。

accept()
accept(inttimeout): //兩者的區別在於後者指定了過時時間,需要注意的是,執行這兩個方法的時候,直到接收到了客戶端的請求(或是過期之後),都會阻塞執行緒,應該放在新執行緒裡執行! 還需要注意,這兩個方法都返回一個BluetoothSocket,最後的連線也是伺服器端與客戶端這兩個BluetoothSocket的連線。
close():關閉

4、BluetoothSocket

跟BluetoothServerSocket相對,是客戶端。表示一個藍芽套接字 (類似於 TCP Socket) 的介面。這是一個允許應用程式與另一臺藍芽裝置通過InputStream和OutputStream來交換資料的連線點。其一共5個方法,一般都會用到。

close():關閉
connect():連線
getInptuStream():獲取輸入流
getOutputStream():獲取輸出流
getRemoteDevice():獲取遠端裝置,這裡指的是獲取bluetoothSocket指定連線的那個遠端藍芽裝置

5、BluetoothClass

描述的一般特徵和藍芽裝置的功能。這是一整套只讀的屬性用於定義裝置的主要和次要裝置類和它的服務。然而,這並不是支援所有藍芽配置檔案和服務的裝置,但很適用於獲取裝置型別

6、BluetoothProfile

表示一個藍芽配置檔案。藍芽配置檔案是基於藍芽通訊裝置之間的無線介面規範。如擴音規範(Hands-Free profile)

7、BluetoothHeadset

藍芽耳機與手機一起使用配置檔案 ,這包括藍芽耳機和擴音(v1.5) 的配置檔案

8、BluetoothA2dp

定義瞭如何高質量的音訊可以進行流式處理從一個裝置到另一個通過藍芽連線。”A2DP”代表先進音訊分配協議

9、BluetoothHealth

表示控制藍芽服務健康裝置協議

10、BluetoothHealthCallback

BluetoothHealthCallback 一個抽象類,您使用來實現 BluetoothHealth 回撥,你必須擴充套件此類並實現回撥方法以接收有關更改的更新應用程式的註冊和藍芽通道狀態。BluetoothHealthAppConfiguration 表示一個藍芽健康第三方應用程式註冊與遠端藍芽健康裝置進行通訊的應用程

11、BluetoothHealthAppConfiguration

表示一個藍芽健康第三方應用程式註冊與遠端藍芽健康裝置進行通訊的應用程式配置

12、BluetoothProfile.ServiceListener

通知 BluetoothProfile IPC 客戶端介面時已被連線或斷開服務 (即執行一個特定的配置檔案內部服務)

(二)、使用藍芽的許可權

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

(三)、 UUID(universal unique identifier , 全域性唯一識別符號)

格式如下:UUID格式一般是”xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”,可到http://www.uuidgenerator.com 申請。UUID分為5段,是一個8-4-4-4-12的字串,這個字串要求永不重複。
String uuid = java.util.UUID.randomUUID().toString();
一般在建立Socket時需要UUID作為埠的唯一性,如果兩臺Android裝置互聯,則沒有什麼特殊的,如果讓非Android的藍芽裝置連線Android藍芽裝置,則UUID必須使用某個固定保留的UUID
Android中建立UUID:
UUID  uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
常用固定的UUID
藍芽串列埠服務(SPP)

 SerialPortServiceClass_UUID = '{00001101-0000-1000-8000-00805F9B34FB}'
 LANAccessUsingPPPServiceClass_UUID = '{00001102-0000-1000-8000-00805F9B34FB}'
撥號網路服務
 DialupNetworkingServiceClass_UUID = '{00001103-0000-1000-8000-00805F9B34FB}'
資訊同步服務
 IrMCSyncServiceClass_UUID = '{00001104-0000-1000-8000-00805F9B34FB}'
 SDP_OBEXObjectPushServiceClass_UUID = '{00001105-0000-1000-8000-00805F9B34FB}'
檔案傳輸服務
 OBEXFileTransferServiceClass_UUID = '{00001106-0000-1000-8000-00805F9B34FB}'
 IrMCSyncCommandServiceClass_UUID = '{00001107-0000-1000-8000-00805F9B34FB}'
 SDP_HeadsetServiceClass_UUID = '{00001108-0000-1000-8000-00805F9B34FB}'
 CordlessTelephonyServiceClass_UUID = '{00001109-0000-1000-8000-00805F9B34FB}'
 SDP_AudioSourceServiceClass_UUID = '{0000110A-0000-1000-8000-00805F9B34FB}'
 SDP_AudioSinkServiceClass_UUID = '{0000110B-0000-1000-8000-00805F9B34FB}'
 SDP_AVRemoteControlTargetServiceClass_UUID = '{0000110C-0000-1000-8000-00805F9B34FB}'
 SDP_AdvancedAudioDistributionServiceClass_UUID = '{0000110D-0000-1000-8000-00805F9B34FB}'
 SDP_AVRemoteControlServiceClass_UUID = '{0000110E-0000-1000-8000-00805F9B34FB}'
 VideoConferencingServiceClass_UUID = '{0000110F-0000-1000-8000-00805F9B34FB}'
 IntercomServiceClass_UUID = '{00001110-0000-1000-8000-00805F9B34FB}'
藍芽傳真服務
 FaxServiceClass_UUID = '{00001111-0000-1000-8000-00805F9B34FB}'
 HeadsetAudioGatewayServiceClass_UUID = '{00001112-0000-1000-8000-00805F9B34FB}'
 WAPServiceClass_UUID = '{00001113-0000-1000-8000-00805F9B34FB}'
 WAPClientServiceClass_UUID = '{00001114-0000-1000-8000-00805F9B34FB}'
藍芽列印服務
 HCRPrintServiceClass_UUID = '{00001126-0000-1000-8000-00805F9B34FB}'
 HCRScanServiceClass_UUID = '{00001127-0000-1000-8000-00805F9B34FB}'
 CommonISDNAccessServiceClass_UUID = '{00001128-0000-1000-8000-00805F9B34FB}'
 VideoConferencingGWServiceClass_UUID = '{00001129-0000-1000-8000-00805F9B34FB}'
 UDIMTServiceClass_UUID = '{0000112A-0000-1000-8000-00805F9B34FB}'
 UDITAServiceClass_UUID = '{0000112B-0000-1000-8000-00805F9B34FB}'
 AudioVideoServiceClass_UUID = '{0000112C-0000-1000-8000-00805F9B34FB}'
 SIMAccessServiceClass_UUID = '{0000112D-0000-1000-8000-00805F9B34FB}'
 PnPInformationServiceClass_UUID = '{00001200-0000-1000-8000-00805F9B34FB}'
 GenericNetworkingServiceClass_UUID = '{00001201-0000-1000-8000-00805F9B34FB}'
 GenericFileTransferServiceClass_UUID = '{00001202-0000-1000-8000-00805F9B34FB}'
 GenericAudioServiceClass_UUID = '{00001203-0000-1000-8000-00805F9B34FB}'
 GenericTelephonyServiceClass_UUID = '{00001204-0000-1000-8000-00805F9B34FB}'
個人區域網服務
 PANUServiceClass_UUID = '{00001115-0000-1000-8000-00805F9B34FB}'
 NAPServiceClass_UUID = '{00001116-0000-1000-8000-00805F9B34FB}'
 GNServiceClass_UUID = '{00001117-0000-1000-8000-00805F9B34FB}'
 DirectPrintingServiceClass_UUID = '{00001118-0000-1000-8000-00805F9B34FB}'
 ReferencePrintingServiceClass_UUID = '{00001119-0000-1000-8000-00805F9B34FB}'
 ImagingServiceClass_UUID = '{0000111A-0000-1000-8000-00805F9B34FB}'
 ImagingResponderServiceClass_UUID = '{0000111B-0000-1000-8000-00805F9B34FB}'
 ImagingAutomaticArchiveServiceClass_UUID = '{0000111C-0000-1000-8000-00805F9B34FB}'
 ImagingReferenceObjectsServiceClass_UUID = '{0000111D-0000-1000-8000-00805F9B34FB}'
 SDP_HandsfreeServiceClass_UUID = '{0000111E-0000-1000-8000-00805F9B34FB}'
 HandsfreeAudioGatewayServiceClass_UUID = '{0000111F-0000-1000-8000-00805F9B34FB}'
 DirectPrintingReferenceObjectsServiceClass_UUID = '{00001120-0000-1000-8000-00805F9B34FB}'
 ReflectedUIServiceClass_UUID = '{00001121-0000-1000-8000-00805F9B34FB}'
 BasicPringingServiceClass_UUID = '{00001122-0000-1000-8000-00805F9B34FB}'
 PrintingStatusServiceClass_UUID = '{00001123-0000-1000-8000-00805F9B34FB}'
人機輸入服務
 HumanInterfaceDeviceServiceClass_UUID = '{00001124-0000-1000-8000-00805F9B34FB}'
 HardcopyCableReplacementServiceClass_UUID = '{00001125-0000-1000-8000-00805F9B34FB}'

(四)、使用藍芽的步驟:【五步曲】

1、獲取本地藍芽介面卡

BluetoothAdapter mAdapter= BluetoothAdapter.getDefaultAdapter();

2、開啟藍芽

//彈出對話方塊提示使用者是否開啟
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabler, REQUEST_ENABLE);
//不做提示,強行開啟
// mAdapter.enable();
}
補充一下,使裝置能夠被搜尋
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
startActivityForResult(enabler,REQUEST_DISCOVERABLE);

3、搜尋裝置

1)mAdapter.startDiscovery()是第一步,可是你會發現沒有返回的藍芽裝置,怎麼知道查詢到了呢?
2)定義BroadcastReceiver,程式碼如下 BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) { String action = intent.getAction();
//找到裝置
if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);if (device.getBondState() != BluetoothDevice.BOND_BONDED) { Log.v(TAG, "find device:" + device.getName()+ device.getAddress()); }
//搜尋完成
}else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { setTitle("搜尋完成");
if (mNewDevicesAdapter.getCount() == 0) { Log.v(TAG,"find over"); } }//執行更新列表的程式碼
} };

這樣,沒查詢到新裝置或是搜尋完成,相應的操作都在上段程式碼的兩個if裡執行了,不過前提是你要先註冊
BroadcastReceiver,具體程式碼如下,該段程式碼,一般寫在onCreate()裡.

IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter);

4、建立連線

首先Android sdk(2.0以上版本)支援的藍芽連線是通過BluetoothSocket建立連線,伺服器端(BluetoothServerSocket)和客戶端(BluetoothSocket)需指定同樣的UUID,才能建立連線,因為建立連線的方法會阻塞執行緒,所以伺服器端和客戶端都應啟動新執行緒連線

1)伺服器端:
BluetoothServerSocket serverSocket = mAdapter. listenUsingRfcommWithServiceRecord(serverSocketName,UUID);
serverSocket.accept();2)客戶端:
還記得剛才在BroadcastReceiver獲取了BLuetoothDevice麼?
BluetoothSocket clienSocket=dcvice. createRfcommSocketToServiceRecord(UUID);
clienSocket.connect();

5、資料傳遞

通過以上操作,就已經建立的BluetoothSocket連線了,資料傳遞無非是通過流的形式

1)獲取流
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();2)寫出、讀入
BluetoothServerSocket
BluetoothServerSocket  BluetoothAdapter.listenUsingRfcommWithServiceRecord(String name, UUID)
通過此方法監聽BluetoothSocket的連線
BluetoothServerSocket.accept() 開始接收BluetoothSocket
BluetoothServerSocket.close() 關閉服務
BluetoothSocket
BluetoothSocket BluetoothDevice.createInsecureRfcommSocketToServiceRecord(UUID uuid)
通過此方法向指定的BluetoothDevice傳送Socket連線
UUID:00001101-0000-1000-8000-00805F9B34FBconnect() 嘗試連線boolean isConnected() 是否已連線,要求最低sdk 14+
BluetoothDevice getRemoteDevice() 獲取當前正在或已連線的裝置
InputStream getInputStream() 獲取輸入流
OutputStream getOutputStream() 獲取輸出流
在讀取資料時用資料流
DataInputStream/DataOutputStream
基於Socket技術實現藍芽聊天
藍芽的配對
查詢已配對的藍芽裝置
Set<BluetoothDevice>  BluetoothAdapter.getBondedDevices()查詢附件的藍芽裝置
BluetoothAdapter.startDiscovery())
BluetoothAdapter.isDiscovering() 是否正在查詢
BluetoothAdapter.cancelDiscovery() 取消查詢
註冊廣播接收器接收查到的裝置資訊
BluetoothAdapter.ACTION_DISCOVERY_STARTED 開始查詢
BluetoothDevice.ACTION_FOUND 查詢到藍芽裝置
BluetoothDevice.EXTRA_DEVICE 獲取查詢到的裝置資訊,此資料為ParcelableExtra,需要intent.getParcelableExtra()獲取到BluetoothDevice物件
BluetoothAdapter.ACTION_DISCOVERY_FINISHED 查詢結束
判斷配對狀態int BluetoothDevice.getBondState()  獲取裝置的配對狀態
BluetoothDevice.BOND_BONDED 已配對
BluetoothDevice.BOND_BONDING 正在配對
BluetoothDevice.BOND_NONE 未配對
與指定未配對的裝置配對
配對:通過反射獲取BluetoothDevice的boolean createBond()方法,並執行
取消配對:通過反射獲取BluetoothDevice的boolean removeBond()方法,並執行

三、藍芽通訊案例【實現伺服器端與多個客戶端通過藍芽聊天】

(一)、伺服器端程式製作步驟:

1、伺服器執行緒
構造方法,建立BluetoothServerSocket物件;
通過bluetoothAdapter 的listenUsingRfcommWithServiceRecord()方法;
重寫run()方法。死迴圈中,BluetoothServerSocket物件等待接收客戶端的請求 ,如果建立連線,則將客戶端執行緒放入客戶端執行緒集合,並啟動客戶端執行緒。

BluetoothSocket clientSocket = serverSocket.accept();
ClientThread clientThread = new ClientThread(clientSocket);
clientsList.add(clientThread);
clientThread.start();

2、客戶端執行緒
構造方法,獲取客戶端裝置名稱,並獲取客戶端套接字物件的輸入、輸出流物件;

clientName = clientSocket.getRemoteDevice().getName();
is = new DataInputStream(clientSocket.getInputStream());
os = new DataOutputStream(clientSocket.getOutputStream());

重寫run()方法。死迴圈中,讀取客戶端傳送過來的資料,追加到聊天記錄中。啟動傳送執行緒,向所有客戶端傳送聊天資料。

String info = is.readUTF();
appendContent(info);new SendThread(info).start(); // 向所有客戶端傳送此資料
自定義sendInfo()訊息傳送方法。 os.writeUTF(info);

3、傳送訊息執行緒
構造方法,初始化要傳送的資訊資料;
this.info = info;
重寫run()方法。迴圈客戶端執行緒集合,獲取當前已連線的客戶端,並逐一向其傳送資料 。
clientThread.sendInfo(info);

(二)、客戶端程式製作步驟:

1、客戶端執行緒
構造方法,初始化BluetoothDevice,連線選擇的裝置,建立連線,並獲取客戶端套接字物件的輸入、輸出流物件;

this.bluetoothDevice = device;
clientSocket = bluetoothDevice .createRfcommSocketToServiceRecord(MainActivity.MY_UUID); 
clientSocket.connect();
is = new DataInputStream(clientSocket.getInputStream());
os = new DataOutputStream(clientSocket.getOutputStream());

重寫run()方法。死迴圈中,讀取客戶端輸入流資料,追加到聊天記錄中。

String info = is.readUTF();
appendContent(info);

自定義sendInfo()訊息傳送方法。
os.writeUTF(info);
2、傳送訊息執行緒
構造方法,初始化要傳送的資訊資料;
this.info = info;
重寫run()方法。呼叫連線執行緒的sendInfo()方法,向伺服器端傳送資料 。
clientThread.sendInfo(info);