一、廢話
就快過年了,公司還不給發年終獎!我都快到大街上討飯了,公司也不帶管我的!
二、正文
藍牙是啥我就不再說了,因為我肯定解釋不清楚藍牙是啥,有興趣請移步至百度百科。
在開始前我們需要准備好一部手機而不是模擬器,且手機已經打開調試模式並連接到電腦上。文中的本地設備均指我們自己的手機,而遠程設備則是指其他的設備(電腦、其他手機或者其他)。
通常情況下,我們對藍牙的操作主要有:開啟和關閉藍牙、搜索周邊設備、能被周邊設備所發現、獲取配對設備、藍牙設備間的數據傳輸。
1、打開藍牙(當然首先要確保你的手機是有藍牙設備的)
藍牙設備主要分為兩部分,一部分為本地設備,另一部分為遠程設備。
- BluetoothAdapter——本地設備,對藍牙操作首先就需要有一個BluetoothAdapter實例。常用的幾個方法如下:
- cancelDiscovery()——取消本地藍牙設備的搜索操作,如果本地設備正在進行搜索,那么調用該方法后將停止搜索操作。
- Disable()——關閉藍牙設備。
- Enable()——打開藍牙設備。相信大家都有過打開藍牙的經歷,一般情況下都會彈出一個窗口,說正在請求打開藍牙設備,你是不是允許雲雲。
- getAddress()——獲取藍牙設備的MAC地址。
- GetDefaultAdapter()——獲取本地的藍牙設備
- getName()——獲取本地藍牙的名稱
- getRemoteDevice(String address)——根據遠程設備的MAC地址來獲取遠程設備
- startDiscovery()——藍牙設備開始搜索周邊設備
- BuletoothDevice——遠程設備。
它所包含的方法和BluetoothAdapter一樣,不再累述。

1 // 獲取本地的藍牙適配器實例
2 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
3 if(adapter!=null)
4 {
5 if(!adapter.isEnabled())
6 {
7 //通過這個方法來請求打開我們的藍牙設備
8 Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
9 startActivity(intent);
10 }
11 }
12 else
13 {
14 System.out.println("本地設備驅動異常!");
15 }
2、搜索周邊設備
對於Android查找發現藍牙設備使用BluetoothAdapter類的startDiscovery()方法就可以執行一個異步方式獲取周邊的藍牙設備,因為是一個異步的方法所以我們不需要考慮線程被阻塞問題,整個過程大約需要12秒時間,這時我們可以注冊一個 BroadcastReceiver 對象來接收查找到的藍牙設備信息,我們通過Filter來過濾ACTION_FOUND這個 Intent動作以獲取每個遠程設備的詳細信息,通過Intent字段EXTRA_DEVICE 和 EXTRA_CLASS可以獲得包含了每個BluetoothDevice 對象和對象的該設備類型 BluetoothClass。
實現一個自己的BroadCastReceiver類,並注冊這個類。

1 private class BluetoothReciever extends BroadcastReceiver {
2
3 @Override
4 public void onReceive(Context context, Intent intent) {
5 // TODO Auto-generated method stub
6 String action = intent.getAction();
7 if (BluetoothDevice.ACTION_FOUND.equals(action)) {
8 BluetoothDevice device = intent
9 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
10 System.out.println(device.getAddress());
11 }
12 }
13
14 }

1 IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
2 bluetoothReceive = new BluetoothReciever();
3 registerReceiver(bluetoothReceive, intentFilter);
因為在注冊一個Receiver后,程序並不知道該何時去回收它,所以需要我們自己重寫Activity類的onDestroy()方法。

1 @Override
2 protected void onDestroy() {
3 // TODO Auto-generated method stub
4 unregisterReceiver(bluetoothReceive);
5 super.onDestroy();
6 }
3、被周邊設備所發現
如果需要用戶確認操作,不需要獲取底層藍牙服務實例,可以通過一個Intent來傳遞ACTION_REQUEST_DISCOVERABLE參數, 這里通過startActivity來請求開啟。

1 Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
2 //50這個參數代表的是藍牙設備能在多少秒內被發現 discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 50);
3 startActivity(discoverableIntent);
4、配對
配對操作呢,一般都是發現設備后,由我們人工來進行選擇后系統自動去配對。我們可以通過下面的方法來獲得配對的設備:

1 //通過getBondedDevices方法來獲取已經與本設備配對的設備
2 Set<BluetoothDevice> device= adapter.getBondedDevices();
3 if(device.size()>0)
4 {
5 for(Iterator iterator=device.iterator();iterator.hasNext();)
6 {
7 BluetoothDevice bluetoothDevice=(BluetoothDevice)iterator.next();
8 System.out.println(bluetoothDevice.getAddress());
9 }
10 }
5、數據交換
在看過前面的啟動、發現/搜索、配對這些操作后,下面來說說數據傳輸的問題。
在Android系統中,藍牙設備間的數據傳輸問題和我們在PC上的網絡編程頗為類似,有一端作為Server端監聽Client端的連接請求,在二者建立了連接后,就可以使用普通的數據傳輸方式進行數據交換操作了。在這個過程中,我需要使用到BluetoothServerSocket和BluetoothSocket兩個類來建立Server端和Client端,還需要使用到一些關於流(Stream)的知識。
- BluetoothServerSocket——服務端(監聽端、監聽器、接受請求的一端)
- Accept()——阻塞宿主線程,直至收到客戶端請求。返回BluetoothSocket對象。由於這個
- Accept(int timeout)——阻塞宿主線程,直至收到客戶端請求或等待時間超過timeout。返回BluetoothSocket對象。
- Close()——關閉BluetoothServerSocket監聽器。
可以看到,Accept方法是一個阻塞方法,所以在進行開發的時候,一般都需要用到多線程的知識。JAVA的多線程知識,可以在JAVA的JDK幫助文檔中查看,就單純的應用來說還是比較簡單的。
- BluetoothSocket——客戶端(請求端)
- Close()——關閉BluetoothSocket請求端。
- Connect()——主動向服務端(監聽端)發起連接請求。
在了解了這兩個類后,可以着手來建立我們自己的Server端和Client端了。
如果一個設備需要和兩個或多個設備連接時,就需要作為一個server來傳輸,服務器端套接字在接受(accepted) 一個客戶發來的BluetoothSocket連接請求時作出相應的響應。服務器socket將監聽進入的連接請求,一旦連接被接受,將產生一個BluetoothSocket。
- 創建一個Server
使用BluetoothAdapter類的listenUsingRfcommWithServiceRecord方法來新建一個ServerSocket。在listenUsingRfcommWithServiceRecord中有一個參數叫做UUID,UUID(Universally Unique Identifier)是一個128位的字符串ID,被用於唯一標識我們的藍牙服務。你可以使用web上的任何一款UUID產生器為你的程序獲取一個UUID,然后使用fromString(String)初始化一個UUID。
使用ServerSocket實例的accept方法進行監聽,當監聽到帶有我們初始化的UUID參數的連接請求后作出響應,連接成功后返回一個BluetoothSocket對象。連接完成后,調用close方法關閉該Socket監聽。

1 // Bluetooth的ServerSocket包裝類
2 class BluetoothServer {
3 public BluetoothServer() throws IOException {
4 }
5
6 // 要建立一個ServerSocket對象,需要使用adapter.listenUsingRfcommWithServiceRecord方法
7 // UUID可以在網上去申請
8 private BluetoothServerSocket serverSocket = adapter.listenUsingRfcommWithServiceRecord("myServerSocket",
9 UUID.fromString("84D1319C-FBAF-644C-901A-8F091F25AF04"));
10 BluetoothSocket socket = serverSocket.accept();
11
12 void m() throws IOException {
13 if (socket != null) {
14 InputStream inputStream = socket.getInputStream();
15 int read = -1;
16 final byte[] bytes = new byte[1024];
17 for (; (read = inputStream.read(bytes)) > -1;) {
18 final int count = read;
19 Thread _start = new Thread(new Runnable() {
20
21 @Override
22 public void run() {
23 // TODO Auto-generated method stub
24 StringBuilder sb = new StringBuilder();
25 for (int i = 0; i < count; i++) {
26 if (i > 0) {
27 sb.append(' ');
28 }
29 String _s = Integer.toHexString(bytes[i] & 0xFF);
30 if (_s.length() < 2) {
31 sb.append('0');
32 }
33 sb.append(_s);
34 }
35 System.out.println(sb.toString());
36 }
37 });
38 _start.start();
39 }
40 }
41 }
42 }
- 創建一個Client
創建一個Client端,首先需要我們使用BluetoothDevice的實例的createRfcommSocketToServiceRecord方法來創建一個BluetoothSocket實例。在創建的時候,需要給createRfcommSocketToServiceRecord方法傳入我們服務端的UUID值。然后使用BluetoothSocket實例的Connect方法對Server端進行連接請求,當連接成功后,Client端和Server端的傳輸通道就被打開。最后在連接完成后使用該實例的close方法來關閉這個連接。

1 class BluetoothClient {
2 BluetoothDevice device = null;
3
4 //通過構造函數來傳入一個BluetoothDevice實例
5 public BluetoothClient(BluetoothDevice device) {
6 this.device = device;
7 }
8 BluetoothSocket socket = null;
9 void connetServer() throws IOException {
10 Thread _clientThread = new Thread(new Runnable() {
11 public void run() {
12 try {
13 //通過BluetoothDevice實例的createRfcommSocketToServiceRecord方法可以返回一個帶有UUID的BluetoothSocket實例
14 socket = device.createRfcommSocketToServiceRecord(UUID.fromString("84D1319C-FBAF-644C-901A-8F091F25AF04"));
15 } catch (IOException e1) {
16 // TODO Auto-generated catch block
17 e1.printStackTrace();
18 }
19 try {
20 socket.connect();
21 } catch (IOException e1) {
22 // TODO Auto-generated catch block
23 e1.printStackTrace();
24 }
25 if (socket != null) {
26 try {
27 socket.close();
28 } catch (Exception e) {
29 // TODO: handle exception
30 }
31 }
32 }
33 });
34 _clientThread.start();
35 }
36 }
getInputStream()——獲得一個可讀的流,該流在連接不成功的情況下依舊可以獲得,但是對其操作的話就會報IOException的異常。需要從外部獲取的數據都從該流中獲取。
getOutputStrem()——獲得一個可寫的流,該流在連接不成功的情況下依舊可以獲得,但是對其操作的話就會報IOException的異常。需要往外部傳輸的數據都可以寫到該流中傳輸出去。
數據傳輸的大致流程如下:
-
- 首先,分別通過getInputStream()和getOutputStream()獲得管理數據傳輸的InputStream和OutputStream。
- 然后,開辟一個線程專門用於數據的讀或寫。這是非常重要的,因為read(byte[])和write(byte[])方法都是阻塞調用。read(byte[])從輸入流(InputStream)中讀取數據。write(byte[])將數據寫入到OutputStream流中去,這個方法一般不會阻塞,但當遠程設備的中間緩沖區已滿而對方沒有及時地調用read(byte[])時將會一直阻塞。所以,新開辟的線程中的主循環將一直用於從InputStream中讀取數據。
還要補充一點,由於藍牙設備是系統設備,所以需要有相應的權限支持。在AndroidManifest.xml文件中添加上權限。
1 <uses-permission android:name="android.permission.BLUETOOTH"></uses-permission>
2 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"></uses-permission>
三、總結
在這個筆記中將了下藍牙的一些簡單的操作,包括藍牙的開啟、搜索、配對等操作,同時還有數據的傳輸問題,關於數據的傳輸的相關操作最重要的其實是多線程的操作和數據流的操作,所以對這兩點不熟悉的同學可以去看看java的JDK幫助文檔。在下節中,將會將一些WIFI的操作,因為WIFI內容不是很多,所以還將附加上一些Android網絡編程方面的內容。