1. 程式人生 > >android 藍芽4.0 ble 低功耗藍芽

android 藍芽4.0 ble 低功耗藍芽

一:概述

這段時間做了藍芽4.0的專案,就是一個藍芽裝置控制手機進行拍照。並且有很多按鍵,不同的按鍵對應到手機上有不同的功能,並且組合起來也有不同的功能。

 低功耗藍芽有中央裝置後周邊裝置的概念手機就是一箇中央裝置,像我這次試用的一個控制器,
   我試過小米體重秤。來測試玩。

 a.GATT 這是藍芽技術聯盟定義的一個協議。

 b.Service 這個是許多或者一個特徵值的集合。

 c.Characteristic 這是特徵值。我們需要使用的資料就是這個。
   我們可以讀取或者在其值在變化的時候會接收到其變化的回撥。

 d.Descriptor 這個是特徵值的描述。最開始我有點不太懂這個東西到底哪裡能用到。結果在設定
   Characteristic 為可以通知的時候才看到,就是輔助Characteristic 的。這個谷歌官方的解釋

這裡寫圖片描述

可以看看我做的一個小demo的效果(一個控制器按鍵後):

二:原始碼解析

現在就通過原始碼講解下我這個小demo:

因為這個是需要藍芽許可權,並且在android 系統4.3後才支援的介面。所以在使用藍芽之前都需要申請許可權和判斷是否可以使用.

 <uses-permission android:name="android.permission.BLUETOOTH"/>
 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
 //這裡的true標示手機如果不支援低功耗藍芽就直接不讓安裝,如果你不想這樣的話,可以寫false
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

這裡是檢查是否支援低功耗藍芽。

 // 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,"不支援藍芽4.0", Toast.LENGTH_SHORT).show(); finish(); }

獲取mBluetoothAdapter 需要用他進行判斷藍芽的開關,和搜尋藍芽裝置。

 // Initializes Bluetooth adapter.
 final BluetoothManager bluetoothManager =
                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
 mBluetoothAdapter = bluetoothManager.getAdapter();

 if(mBluetoothAdapter == null){
            Toast.makeText(this,"獲取失敗!", Toast.LENGTH_SHORT).show();
            finish();
 }

猜這個是幹嘛的,居然猜到是判斷是否開藍芽了。喲西。
如果沒有開藍芽當然就讓使用者開了。會有一個彈窗出來讓使用者選擇是開還是關。

 if (!mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new  Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
 }

如果使用者選擇不開的話,就直接結束當前的activity。

   @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // User chose not to enable Bluetooth.
        if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) {
            finish();
            return;
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

好了。藍芽也開了。那麼開始搜尋藍芽了。mLeScanCallback是一個回撥,當搜尋到一個藍芽的時候就回調他的介面

 private void scanLeDevice(final boolean enable) {
        if (enable) {
            // Stops scanning after a pre-defined scan period.
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScanning = false;
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                }
            }, SCAN_PERIOD);

            mScanning = true;
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        } else {
            mScanning = false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }
    }

喏,回撥就長這個樣子。還行吧。
BluetoothDevice 重寫了equals的方法用裝置的address作為作為判斷的
所以 listDevice.contains(device) 是可以判斷的。好吧。這是java的基礎知識。還是嘮叨兩句。

  private BluetoothAdapter.LeScanCallback mLeScanCallback =
            new BluetoothAdapter.LeScanCallback() {
                @Override
                public void onLeScan(final BluetoothDevice device, int rssi,
                                     byte[] scanRecord) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            if(!listDevice.contains(device)){
                                //不重複新增
                                listDevice.add(device);
                                deviceAdapter.setListDevice(listDevice);
                            }
                        }
                    });
                }
            };

我們找到了藍芽之後幹嘛呢?你覺得還能幹嘛呢?肯定就是開始玩dota啦(想的美)。

好了我們準備開始連線藍芽。

/*mBluetoothLeService是一個android上的後臺服務,不是低功耗藍芽中的服務啊。這個service是從谷歌demo中拷貝的,改動了部分。*/
mBluetoothLeService.connect(device.getAddress());

這個connect中具體的實現,我看見了mBluetoothGatt,就是那個破協議。
然後還看見mGattCallback一個回撥,想想應該是連線成功後的一些回撥。


    public boolean connect(final String address) {
        if (mBluetoothAdapter == null || address == null) {
            Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
            return false;
        }
        if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
                && mBluetoothGatt != null) {
            if (mBluetoothGatt.connect()) {
                mConnectionState = STATE_CONNECTING;
                return true;
            } else {
                return false;
            }
        }

        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
        if (device == null) {
            Log.w(TAG, "Device not found.  Unable to connect.");
            return false;
        }
        // We want to directly connect to the device, so we are setting the autoConnect
        // parameter to false.
        mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
        Log.d(TAG, "Trying to create a new connection.");
        mBluetoothDeviceAddress = address;
        mConnectionState = STATE_CONNECTING;
        return true;
    }

下面的就是連線成功後具體的回撥。各部分幹什麼的在註釋中可以看看。
broadcastUpdate(…)是傳送廣播。

private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            String intentAction;
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                //藍芽連線成功後
                broadcastUpdate(intentAction);

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                //藍芽斷開連線
                broadcastUpdate(intentAction);
            }
        }
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
            //這個就是發現了藍芽4.0的服務
                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
            } else {
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
            //這個是讀取Characteristic,通俗點就是讀取我們想要的資料
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            //這個是我們監聽的某個Characteristic變化了。然後傳送一個廣播
            broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
        }

    };

再看看我們接收廣播的時候做了什麼 這個廣播接收的部分程式碼。因為程式碼貼的太多感覺有點不爽了。

  else if (BluetoothLeService.
                    ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
        //services被發現的狀態
        //在這裡找到要要讀的特徵值或者需要通知的特徵值
        LogServiceAndChara(mBluetoothLeService.getSupportedGattServices());
  }

我們在看看LogServiceAndChara()函式到底幹了啥。(這個函式有點亂。T_T,就貼出部分程式碼吧)
首先我們遍歷所有服務然後找我們想要的特殊服務(-_-我啥都不知道。)這個特殊服務怎麼找?
用uuid,找到了我們想找的服務後,然互就開始找特徵值,也是用uuid識別。

private void LogServiceAndChara(List<BluetoothGattService> gattServices){

 // Loops through available GATT Services.
 for (BluetoothGattService gattService : gattServices) {
     if(gattService.getUuid().toString().equals(SampleGattAttributes.DEVICE_SERVICE)){
         //找到了這個服務
          List<BluetoothGattCharacteristic> gattCharacteristics =
                        gattService.getCharacteristics();
              for(BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics){                         if(gattCharacteristic.getUuid().toString().equals(SampleGattAttributes.DEVICE_CHARACTER)){
                        //找到了對應的服務

                        if((gattCharacteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_READ)>0){
                            if(mNotifyCharacteristic != null){
                                mBluetoothLeService.setCharacteristicNotification(gattCharacteristic,false);
                                mNotifyCharacteristic = null;
                            }
                             mBluetoothLeService.readCharacteristic(gattCharacteristic);
                         }
                        if((gattCharacteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_NOTIFY)>0){
                            mBluetoothLeService.setCharacteristicNotification(gattCharacteristic,true);
                            mNotifyCharacteristic = gattCharacteristic;
                        }
                    }
                }
            }


        }

    }

這兩個分別是特徵值和服務的uuid

public static String DEVICE_CHARACTER = "0000dfb1-0000-1000-8000-00805f9b34fb";
public static String DEVICE_SERVICE = "0000dfb0-0000-1000-8000-00805f9b34fb"; 

找到了我們想要的特徵值後我們就要監聽他了,或者讀取他,我這裡是用的監聽。
這裡有個點我糾結了挺久的地方。

 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
                UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));

我列印了所有特徵值的descriptor的uuid都是一個樣,我覺得應該不是硬體上自定義的。應該是藍芽技術聯盟定義的公用的。

確定了就是他們定義公用的,然後用

descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);

設定這個特徵值為可以為監聽狀態(就是可以變化的時我能收到通知)

    public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
                                              boolean enabled) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
        //關於CLIENT_CHARACTERISTIC_CONFIG這個descriptor的解釋
            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
                    UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            mBluetoothGatt.writeDescriptor(descriptor);
    }

在接收到變化或讀取到後都能在這裡接收到(這個是來自廣播中的方法)

   else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
                //接收到資料的狀態
                str += intent.getStringExtra(BluetoothLeService.EXTRA_DATA);
                str += "\n\r";
                tv.setText(str);
  }

原始碼下載

加個好友共同學習(不是公眾號):

這裡寫圖片描述

因為小弟水平有限,如果有寫的有問題,希望指出。