原文地址:html
Android 藍牙開發(一)藍牙通訊 CSDNjava
Android 藍牙開發(一)藍牙通訊 簡書android
開發環境:安全
jdk1.8服務器
Eclipse Luna Service Release 1 (4.4.1)異步
運行環境:socket
華爲榮耀6(Android4.4)、華爲p9(Android7.0)ide
實現功能:函數
Android 藍牙開發 (開關藍牙、搜索設備、藍牙配對、鏈接、通訊、斷開鏈接等)。ui
代碼包裏面,有兩個部分,一個是源碼,一個是V7支持包。
隨着可穿戴設備的流行,研究藍牙是必不可少的一門技術了。
總結了下藍牙開發使用的一些東西分享一下。
藍牙權限
首先須要AndroidManifest.xml文件中添加操做藍牙的權限。
<uses-permissionandroid:name="Android.permission.BLUETOOTH" /> //容許程序鏈接到已配對的藍牙設備。 <uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" /> //容許程序發現和配對藍牙設備。
BluetoothAdapter
操做藍牙主要用到的類 BluetoothAdapter類,使用時導包
import android.bluetooth.BluetoothAdapter;
源碼具體位置frameworks/base/core/Java/android/bluetooth/BluetoothAdapter.java
BluetoothAdapter 表明本地設備的藍牙適配器。該BluetoothAdapter能夠執行基本的藍牙任務,例如啓
動設備發現,查詢配對的設備列表,使用已知的MAC地址實例化一個BluetoothDevice類,並建立一個
BluetoothServerSocket監聽來自其餘設備的鏈接請求。
獲取藍牙適配器
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
開啓藍牙
if(!mBluetoothAdapter.isEnabled()){ //彈出對話框提示用戶是後打開 Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enabler, REQUEST_ENABLE); //不作提示,直接打開,不建議用下面的方法,有的手機會有問題。 // mBluetoothAdapter.enable(); }
獲取本地藍牙信息
//獲取本機藍牙名稱 String name = mBluetoothAdapter.getName(); //獲取本機藍牙地址 String address = mBluetoothAdapter.getAddress(); Log.d(TAG,"bluetooth name ="+name+" address ="+address); //獲取已配對藍牙設備 Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices(); Log.d(TAG, "bonded device size ="+devices.size()); for(BluetoothDevice bonddevice:devices){ Log.d(TAG, "bonded device name ="+bonddevice.getName()+" address"+bonddevice.getAddress()); }
搜索設備
mBluetoothAdapter.startDiscovery();
中止搜索
mBluetoothAdapter.cancelDiscovery();
搜索藍牙設備,該過程是異步的,經過下面註冊廣播接受者,能夠監聽是否搜到設備。
IntentFilter filter = new IntentFilter(); //發現設備 filter.addAction(BluetoothDevice.ACTION_FOUND); //設備鏈接狀態改變 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); //藍牙設備狀態改變 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); registerReceiver(mBluetoothReceiver, filter);
監聽掃描結果
經過廣播接收者查看掃描到的藍牙設備,每掃描到一個設備,系統都會發送此廣播(BluetoothDevice.ACTION_FOUNDE)。其中參數intent能夠獲取藍牙設備BluetoothDevice。
該demo中是鏈接指定名稱的藍牙設備,BLUETOOTH_NAME爲"Galaxy Nexus",若是掃描不到,記得改這個藍牙名稱。
private BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Log.d(TAG,"mBluetoothReceiver action ="+action); if(BluetoothDevice.ACTION_FOUND.equals(action)){//每掃描到一個設備,系統都會發送此廣播。 //獲取藍牙設備 BluetoothDevice scanDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if(scanDevice == null || scanDevice.getName() == null) return; Log.d(TAG, "name="+scanDevice.getName()+"address="+scanDevice.getAddress()); //藍牙設備名稱 String name = scanDevice.getName(); if(name != null && name.equals(BLUETOOTH_NAME)){ mBluetoothAdapter.cancelDiscovery(); //取消掃描 mProgressDialog.setTitle(getResources().getString(R.string.progress_connecting)); //鏈接到設備。 mBlthChatUtil.connect(scanDevice); } }else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){ } } };
設置藍牙可見性
有時候掃描不到某設備,這是由於該設備對外不可見或者距離遠,須要設備該藍牙可見,這樣該才能被搜索到。
可見時間默認值爲120s,最多可設置300。
if (mBluetoothAdapter.isEnabled()) { if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { Intent discoverableIntent = new Intent( BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra( BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120); startActivity(discoverableIntent); } }
android 藍牙之間能夠經過SDP協議創建鏈接進行通訊,通訊方式相似於日常使用socket。
首先建立BluetoothServerSocket ,BluetoothAdapter中提供了兩種建立BluetoothServerSocket 方式,以下圖所示爲建立安全的RFCOMM Bluetooth socket,該鏈接是安全的須要進行配對。而經過listenUsingInsecureRfcommWithServiceRecord建立的RFCOMM Bluetooth socket是不安全的,鏈接時不須要進行配對。
其中的uuid須要服務器端和客戶端進行統一。
private class AcceptThread extends Thread { // 本地服務器套接字 private final BluetoothServerSocket mServerSocket; public AcceptThread() { BluetoothServerSocket tmp = null; // 建立一個新的偵聽服務器套接字 try { tmp = mAdapter.listenUsingRfcommWithServiceRecord( SERVICE_NAME, SERVICE_UUID); //tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(SERVICE_NAME, SERVICE_UUID); } catch (IOException e) { Log.e(TAG, "listen() failed", e); } mServerSocket = tmp; } public void run() { BluetoothSocket socket = null; // 循環,直到鏈接成功 while (mState != STATE_CONNECTED) { try { // 這是一個阻塞調用 返回成功的鏈接 // mServerSocket.close()在另外一個線程中調用,能夠停止該阻塞 socket = mServerSocket.accept(); } catch (IOException e) { Log.e(TAG, "accept() failed", e); break; } // 若是鏈接被接受 if (socket != null) { synchronized (BluetoothChatUtil.this) { switch (mState) { case STATE_LISTEN: case STATE_CONNECTING: // 正常狀況。啓動ConnectedThread。 connected(socket, socket.getRemoteDevice()); break; case STATE_NONE: case STATE_CONNECTED: // 沒有準備或已鏈接。新鏈接終止。 try { socket.close(); } catch (IOException e) { Log.e(TAG, "Could not close unwanted socket", e); } break; } } } } if (D) Log.i(TAG, "END mAcceptThread"); } public void cancel() { if (D) Log.d(TAG, "cancel " + this); try { mServerSocket.close(); } catch (IOException e) { Log.e(TAG, "close() of server failed", e); } } }
mServerSocket經過accept()等待客戶端的鏈接(阻塞),直到鏈接成功或失敗。
客戶端主要用來建立RFCOMM socket,並鏈接服務端。
先掃描周圍的藍牙設備,若是掃描到指定設備則進行鏈接。mBlthChatUtil.connect(scanDevice)鏈接到設備,
鏈接過程主要在ConnectThread線程中進行,先建立socket,方式有兩種,
以下代碼中是安全的(createRfcommSocketToServiceRecord)。另外一種不安全鏈接對應的函數是createInsecureRfcommSocketToServiceRecord。
private class ConnectThread extends Thread { private BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; public ConnectThread(BluetoothDevice device) { mmDevice = device; BluetoothSocket tmp = null; // 獲得一個bluetoothsocket try { mmSocket = device.createRfcommSocketToServiceRecord (SERVICE_UUID); } catch (IOException e) { Log.e(TAG, "create() failed", e); mmSocket = null; } } public void run() { Log.i(TAG, "BEGIN mConnectThread"); try { // socket 鏈接,該調用會阻塞,直到鏈接成功或失敗 mmSocket.connect(); } catch (IOException e) { connectionFailed(); try {//關閉這個socket mmSocket.close(); } catch (IOException e2) { e2.printStackTrace(); } return; } // 啓動鏈接線程 connected(mmSocket, mmDevice); } public void cancel() { try { mmSocket.close(); } catch (IOException e) { Log.e(TAG, "close() of connect socket failed", e); } } }
接着客戶端socket主動鏈接服務端。鏈接過程當中會自動進行配對,須要雙方贊成才能夠鏈接成功。
客戶端與服務端鏈接成功後都會調用connected(mmSocket, mmDevice),建立一個ConnectedThread線程()。
該線程主要用來接收和發送數據。客戶端和服務端處理方式同樣。該線程經過socket得到輸入輸出流。
private InputStream mmInStream = socket.getInputStream();
private OutputStream mmOutStream =socket.getOutputStream();
發送數據
public void write(byte[] buffer) { try { mmOutStream.write(buffer); // 分享發送的信息到Activity mHandler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer) .sendToTarget(); } catch (IOException e) { Log.e(TAG, "Exception during write", e); } }
接收數據
線程循環進行接收數據。
public void run() { // 監聽輸入流 while (true) { try { byte[] buffer = new byte[1024]; // 讀取輸入流 int bytes = mmInStream.read(buffer); // 發送得到的字節的ui activity Message msg = mHandler.obtainMessage(MESSAGE_READ); Bundle bundle = new Bundle(); bundle.putByteArray(READ_MSG, buffer); msg.setData(bundle); mHandler.sendMessage(msg); } catch (IOException e) { Log.e(TAG, "disconnected", e); connectionLost(); break; } } }
一、運行,右鍵項目:Run as -》Android Application (備註:Eclipse須要配置Android開發環境)
二、運行效果以下:
客戶端
服務端