進行藍牙連接的兩種方式


為了在兩台設備間創建一個連接,必須實現服務器端和客戶端的機制,因為一個設備必須打開一個Server Socket,而另一個必須發起連接(使用服務器端設備的MAC地址發起連接)。當服務器端和客戶端在同一個RFCOMM信道上都有一個BluetoothSocket時,則兩端就建立了連接。此刻,每個設備都能獲得一個輸入輸出流,進行數據傳輸。
服務器端和客戶端獲得BluetoothSocket的方法是不同的,服務器端是在客戶端的連接被接受時才產生一個BluetoothSocket,客戶端是在打開一個到服務器端的RFCOMM信道時獲得BluetoothSocket的。
藍牙連接的一種實現技術是,每一個設備都自動准備作為一個服務器,所以每個設備都有一個Server Socket並監聽連接。然后每個設備都能作為客戶端建立一個到另一個設備的連接。
另外一種替代方法是,一個設備按需打開一個Server Socket,另外一個設備僅作為客戶端建立與這個設備的連接。

1.作為服務器連接
如果要連接兩個設備,其中一個必須充當服務器,它擁有BluetoothServerSocket。服務器Socket的作用是偵聽進來的連接,且在一個連接被接受時返回一個BluetoothSocket對象。從BluetoothServerSocket獲取到BluetoothSocket對象之后,BluetoothServerSocket就可以(也應該)丟棄了,除非還要用它接收更多的連接。
下面是建立服務器Socket和接受一個連接的基本步驟:
步驟1 通過調用listenUsingRfcommWithServiceRecord(String, UUID)方法得到一個BluetoothServerSocket對象。字符串參數為服務的標識名稱,名字是任意的,可以簡單地是應用程序的名稱。當客戶端試圖連接本設備時,它將攜帶一個UUID用來唯一標識它要連接的服務,UUID必須匹配,連接才會被接受。
步驟2 通過調用accept()來偵聽連接請求。這是一個阻塞線程,直到接受一個連接或者產生異常才會返回。當客戶端攜帶的UUID與偵聽它Socket注冊的UUID匹配,連接請求才會被接受。如果成功,accept()將返回一個BluetoothSocket對象。
步驟3 除非需要再接受另外的連接,否則的話調用close()。close()釋放Server Socket及其資源,但不會關閉accept()返回的BluetoothSocket對象。與TCP/IP不同,RFCOMM同一時刻一個信道只允許一個客戶端連接,因此大多是情況下意味着在BluetoothServerSocket接受一個連接請求后應該立即調用close()。
accept()調用不應該在主Activity UI線程中進行,因為它是個阻塞線程,會妨礙應用中其他的交互。通常在一個新線程中做BluetoothServerSocket或BluetoothSocket的所有工作來避免線程阻塞。如果需要放棄阻塞線程,可以調用close()方法。
下面是一個服務器組件接受連接的線程示例。

//定義接受線程
private class AcceptThread extends Thread {
	//創建BluetoothServerSocket類
	private final BluetoothServerSocket mmServerSocket;
	public AcceptThread() {
		BluetoothServerSocket tmp = null;
		try {
			//MY_UUID是應用的UUID標識
			tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
		} catch(IOException e) {}
		mmServerSocket = tmp;
	}
	//線程啟動時候運行
	public void run() {
		BluetoothSocket socket = null;
		//保持偵聽
		while(true) {
			try {
				//接受
				socket = mmServerSocket.accept();
			} catch(IOException e) {
				break;
			}
			//連接被接受
			if(socket!=null) {
				//管理連接
				manageConnectedSocket(socket);
				//關閉連接
				mmServerSocket.close();
				break;
			}
		}
	}
	//關閉連接
	public void cancel() {
		try
		{
			//關閉BluetoothServerSocket
			mmServerSocket.close();
		}
		catch(IOException e) {}
	}
}

本例中,只接受一個進來的連接,一旦連接被接受並獲取BluetoothSocket,應用就發送獲取到的BluetoothSocket給一個單獨的線程,然后關閉BluetoothServerSocket並跳出循環。
---------------------
注意 accept()返回BluetoothSocket后,Socket就建立了連接,所以在客戶端就不應該再調用connect()。
---------------------

 

2.作為客戶端連接

為了實現與遠程服務器設備的連接,必須首先獲得一個代表遠程設備BluetoothDevice的對象。然后使用BluetoothDevice對象來獲取一個BluetoothSocket以實現連接。
基本步驟如下:
步驟1 使用BluetoothDevice調用方法createRfcommSocketToServiceRecord(UUID)獲取一個BluetoothSocket對象。
步驟2 調用connect()建立連接。當調用這個方法的時候,系統會在遠程設備上完成一個SDP查找來匹配UUID。如果查找成功並且遠程設備接受連接,就共享RFCOMM信道,connect()會返回。這個方法也是一個阻塞的調用。如果連接失敗或者超時(12秒)都會拋出異常。
---------------------
注意 要確保在調用connect()時沒有同時做設備搜索,如果在搜索設備,該連接嘗試會顯著變慢,容易導致連接失敗。
---------------------
下面是一個發起Bluetooth連接的線程示例。

//定義Bluetooth連接線程
private class ConnectThread extends Thread {
	//新建BluetoothSocket類
	private final BluetoothSocket mmSocket;
	//新建BluetoothDevice對象
	private final BluetoothDevice mmDevice;
	public ConnectThread(BluetoothDevice device) {
		BluetoothSocket tmp = null;
		//賦值給設備
		mmDevice = device;
		try {
			//根據UUID創建並返回一個BluetoothSocket
			tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
		} catch(IOException e) {}
		//賦值給BluetoothSocket
		mmSocket = tmp;
	}
	public void run() {
		//取消發現設備
		mBluetoothAdapter.cancelDiscovery();
		try {
			//連接到設備
			mmSocket.connect();
		} catch(IOException connectException) {
			//無法連接,關閉Socket
			try
			{
				mmSocket.close();
			}
			catch (IOException connectException) {}
			return;
		}
		//管理連接
		manageConnectedSocket(mmSocket);
	}
	//取消連接
	public void cancel() {
		try
		{
			//關閉BluetoothSocket
			mmSocket.close();
		}
		catch(IOException e) {}
	}
}

注意:cancelDiscovery()應用連接操作前被調用。在連接之前,不管搜索有沒有進行。該調用都是安全的,不需要確認(當然如果要確認,可以調用isDiscovering()方法)。處理完后別忘了調用close()方法來關閉連接的Socket和釋放所有的內部資源。

 

3.管理連接

如果兩個設備成功建立了連接,各自會有一個BluetoothSocket,此時可以在設備間共享數據了。使用BluetoothSocket,傳輸任何數據通常來說都比較容易,通常如下進行:
+分別使用getInputStream()和getOutputStream()獲取輸入輸出流來處理傳輸。
+調用read(byte[])和write(byte[])來實現數據讀寫。
當然,要注意一些實現細節。比如,需要用一個專門的線程來實現流的讀寫,因為方法read(byte[])和write(byte[])都是阻塞調用。read(byte[])會阻塞,直到流中有數據可讀。write(byte[])雖然通常不會阻塞,但是如果遠程設備調用read(byte[])不夠快而導致中間緩沖區滿,它也可能阻塞。所以線程中的主循環應該用於讀取InputStream。線程中也應該有單獨的方法用來完成寫OutputStream。
請看下面的示例:

//連接管理線程
private class ConnectThread extends Thread {
	//新建BluetoothSocket類
	private final BluetoothSocket mmSocket;
	//新建輸入流對象
	private final InputStream mmInStream;
	//新建輸出流對象
	private final OutputStream mmOutStream;
	public ConnectThread(BluetoothSocket socket) {
		//為BluetoothSocket賦初始值
		mmSocket = socket;
		//輸入流賦值為null
		InputStream tmpIn = null;
		//輸出流賦值為null
		OutputStream tmpOut = null;
		try {
			//從BluetoothSocket中獲取輸入流
			tmpIn = socket.getInputStream();
			//從BluetoothSocket中獲取輸出流
			tmpOut = socket.getOutputStream();
		} catch(IOException e) {}
		//為輸入流賦值
		mmInStream = temIn;
		//為輸出流賦值
		mmOutStream = temOut;
	}
	public void run() {
		//流的緩沖大小
		byte[] buffer = new byte[1024];
		//用於保存read()所讀取的字節數
		int bytes;
		while(true) {
			try
			{
				//從輸入流中讀取數據
				bytes = mmInStream.read(buffer);
				//發送數據到界面
				mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer).sendToTarget();
			}
			catch ()
			{
				break;
			}
		}
	}
	//發送數據到遠程設備
	public void write(byte[] bytes) {
		try
		{
			//寫數據到輸出流中
			mmOutStream.write(bytes);
		}
		catch(IOException e) {}
	}
	//取消
	public void cancel() {
		try
		{
			//關閉B連接
			mmSocket.close();
		}
		catch(IOException e) {}
	}
}

構造函數中得到需要的流,一旦執行,線程會等待從InputStream來的數據,當read(byte[])返回從流中讀到的字節后,數據通過父類的成員Handler被送到主Activity,然后繼續等待讀取流中的數據。向外發送數據只需簡單地調用線程的write()方法。線程的cancel()方法是很重要的,以便連接可以在任何時候通過關閉BluetoothSocket來終止。它總在處理完Bluetooth連接后被調用。


免責聲明!

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



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