作爲一個小白,爲做項目自學安卓才10天,要做出一個安卓app,手機藍牙從stm32上的藍牙讀出數據,並畫波形圖真是不容易啊。加油,乾乾幹!!
學習來源:
http://www.apkbus.com/forum.php?mod=viewthread&tid=140138
一、如何利用android平臺提供的藍牙API去實現藍牙設備之間的通信?
藍牙設備之間的通信主要包括了四個步驟:
1.設置藍牙設備,2.尋找局域網內可能或者匹配的設備,3.連接設備,4.設備之間的數據傳輸。
建立藍牙連接的所需要的一些基本類有:
(1) BluetoothAdapter類:代表了一個本地的藍牙適配器。他是所有藍牙交互的的入口點。利用它你可以發現其他藍牙設備,查詢綁定了的設備,使用已知的MAC地址實例化一個藍牙設備和建立一個BluetoothServerSocket(作爲服務器端)來監聽來自其他設備的連接。
(2)BluetoothDevice類:代表了一個遠端的藍牙設備,使用它請求遠端藍牙設備連接或者獲取遠端藍牙設備的名稱、地址、種類和綁定狀態。(其信息封裝在bluetoothsocket中。)
(3)Bluetoothsocket類:代表了一個藍牙套接字的接口(類似於tcp中的套接字),他是應用程序通過輸入、輸出流與其他藍牙設備通信的連接點。
(4)Blueboothserversocket類:代表打開服務連接來監聽可能到來的連接請求(屬於server端),爲了連接兩個藍牙設備必須有一個設備作爲服務器打開一個服務套接字。當遠端設備發起連接連接請求的時候,並且已經連接到了的時候,Blueboothserversocket類將會返回一個bluetoothsocket。
(5)Bluetoothclass類:描述了一個藍牙設備的一般特點和能力。他的只讀屬性集定義了設備的主、次設備類和一些相關服務。然而,他並沒有準確的描述所有該設備所支持的藍牙文件和服務,而是作爲對設備種類來說的一個小小暗示。
具體編程實現:
【你的設備必須支持藍牙。要將它使能有兩種方法:一是在你的系統設置裏開啓藍牙,二是在應用程序裏啓動藍牙功能。具體說明第二種方法。】
1.首先通過調用靜態方法getDefaultAdapter()獲取藍牙適配器bluetoothadapter,然後就可以使用該對象了。如果返回爲空,沒戲了。
eg:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null)
{
// Device does not support Bluetooth
}
2.調用isEnabled()來查詢當前藍牙設備的狀態,如果返回爲false,則表示藍牙設備沒有開啓。接下來就需要封裝一個ACTION_REQUEST_ENABLE請求到intent裏面,調用startActivityForResult()方法使能藍牙設備。
eg:
if (!mBluetoothAdapter.isEnabled())
{
Intent enableBtIntent = newIntent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
至此,如不出意外,藍牙設備已經開啓了。接下來需要查找周邊可能存在的藍牙設備了。
3.查找設備:
【使用bluetoothadapter類裏的方法,你可以查找遠端設備(藍牙查找的範圍好像是在十米以內吧)或者查詢在你手機上已經匹配的其他手機了。當然需要確定對方藍牙設備已經開啓了“被發現使能“功能(對方設備是可以被發現是你能夠發起連接的前提條件)。如果該設備是可以被發現的,會反饋回來一些對方的設備信息,比如名字、MAC地址等,利用這些信息,你的設備就可以選擇去向對方初始化一個連接。】
如果是第一次與該設備連接,那麼一個配對的請求就會自動的顯示給用戶。當設備配對好之後,他的一些基本信息(主要是名字和MAC)被保存下來並可以使用藍牙的API來讀取。使用已知的MAC地址就可以對遠端的藍牙設備發起連接請求。
匹配好的設備和連接上的設備的不同點:匹配好只是說明對方設備發現了你的存在,並擁有一個共同的識別碼,並且可以連接。連接上:表示當前設備共享一個RFCOMM信道並且兩者之間可以交換數據。也就是是說藍牙設備在建立RFCOMM信道之前,必須是已經配對好了的。
如何查詢匹配好的設備:
在建立連接之前你必須先查詢配對好了的藍牙設備集(你周圍的藍牙設備可能不止一個),以便你選取其中一個設備進行通信,例如你可以查詢所有配對的藍牙設備,並使用一個數組適配器將其打印顯示出來。
eg:
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0)
{
// Loop through paired device
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地址就足夠了。
4.掃描設備:
掃描設備,只需要簡單的調用startDiscovery()方法,這個掃描的過程大概持續是12秒,應用程序爲了ACTION_FOUND動作需要註冊一個BroadcastReceiver來接受設備掃描到的信息。對於每一個設備,系統都會廣播ACTION_FOUND動作。
eg:
”’
// 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()方法停止掃描。顯然,如果你已經連接上一個設備,啓動掃描會減少你的通信帶寬。
5.連接設備:
在你的應用程序中,想建立兩個藍牙設備之間的連接,你必須實現客戶端和服務器端的代碼(因爲任何一個設備都必須可以作爲服務端或者客戶端)。一個開啓服務來監聽,一個發起連接請求(使用服務器端設備的MAC地址)。當他們都擁有一個藍牙套接字在同一RFECOMM信道上的時候,可以認爲他們之間已經連接上了。服務端和客戶端通過不同的方式或其他們的藍牙套接字。當一個連接監聽到的時候,服務端獲取到藍牙套接字。當客戶可打開一個FRCOMM信道給服務器端的時候,客戶端獲取到藍牙套接字。
注意:在此過程中,如果兩個藍牙設備還沒有配對好的,android系統會通過一個通知或者對話框的形式來通知用戶。
服務端的連接:
當想要連接兩臺設備時,必須一個作爲服務端(通過持有一個打開的bluetoothserversocket),目的是監聽外來連接請求,當監聽到以後提供一個連接上的bluetoothsocket給客戶端,當客戶端從bluetoothserversocket得到bluetoothsocket以後就可以銷燬bluetoothserversocket,除非還想監聽更多的連接請求。
建立服務套接字和監聽連接的基本步驟:
1.首先通過調用listenUsingRfcommWithServiceRecord(String, UUID)方法來獲取bluetoothserversocket對象,參數string代表了該服務的名稱,UUID代表了和客戶端連接的一個標識(128位格式的字符串ID,相當於pin碼),UUID必須雙方匹配纔可以建立連接。
2.其次調用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()方法返回。
【!!!注意:conncet()方法也是阻塞調用,一般建立一個獨立的線程中來調用該方法。在設備discover過程中不應該發起連接connect(),這樣會明顯減慢速度以至於連接失敗。且數據傳輸完成只有調用close()方法來關閉連接,這樣可以節省系統內部資源。】
eg:
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) { }
}
管理連接(主要涉及數據的傳輸):
當設備連接上以後,每個設備都擁有各自的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) { }
}
}