1. 程式人生 > >Android BLE淺析

Android BLE淺析

這篇部落格想寫很久了,只是之前一直提不起勁,剛好最近還是一如既往的閒得蛋疼,那就寫寫吧,免得自己都忘了!   
 剛進公司的時候,做的就是BLE的專案,隨著這個專案的不了了之,我也忘了這事。   
 BLE的全名是 Bluetooth Low Energy 就是低功耗藍芽的意思,支援 API18(Android 4.3)及以上的裝置,本文將說明如何通過BLE實現資料的收發

     如果你的app只是用BLE的話,在manifest.xml里加以下許可權就好了:

<uses-featureandroid:name="android.hardware.bluetooth_le"android:required
="true"/>

如果想讓App在不支援 BLE(即API<18)的裝置上也能執行,那麼應該將上面許可權中的required設成false。同時,加上下面的程式碼,
// Use this check to determine whether BLE is supported on the device. Then// you can selectively disable BLE-related features.if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){Toast.makeText
(this, R.string.ble_not_supported,Toast.LENGTH_SHORT).show();     finish();}
程式碼的作用就是當裝置不支援BLE時,彈出Toast告訴使用者新增完許可權後,我們便可以敲程式碼了先把BLE給開啟,這裡涉及到BluetoothAdapter和BluetoothManger物件,一個支援BLE的android裝置有且只有一個BluetoothAdapter,並通過BluetoothManger來獲取,上程式碼
// Initializes Bluetooth adapter.
BluetoothAdapter mBluetoothAdapter;
finalBluetoothManager bluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter();
然後是開啟BLE:
privateBluetoothAdapter mBluetoothAdapter;...// Ensures Bluetooth is available on the device and it is enabled. If not,// displays a dialog requesting user permission to enable Bluetooth.if(mBluetoothAdapter ==null||!mBluetoothAdapter.isEnabled()){Intent enableBtIntent =newIntent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);//這時會彈出對話方塊,詢問使用者是否開啟BLE}
搜尋周邊的藍芽裝置:
/**
 * Activity for scanning and displaying available BLE devices.
 */publicclassDeviceScanActivityextendsListActivity{privateBluetoothAdapter mBluetoothAdapter;privateboolean mScanning;privateHandler mHandler;// Stops scanning after 10 seconds.privatestaticfinallong SCAN_PERIOD =10000;...privatevoid scanLeDevice(finalboolean enable){if(enable){// Stops scanning after a pre-defined scan period.
            mHandler.postDelayed(newRunnable(){@Overridepublicvoid run(){
                    mScanning =false;
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);//mLeScanCallback是一個搜尋裝置的回撥引數,待會貼它的程式碼}}, SCAN_PERIOD);

            mScanning =true;
            mBluetoothAdapter.startLeScan(mLeScanCallback);//開始搜尋}else{
            mScanning =false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);}...//}...//這裡的...是設定ListActivity的Adapter的程式碼,在mLeScanCallback的程式碼中會提到}
LeScanCallback是裝置搜尋周邊其他藍芽裝置時,回撥的介面物件
privateLeDeviceListAdapter mLeDeviceListAdapter;...// Device scan callback.privateBluetoothAdapter.LeScanCallback mLeScanCallback =newBluetoothAdapter.LeScanCallback(){@Overridepublicvoid onLeScan(finalBluetoothDevice device,int rssi,byte[] scanRecord){
        runOnUiThread(newRunnable(){@Overridepublicvoid run(){
               mLeDeviceListAdapter.addDevice(device);
               mLeDeviceListAdapter.notifyDataSetChanged();}});}};
//////////////////////////////////////////////////////////////////////////////////
public class LeDeviceListAdapter extends BaseAdapter{
     
     private List<BluetoothDevice> devices;
     
     public LeDeviceListAdapter(){
          devices = new ArrayList<>();
          .....
     }

     public void addDevice(BluetoothDevice mdevice){
          if(!devices.contains(mdevice)){
           
           devices.add(mdevice);
     }

     }
.......
      @Override
      public View getView(int position ,View view ,ViewGroup viewGroup){
           BluetoothDevice bd = devices.get(position);
           String deviceName = bd.getName();//獲取周邊裝置的名字,然後就可以在ListActivity上顯示了
    
}

 
.....................//至於viewholder的程式碼就不寫了

}
接下來就到了收發資料了
先來了解一下GATT是什麼鬼,GATT是通過藍芽收發資料的一種協議,包含Characteristic、DescriptorService三個屬性值
BLE分為三部分Service、Characteristic、Descriptor,這三部分都由UUID作為唯一標示符。一個藍芽4.0的終端可以
包含多個Service,一個Service可以包含多個Characteristic,一個Characteristic包含一個Value和多個Descriptor
,一個Descriptor包含一個Value
先連GATT Server
mBluetoothGatt = device.connectGatt(Context context,boolean autoConnect, BluetoothGattCallback mGattCallback);

BluetoothGattCallback主要是監控藍芽的連線狀態,併發出廣播,下面一大段都是關於廣播的處理
BluetoothGattCallback回撥的實現:
// A service that interacts with the BLE device via the Android BLE API.publicclassBluetoothLeServiceextendsService{privatefinalstaticString TAG =BluetoothLeService.class.getSimpleName();privateBluetoothManager mBluetoothManager;privateBluetoothAdapter mBluetoothAdapter;privateString mBluetoothDeviceAddress;privateBluetoothGatt mBluetoothGatt;privateint mConnectionState = STATE_DISCONNECTED;privatestaticfinalint STATE_DISCONNECTED =0;privatestaticfinalint STATE_CONNECTING =1;privatestaticfinalint STATE_CONNECTED =2;publicfinalstaticString ACTION_GATT_CONNECTED ="com.example.bluetooth.le.ACTION_GATT_CONNECTED";publicfinalstaticString ACTION_GATT_DISCONNECTED ="com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";publicfinalstaticString ACTION_GATT_SERVICES_DISCOVERED ="com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";publicfinalstaticString ACTION_DATA_AVAILABLE ="com.example.bluetooth.le.ACTION_DATA_AVAILABLE";publicfinalstaticString EXTRA_DATA ="com.example.bluetooth.le.EXTRA_DATA";publicfinalstatic UUID UUID_HEART_RATE_MEASUREMENT =
            UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);// Various callback methods defined by the BLE API.privatefinalBluetoothGattCallback mGattCallback =newBluetoothGattCallback(){@Overridepublicvoid onConnectionStateChange(BluetoothGatt gatt,int status,int newState){String intentAction;if(newState ==BluetoothProfile.STATE_CONNECTED){
                intentAction = ACTION_GATT_CONNECTED;
                mConnectionState = STATE_CONNECTED;
                broadcastUpdate(intentAction);Log.i(TAG,"Connected to GATT server.");Log.i(TAG,"Attempting to start service discovery:"+
                        mBluetoothGatt.discoverServices());}elseif(newState ==BluetoothProfile.STATE_DISCONNECTED){
                intentAction = ACTION_GATT_DISCONNECTED;
                mConnectionState = STATE_DISCONNECTED;Log.i(TAG,"Disconnected from GATT server.");
                broadcastUpdate(intentAction);}}@Override// New services discoveredpublicvoid onServicesDiscovered(BluetoothGatt gatt,int status){if(status ==BluetoothGatt.GATT_SUCCESS){
                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);}else{Log.w(TAG,"onServicesDiscovered received: "+ status);}}@Override// Result of a characteristic read operationpublicvoid onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic,int status){if(status ==BluetoothGatt.GATT_SUCCESS){
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);}}...};...}
好長的程式碼,其實就是監聽藍芽的連線狀態,並向系統發出廣播
broadcastUpdate()的程式碼:
privatevoid broadcastUpdate(finalString action){finalIntent intent =newIntent(action);
    sendBroadcast(intent);}privatevoid broadcastUpdate(finalString action,finalBluetoothGattCharacteristic characteristic){finalIntent intent =newIntent(action);// This is special handling for the Heart Rate Measurement profile. Data// parsing is carried out as per profile specifications.if(UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())){int flag = characteristic.getProperties();int format =-1;if((flag &0x01)!=0){
            format =BluetoothGattCharacteristic.FORMAT_UINT16;Log.d(TAG,"Heart rate format UINT16.");}else{
            format =BluetoothGattCharacteristic.FORMAT_UINT8;Log.d(TAG,"Heart rate format UINT8.");}finalint heartRate = characteristic.getIntValue(format,1);Log.d(TAG,String.format("Received heart rate: %d", heartRate));
        intent.putExtra(EXTRA_DATA,String.valueOf(heartRate));}else{// For all other profiles, writes the data formatted in HEX.finalbyte[] data = characteristic.getValue();if(data !=null&& data.length >0){finalStringBuilder stringBuilder =newStringBuilder(data.length);for(byte byteChar : data)
                stringBuilder.append(String.format("%02X ", byteChar));
            intent.putExtra(EXTRA_DATA,newString(data)+"\n"+
                    stringBuilder.toString());}}
    sendBroadcast(intent);}
有廣播發出,當然就要對廣播進行處理了
// Handles various events fired by the Service.// ACTION_GATT_CONNECTED: connected to a GATT server.// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.// ACTION_DATA_AVAILABLE: received data from the device. This can be a// result of read or notification operations.privatefinalBroadcastReceiver mGattUpdateReceiver =newBroadcastReceiver(){@Overridepublicvoid onReceive(Context context,Intent intent){finalString action = intent.getAction();if(BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)){
            mConnected =true;
            updateConnectionState(R.string.connected);
            invalidateOptionsMenu();//對選單按鈕的狀態進行更新}elseif(BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)){
            mConnected =false;
            updateConnectionState(R.string.disconnected);
            invalidateOptionsMenu();
            clearUI();}elseif(BluetoothLeService.
                ACTION_GATT_SERVICES_DISCOVERED.equals(action)){// Show all the supported services and characteristics on the// user interface.
            displayGattServices(mBluetoothLeService.getSupportedGattServices());}elseif(BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)){
            displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));}}};

扯了一大堆,終於可以發資料了!!!官網沒有介紹,只能手寫了.......
<span style="font-family:Arial;">public boolean sendCharacteristic(){</span>
<span style="font-family:Arial;">
String kUUIDWriteCommand = "0000FEC7-0000-1000-8000-00805F9B34FB";//"0000FF01-0000-1000-8000-00805F9B34FB";
     
    
     UUID writeUUID = UUID.fromString(kUUIDWriteCommand);
     
     
     BluetoothGattCharacteristic btWriteGattChar = gattService.getCharacteristic(writeUUID);
     
 		
		   			
      boolean right = divideFrameBleSendData(data, btWriteGattChar);</span>
<span style="font-family:Arial;">
</span>
<span style="font-family:Arial;">      
}</span>

傳送給藍芽的資料要分包發,太長的資料一次性是釋出過去的,
<span style="font-family:Arial;"></span>
private static boolean  divideFrameBleSendData(byte[] data, BluetoothGattCharacteristic btWriteGattChar){
boolean right = false;
 int tmpLen = data.length;
 int start = 0;
 int end = 0;
 while (tmpLen > 0){
 byte[] sendData = new byte[21];
 if (tmpLen >= 20){
 end += 20;
 sendData = Arrays.copyOfRange(data, start, end);
 start += 20;
 tmpLen -= 20;
 }
 else{
 end += tmpLen;
 sendData = Arrays.copyOfRange(data, start, end);
 tmpLen = 0;
 }
 
 right = btWriteGattChar.setValue(sendData);//發資料
   if (!right){
          MyUtil.writeLog("btWriteGattChar.setValue false! data=" + MyUtil.binToHex(data, 0, data.length));
  return false;
  }
 
  right = mBluetoothGatt.writeCharacteristic(btWriteGattChar);
  if (!right) return right;

 } 
 return right;
}
這樣,就把資料給發過去了(引數中的data就是需要傳送的資料)
然後就是收資料....