因爲項目須要,筆者須要在安卓平臺開發一個程序,可以用藍牙和下層的單片機通信。java
出於測試目的,筆者先用兩部均支持藍牙的安卓設備進行設備間通信實驗,本文就是在這個實驗基礎上寫就的。android
查閱了一些參考書籍和博客文章,筆者非常失望,由於它們都是羅列代碼(並且幾乎都是套用安卓官方自帶的那兩個例子——API Guides下的藍牙指導代碼和samples下的BluetoothChat),可是並無給出系統的宏觀分析,讓人頗感失望。程序員
認真研讀API Guides下的藍牙指導代碼後,筆者先繪製出了類圖,而後在其指導下完成了整個實驗程序的構建,其間固然多有反覆與修改,做爲程序員你們確定都懂的——「三分編程,七分調試」。編程
1.藍牙是什麼?app
一種近距離無線通訊協議,小功率藍牙設備(通常咱們見到的都是)的通信速率約爲1Mbps,通信距離爲10m。框架
2.藍牙分主從嗎?socket
分的,藍牙組網的方式是:1主(<8)從。藍牙組的網有個可愛的名字——「微微網」(piconet),藍牙設備在微微網的地址(注意不是mac地址)是3位,所以一個微微網最多有8臺被激活設備。設備的主從角色的分配是在組成微微網時臨時肯定的,不過藍牙技術支持「主從轉換」。ide
3.藍牙設備都有哪些狀態?工具
激活,呼吸,保持,休眠:功率依次遞減。oop
咱們先來看看通常的通信模型是怎樣的
打開-》創建鏈接-》通信-》斷開鏈接-》關閉
打開設備是一切工做的前提,創建鏈接須要保證兩個藍牙設備之間的可見性而搜索就是找尋周圍的藍牙設備(此操做比較佔帶寬,通信時務必關掉),通信就是把兩個設備用某種方式鏈接起來(一主一從)而後發送消息與接收消息,最後須要斷開鏈接,關閉設備。
據此,設計UI以下(接收消息按鈕僅僅是爲了美觀,代碼中並未實現什麼功能):
這個程序僅用到了一個活動:
//實現OnClickListener接口是一個技巧,這樣在活動中給控件設置監聽的時候直接傳this就行了,代碼會簡潔許多
比較重要的是三個內部類:
//這三個都是線程類,和藍牙相關的會阻塞的操做都封裝在它們中
代碼寫的不是很完善,但徹底可以達到測試的功能
建議把代碼複製下來,再用IDE工具查看,先看框架(outline),再看細節
//看代碼有迷惑的地方,再去看前面的類圖,在樹林中迷了路,此時須要登高四望
//全部的輸出均會打印到logcat中,用System.out過濾一下
//注意:使用藍牙,須要聲明BLUETOOTH權限,若是須要掃描設備或者操做藍牙設置,則還須要BLUETOOTH_ADMIN權限,本實驗兩個權限都須要
1.拿出兩臺設備,安裝好程序,完成配對//配對的過程須要人爲操做,與這個程序沒有關係
2.兩臺設備均打開藍牙(從系統界面或是這個程序的「打開藍牙按鈕都可」)
3.一臺設備經過adb鏈接到電腦,點擊「啓動主機」按鈕
4.在另外一臺設備點擊「啓動從機」按鈕
5.在這臺設備點擊「發送消息」按鈕
6.不出意外的話,logcat的System.out會打印出三條記錄——1,2,3
package com.example.testbluetooth; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Set; import java.util.UUID; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.app.Activity; import android.widget.Button; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; public class MainActivity extends Activity implements OnClickListener { //藍牙 BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); //藍牙狀態 final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {//接收藍牙發現的消息 @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); System.out.println("From mBroadcastReceiver:"+device.getName() + "-" + device.getAddress()); } } }; //消息處理 private static final int MESSAGE_READ = 0; private Handler mHandler = new Handler(){ public void handleMessage(Message msg){ switch(msg.what){ case MESSAGE_READ: byte[] buffer = (byte[])msg.obj;//buffer的大小和裏面數據的多少沒有關係 for(int i=0; i<buffer.length; i++){ if(buffer[i] != 0){ System.out.println(buffer[i]); } } break; } } }; //線程 ConnectedThread mConnectedThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //控件 findViewById(R.id.open).setOnClickListener(this); findViewById(R.id.close).setOnClickListener(this); findViewById(R.id.search).setOnClickListener(this); findViewById(R.id.server).setOnClickListener(this); findViewById(R.id.client).setOnClickListener(this); findViewById(R.id.send).setOnClickListener(this); findViewById(R.id.receive).setOnClickListener(this); findViewById(R.id.paired).setOnClickListener(this); //註冊廣播 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(mBroadcastReceiver, filter); // Don't forget to unregister during onDestroy } // a fictional method in the application //that will initiate the thread for transferring data void manageConnectedSocket(BluetoothSocket socket){//不知何故,可是在此處用Toast會出現Looper問題//Toast.makeText(this, "A socket opened!", Toast.LENGTH_SHORT).show(); //System.out.println("From manageConnectedSocket:"+socket); mConnectedThread = new ConnectedThread(socket); mConnectedThread.start(); } @Override public void onClick(View v) { // TODO Auto-generated method stub //設備支持不支持藍牙和有沒有插藍牙芯片沒有關係,這個是操做系統的事情 //若是系統不支持藍牙,會返回空,經測試,即便沒有藍牙芯片,bluetooth的值可爲非空 //可是若是沒有插藍牙芯片,系統會阻塞住 switch (v.getId()) { case R.id.open: if (!mBluetoothAdapter.isEnabled()) {//若是藍牙沒有打開 mBluetoothAdapter.enable();//這種打開方式能夠跳過系統打開藍牙的界面 } break; case R.id.close: if (mBluetoothAdapter.isEnabled()) {//若是藍牙已經打開 mBluetoothAdapter.disable(); } break; case R.id.search: if (!mBluetoothAdapter.isDiscovering()) {//若是藍牙不處於搜索狀態(即找尋附近藍牙) mBluetoothAdapter.startDiscovery();//Enabling discoverability will automatically enable Bluetooth. } break; case R.id.server: new AcceptThread().start(); ((Button)findViewById(R.id.client)).setVisibility(View.INVISIBLE); break; case R.id.client: for(BluetoothDevice device:pairedDevices){ new ConnectThread(device).start(); ((Button)findViewById(R.id.server)).setVisibility(View.INVISIBLE); } break; case R.id.send: if(mConnectedThread != null){ byte[] bytes = new byte[]{1,2,3}; mConnectedThread.write(bytes); } break; case R.id.receive: break; case R.id.paired: pairedDevices = mBluetoothAdapter.getBondedDevices(); for(BluetoothDevice device:pairedDevices){ System.out.println("From paired:"+device.getName()); } break; default: break; } } 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 = mBluetoothAdapter.listenUsingRfcommWithServiceRecord("blind_nav", UUID.fromString("0c312388-5d09-4f44-b670-5461605f0b1e")); } catch (IOException e) { } mmServerSocket = tmp; } public void run() { BluetoothSocket socket = null; // Keep listening until exception occurs or a socket is returned while (true) { try { //System.out.println("From AcceptThread:"); 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); try { mmServerSocket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } break; } } } /** Will cancel the listening socket, and cause the thread to finish */ public void cancel() { try { mmServerSocket.close(); } catch (IOException e) { } } } 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(UUID.fromString("0c312388-5d09-4f44-b670-5461605f0b1e")); } catch (IOException e) { } mmSocket = tmp; } public void run() { // Cancel discovery because it will slow down the connection mBluetoothAdapter.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, clean up all internal resources, and close the socket */ public void cancel() { try { mmSocket.close(); } catch (IOException e) { } } } 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 //Message msg = new Message(); //msg.what = MESSAGE_READ; 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) { } } } }
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <Button android:id="@+id/open" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="打開藍牙" /> <Button android:id="@+id/close" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="關閉藍牙" /> </LinearLayout> <Button android:id="@+id/search" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="搜索藍牙" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <Button android:id="@+id/server" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="啓動主機" /> <Button android:id="@+id/client" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="啓動從機" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <Button android:id="@+id/send" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="發送消息" /> <Button android:id="@+id/receive" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="接收消息" /> </LinearLayout> <Button android:id="@+id/paired" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="已配對藍牙" /> </LinearLayout>