android Ble通信

一.BLE介紹

BLE是Bluetooth Low Energy的縮寫,又叫藍牙4.0,區別於藍牙3.0和之前的技術。BLE前身是NOKIA開發的Wibree技術,主要用於實現移動智能終端與周邊配件之間的持續連接,是功耗極低的短距離無線通信技術,並且有效傳輸距離被提升到了100米以上,同時只需要一顆鈕釦電池就可以工作數年之久。BLE是在藍牙技術的基礎上發展起來的,既同於藍牙,又區別於傳統藍牙。BLE設備分單模和雙模兩種,雙模簡稱BR,商標爲Bluetooth Smart Ready,單模簡稱BLE或者LE,商標爲Bluetooth Smart。Android是在4.3後才支持BLE,這說明不是所有藍牙手機都支持BLE,而且支持BLE的藍牙手機一般是雙模的。雙模兼容傳統藍牙,可以和傳統藍牙通信,也可以和BLE通信,常用在手機上,android4.3和IOS4.0之後版本都支持BR,也就是雙模設備。單模只能和BR和單模的設備通信,不能和傳統藍牙通信,由於功耗低,待機長,所以常用在手環的智能設備上。


二、基本概念

  1. Generic Access Profile(GAP)
    用來控制設備連接和廣播,GAP使你的設備被其他設備可見,並決定了你的設備是否可以或者怎樣與合同設備進行交互。

  2. Generic Attribute Profile(GATT)
    通過BLE連接,讀寫屬性類數據的Profile通用規範,現在所有的BLE應用Profile都是基於GATT的。

  3. Attribute Protocol (ATT)
    GATT是基於ATTProtocol的,ATT針對BLE設備做了專門的優化,具體就是在傳輸過程中使用盡量少的數據,每個屬性都有一個唯一的UUID,屬性將以characteristics and services的形式傳輸。

  4. Characteristic
    Characteristic可以理解爲一個數據類型,它包括一個value和0至多個對次value的描述(Descriptor)。

  5. Descriptor
    對Characteristic的描述,例如範圍、計量單位等。

  6. Service
    Characteristic的集合。例如一個service叫做“Heart Rate Monitor”,它可能包含多個Characteristics,其中可能包含一個叫做“heart ratemeasurement”的Characteristic。

  7. UUID
    唯一標示符,每個Service,Characteristic,Descriptor,都是由一個UUID定義。


三、Android BLE API

  1. BluetoothManager:
    通過BluetoothManager來獲取BluetoothAdapter。
    BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);

  2. BluetoothAdapter:
    代表了移動設備的本地的藍牙適配器, 通過該藍牙適配器可以對藍牙進行基本操作,一個Android系統只有一個BluetoothAdapter,通過BluetoothManager獲取。
    BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();

  3. BluetoothDevice:
    掃描後發現可連接的設備,獲取已經連接的設備。
    BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(address);

  4. BluetoothGatt:
    繼承BluetoothProfile,通過BluetoothGatt可以連接設備(connect),發現服務(discoverServices),並把相應地屬性返回到BluetoothGattCallback,可以看成藍牙設備從連接到斷開的生命週期。

  5. BluetoothGattCharacteristic:
    相當於一個數據類型,可以看成一個特徵或能力,它包括一個value和0~n個value的描述(BluetoothGattDescriptor)。

  6. BluetoothGattDescriptor:
    描述符,對Characteristic的描述,包括範圍、計量單位等。

  7. BluetoothGattService:
    服務,Characteristic的集合。

  8. BluetoothProfile:
    一個通用的規範,按照這個規範來收發數據。

  9. BluetoothGattCallback:
    已經連接上設備,對設備的某些操作後返回的結果。

    • onConnectionStateChange:Callback indicating when GATT client has connected/disconnected to/from a remote GATT server.
    • onServicesDiscovered:Callback invoked when the list of remote services, characteristics and descriptors for the remote device have been updated, ie new services have been discovered.
    • onCharacteristicChanged:Callback triggered as a result of a remote characteristic notification.
    • onCharacteristicRead:Callback reporting the result of a characteristic read operation.
    • onCharacteristicWrite:Callback indicating the result of a characteristic write operation.

四、操作流程

google GitHub例子連接BluetoothLe
google Developers Bluetooth SDK reference
首先權限不能忘:

uses-permission android:name=”android.permission.ACCESS_COARSE_LOCATION” />
uses-permission android:name=”android.permission.BLUETOOTH” />
uses-permission android:name=”android.permission.BLUETOOTH_ADMIN” />
除了藍牙權限外,如果需要BLE feature則還需要聲明uses-feature:
uses-featureandroid:name=”android.hardware.bluetooth_le”android:required=”true”/>
按時required爲true時,則應用只能在支持BLE的Android設備上安裝運行;required爲false時,Android設備均可正常安裝運行,需要在代碼運行時判斷設備是否支持BLE feature

1.開啓藍牙

在使用藍牙BLE之前,需要確認Android設備是否支持BLE feature(required爲false時),另外要需要確認藍牙是否打開。如果發現不支持BLE,則不能使用BLE相關的功能;如果支持BLE,但是藍牙沒打開,則需要打開藍牙。代碼示例如下:

    // 檢查當前手機是否支持ble 藍牙,如果不支持退出程序
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
            finish();
        }

        //初始化 Bluetooth adapter, 通過藍牙管理器得到一個參考藍牙適配器(API必須在以上android4.3或以上和版本)
        final BluetoothManager bluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);

        // 檢查設備上是否支持藍牙
        mBluetoothAdapter = bluetoothManager.getAdapter();
        if (mBluetoothAdapter == null) {
            Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        //自Android 6.0開始需要打開位置權限纔可以搜索到Ble設備
        //如果 API level 是大於等於 23(Android 6.0) 時判斷是否具有權限,
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
            //判斷是否具有權限
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED){
                //請求權限
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},REQUEST_CODE_ACCESS_COARSE_LOCATION);
            }
        }

        Boolean result =isLocationEnable(this);
        if (result){
            Log.e(TAG, "用戶以打開定位功能===");
        }else {
            setLocationService();
        }

執行完上面的請求權限後,系統會彈出提示框讓用戶選擇是否允許改權限。選擇的結果可以在回到接口中得知:

//執行完上面的請求權限後,系統會彈出提示框讓用戶選擇是否允許改權限。選擇的結果可以在回到接口中得知:
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_CODE_ACCESS_COARSE_LOCATION) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //用戶允許改權限,0表示允許,-1表示拒絕 PERMISSION_GRANTED = 0, PERMISSION_DENIED = -1
                //permission was granted, yay! Do the contacts-related task you need to do.
                //這裏進行授權被允許的處理
                Log.e(TAG, "允許定位權限===");
            } else {
                //permission denied, boo! Disable the functionality that depends on this permission.
                //這裏進行權限被拒絕的處理
                Log.e(TAG, "不允許定位權限===");
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

判斷定位:


    public final boolean isLocationEnable(Context context) {
        LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        boolean networkProvider = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
        boolean gpsProvider = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
        if (networkProvider || gpsProvider) {
            return true;
        }

        return false;
    }

    private void setLocationService() {
        Intent locationIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
        startActivityForResult(locationIntent, REQUEST_CODE_LOCATION_SETTINGS);
    }

執行完上面的請求權限後回調:

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // 用戶沒有開啓藍牙
        if (requestCode == REQUEST_CODE_LOCATION_SETTINGS) {
            if (isLocationEnable(this)) {
                //定位已打開的處理
                Toast.makeText(this, "定位已經打開", Toast.LENGTH_SHORT).show();
            }
            else {
                //定位依然沒有打開的處理
                Toast.makeText(this, "定位沒有打開", Toast.LENGTH_SHORT).show();
            }
        }
        else if (requestCode == REQUEST_ENABLE_BT && resultCode == RESULT_CANCELED) {
            finish();
            return;
        }

        super.onActivityResult(requestCode, resultCode, data);
    }

2、設備搜索

  • BluetoothAdapter.startDiscovery在大多數手機上是可以同時發現經典藍牙和Ble的,但是startDiscovery的回調無法返回Ble的廣播,所以無法通過廣播識別設備,且startDiscovery掃描Ble的效率比StartLeScan低很多。所以在實際應用中,還是StartDiscovery和StartLeScan分開掃,前者掃傳統藍牙,後者掃低功耗藍牙。

  • 由於搜索需要儘量減少功耗,因此在實際使用時需要注意:當找到對應的設備後,立即停止掃描;不要循環搜索設備,爲每次搜索設置適合的時間限制,避免設備不在可用範圍的時候持續不停掃描,消耗電量。

  • 通過調用BluetoothAdapter的 startLeScan() 搜索BLE設備。調用此方法時需要傳入 BluetoothAdapter.LeScanCallback 參數。具體代碼示例如下:

    /**
     * Device scan callback.
     * BluetoothAdapter.LeScanCallback :public static interface
     * onLeScan:Callback reporting an LE device found during a device scan initiated by the        startLeScan(BluetoothAdapter.LeScanCallback) function.
     */
    private final BluetoothAdapter.LeScanCallback mLeScanCallback =
            new BluetoothAdapter.LeScanCallback() {
    
                @Override
                public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mLeDeviceListAdapter.addDevice(device);
                            mLeDeviceListAdapter.notifyDataSetChanged();
                        }
                    });
                }
            };

3、設備通信

兩個設備通過BLE通信,首先需要建立GATT連接,這裏我們講的是Android設備作爲client端,連接GATT Server。連接GATT Server需要調用BluetoothDevice的connectGatt()方法,此函數帶三個參數:Context、autoConnect(boolean)和 BluetoothGattCallback 對象。調用後返回BluetoothGatt對象,它是GATT profile的封裝,通過這個對象,我們就能進行GATT Client端的相關操作。如斷開連接bluetoothGatt.disconnect(),發現服務bluetoothGatt.discoverServices()等等。示例代碼如下:

   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.
    //Connect to GATT Server hosted by this device.
    mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
    Log.d(TAG, "Trying to create a new connection.");


    /**
     * BluetoothGattCallback: public abstract class
     * onConnectionStateChange:Callback indicating when GATT client has connected/disconnected to/from a remote GATT server.
     * onServicesDiscovered:Callback invoked when the list of remote services, characteristics and descriptors for the remote device have been updated, ie new services have been discovered.
     * onCharacteristicChanged:Callback triggered as a result of a remote characteristic notification.
     * onCharacteristicRead:Callback reporting the result of a characteristic read operation.
     * onCharacteristicWrite:Callback indicating the result of a characteristic write operation.
     */
    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback(){

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            Log.i(TAG, "onConnectionStateChange.newState = " + newState);
            String intentAction;
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                intentAction = ACTION_GATT_CONNECTED;
                mConnectionState = STATE_CONNECTED;
                broadcastUpdate(intentAction);
                Log.i(TAG, "Connected to GATT server.");
                // Attempts to discover services after successful connection.
                Log.i(TAG, "Attempting to start service discovery:" +
                        mBluetoothGatt.discoverServices());

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                intentAction = ACTION_GATT_DISCONNECTED;
                mConnectionState = STATE_DISCONNECTED;
                Log.i(TAG, "Disconnected from GATT server.");
                broadcastUpdate(intentAction);
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            Log.i(TAG, "onServicesDiscovered.");

            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
            } else {
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            Log.i(TAG, "onCharacteristicChanged.");

            //broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
            Log.i(TAG, "onCharacteristicRead.status = " + status);

            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }

        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            Log.i(TAG, "onCharacteristicWrite.status = " + status);

            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }
        }
    };

以下爲獲得Gatt後的相關操作對應的響應方法
//notification to onCharacteristicChanged;
bluetoothGatt.setCharacteristicNotification(characteristic, true);

//readCharacteristic to onCharacteristicRead;
bluetoothGatt.readCharacteristic(characteristic);

//writeCharacteristic to onCharacteristicWrite;
bluetoothGatt.wirteCharacteristic(mCurrentcharacteristic);

//connect and disconnect to onConnectionStateChange;
bluetoothGatt.connect();
bluetoothGatt.disconnect();

//readDescriptor to onDescriptorRead;
bluetoothGatt.readDescriptor(descriptor);

//writeDescriptor to onDescriptorWrite;
bluetoothGatt.writeDescriptor(descriptor);

//readRemoteRssi to onReadRemoteRssi;
bluetoothGatt.readRemoteRssi();

//executeReliableWrite to onReliableWriteCompleted;
bluetoothGatt.executeReliableWrite();

//discoverServices to onServicesDiscovered;
bluetoothGatt.discoverServices();

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章