1. 程式人生 > >Android藍芽開發—BLE(低功耗)藍芽詳細開發流程

Android藍芽開發—BLE(低功耗)藍芽詳細開發流程

        Android藍芽開發前,首先要區分是經典藍芽開發還是BLE(低功耗)藍芽開發,它們的開發是有區別的,如果還分不清經典藍芽和BLE(低功耗)藍芽的小夥伴,可以先看Android藍芽開發—經典藍芽和BLE(低功耗)藍芽的區別

注意:藍芽4.0只有android4.3或4.3以上才支援

簡單介紹

基本概念

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。 

2、BluetoothAdapter

代表了移動裝置的本地的藍芽介面卡, 通過該藍芽介面卡可以對藍芽進行基本操作,一個Android系統只有一個BluetoothAdapter,通過BluetoothManager獲取。 

3、BluetoothDevice

掃描後發現可連線的裝置,獲取已經連線的裝置,通過它可以獲取到BluetoothGatt。

4、BluetoothGatt

繼承BluetoothProfile,通過BluetoothGatt可以連線裝置(connect),發現服務(discoverServices),並把相應地屬性返回到BluetoothGattCallback,可以看成藍芽裝置從連線到斷開的生命週期。

5、BluetoothGattService

服務,Characteristic的集合。

6、BluetoothGattCharacteristic

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

7、BluetoothGattDescriptor

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

8、BluetoothProfile

一個通用的規範,按照這個規範來收發資料。 

9、BluetoothGattCallback

已經連線上裝置,對裝置的某些操作後返回的結果。

簡單總結:當我們掃描後發現多個裝置BluetoothDevice,每個裝置下會有很多服務BluetoothGattService,這些服務通過service_uuid(唯一識別符號)來區分,每個服務下又會有很多特徵BluetoothGattCharacteristic,這些特徵通過uuid來區分的,它是手機與BLE終端裝置交換資料的關鍵。而BluetoothGatt可以看成手機與BLE終端裝置建立通訊的一個管道,只有有了這個管道,才有了通訊的前提。

開發流程

開發的流程和經典藍芽是一樣的,都需要以下這幾個步驟。

  • 開啟藍芽
  • 掃描藍芽
  • 繫結藍芽
  • 連線藍芽
  • 通訊

開啟藍芽

1.獲取BluetoothAdapter物件

這裡有兩種方法可以獲取到BluetoothAdapter物件(區別在於版本不同而已,沒有太大的區別)

第一種通過BluetoothManager物件獲取

final BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();

第二種通過單例獲取

mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

2.判斷是否有藍芽功能模組

    if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            //有藍芽功能模組
        }

3.判斷裝置是否支援藍芽

/**
 * 裝置是否支援藍芽  true為支援
 * @return
 */
public boolean isSupportBlue(){
    return mBluetoothAdapter != null;
}

4.判斷藍芽是否開啟

/**
 * 藍芽是否開啟   true為開啟
 * @return
 */
public boolean isBlueEnable(){
    return isSupportBlue() && mBluetoothAdapter.isEnabled();
}

5.開啟藍芽

  • 非同步自動開啟藍芽
/**
 * 自動開啟藍芽(非同步:藍芽不會立刻就處於開啟狀態)
 * 這個方法開啟藍芽不會彈出提示
 */
public void openBlueAsyn(){
    if (isSupportBlue()) {
        mBluetoothAdapter.enable();
    }
}
  • 同步提示開啟藍芽
/**
 * 自動開啟藍芽(同步)
 * 這個方法開啟藍芽會彈出提示
 * 需要在onActivityResult 方法中判斷resultCode == RESULT_OK  true為成功
 */
public void openBlueSync(Activity activity, int requestCode){
    Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    activity.startActivityForResult(intent, requestCode);
}

6.許可權處理

  • 處理6.0以下版本的許可權

    在AndroidManifest裡面新增許可權

<!-- 使用藍芽的許可權 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<!-- 掃描藍芽裝置或者操作藍芽設定 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
  • 處理6.0及以上版本的許可權

    (1)在AndroidManifest裡面新增許可權

<!-- 使用藍芽的許可權 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<!-- 掃描藍芽裝置或者操作藍芽設定 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!--模糊定位許可權,僅作用於6.0+-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!--精準定位許可權,僅作用於6.0+-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    (2)動態檢查許可權

/**
 * 檢查許可權
 */
private void checkPermissions() {
    String[] permissions = {Manifest.permission.ACCESS_FINE_LOCATION};
    List<String> permissionDeniedList = new ArrayList<>();
    for (String permission : permissions) {
        int permissionCheck = ContextCompat.checkSelfPermission(this, permission);
        if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
            onPermissionGranted(permission);
        } else {
            permissionDeniedList.add(permission);
        }
    }
    if (!permissionDeniedList.isEmpty()) {
        String[] deniedPermissions = permissionDeniedList.toArray(new String[permissionDeniedList.size()]);
        ActivityCompat.requestPermissions(this, deniedPermissions, REQUEST_CODE_PERMISSION_LOCATION);
    }
}

/**
 * 許可權回撥
 * @param requestCode
 * @param permissions
 * @param grantResults
 */
@Override
public final void onRequestPermissionsResult(int requestCode,
                                             @NonNull String[] permissions,
                                             @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case REQUEST_CODE_PERMISSION_LOCATION:
            if (grantResults.length > 0) {
                for (int i = 0; i < grantResults.length; i++) {
                    if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                        onPermissionGranted(permissions[i]);
                    }
                }
            }
            break;
    }
}

    (3)開啟GPS

/**
 * 開啟GPS
 * @param permission
 */
private void onPermissionGranted(String permission) {
    switch (permission) {
        case Manifest.permission.ACCESS_FINE_LOCATION:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !checkGPSIsOpen()) {
                new AlertDialog.Builder(this)
                        .setTitle("提示")
                        .setMessage("當前手機掃描藍芽需要開啟定位功能。")
                        .setNegativeButton("取消",
                                new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        finish();
                                    }
                                })
                        .setPositiveButton("前往設定",
                                new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                                        startActivityForResult(intent, REQUEST_CODE_OPEN_GPS);
                                    }
                                })

                        .setCancelable(false)
                        .show();
            } else {
                //GPS已經開啟了
            }
            break;
    }
}

    (4)檢查GPS是否開啟

/**
 * 檢查GPS是否開啟
 * @return
 */
private boolean checkGPSIsOpen() {
    LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
    if (locationManager == null)
        return false;
    return locationManager.isProviderEnabled(android.location.LocationManager.GPS_PROVIDER);
}

掃描藍芽

1.掃描周圍藍芽裝置(配對上的裝置有可能掃描不出來)

呼叫BluetoothAdapter的startLeScan()方法來實現開始搜尋。此方法時需要傳入 BluetoothAdapter.LeScanCallback引數。搜尋到的藍芽裝置都會通過這個回撥返回。

    private BluetoothAdapter mBluetoothAdapter;
    private boolean isScanning;//是否正在搜尋
    private Handler mHandler;
    //15秒搜尋時間
    private static final long SCAN_PERIOD = 15000;

    private void scanLeDevice(final boolean enable) {
        if (enable) {//true
            //15秒後停止搜尋
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    isScanning = false;
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                }
            }, SCAN_PERIOD);
            isScanning = true;
            mBluetoothAdapter.startLeScan(mLeScanCallback); //開始搜尋
        } else {//false
            isScanning = false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);//停止搜尋
        }
    }

2.BluetoothAdapter.LeScanCallback回撥

    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() {
                    //在這裡可以把搜尋到的裝置儲存起來
                    //device.getName();獲取藍芽裝置名字
                    //device.getAddress();獲取藍芽裝置mac地址
                    //這裡的rssi即訊號強度,即手機與裝置之間的訊號強度。
                }
            });
        }

    };

3.停止掃描裝置

 掃描裝置是一個耗能的操作,在我們連線藍芽之前如果掃描沒有結束,應該先停止掃描。

mBluetoothAdapter.stopLeScan(mLeScanCallback);//停止搜尋

繫結藍芽

繫結藍芽是在我們呼叫連線方法時去繫結的,不用我們手動的去調方法繫結。

連線藍芽

1.呼叫BluetoothDevice的connectGatt()方法,此函式帶三個引數:Context、autoConnect(boolean)和 BluetoothGattCallback 物件。呼叫後返回BluetoothGatt物件

//這個方法需要三個引數:一個Context物件,自動連線(boolean值,表示只要BLE裝置可用是否自動連線它),和BluetoothGattCallback呼叫。
BluetoothGatt mBluetoothGatt = device.connectGatt(this, false, mBluetoothGattCallback);

2.BluetoothGattCallback的回撥

BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {
        @Override
        public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
            super.onPhyUpdate(gatt, txPhy, rxPhy, status);
        }

        @Override
        public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
            super.onPhyRead(gatt, txPhy, rxPhy, status);
        }

        //當連線狀態發生改變
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
        }

        //發現新服務,即呼叫了mBluetoothGatt.discoverServices()後,返回的資料
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
        }

        //呼叫mBluetoothGatt.readCharacteristic(characteristic)讀取資料回撥,在這裡面接收資料
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
        }

        //傳送資料後的回撥
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
        }

        @Override
        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {//descriptor讀
            super.onDescriptorRead(gatt, descriptor, status);
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {//descriptor寫
            super.onDescriptorWrite(gatt, descriptor, status);
        }

        @Override
        public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
            super.onReliableWriteCompleted(gatt, status);
        }

        //呼叫mBluetoothGatt.readRemoteRssi()時的回撥,rssi即訊號強度
        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {//讀Rssi
            super.onReadRemoteRssi(gatt, rssi, status);
        }

        @Override
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            super.onMtuChanged(gatt, mtu, status);
        }
    };

3.斷開連線

mBluetoothGatt.disconnect(); //主動斷開連線

通訊

1.寫入資料,通過mBluetoothGatt.writeCharacteristic(characteristic)

/**
 * 向藍芽傳送資料
 */
public void dataSend(){
    //byte[] send={(byte) 0xaa,0x01,0x01,(byte)0x81,(byte) 0xff};
    byte[] send = new byte[20];
    send = hexStringToBytes(et_send.getText().toString());
    byte[] sendData=new byte[send.length+2];
    sendData[0]=(byte) 0xaa;
    sendData[sendData.length-1]=(byte) 0xff;
    for(int i=1;i<sendData.length-1;i++){
        sendData[i]=send[i-1];
    }
    Log.e("dataSend", bytesToHexString(sendData));
    Log.e("dataSend", linkLossService +"");
    mCharacteristic.setValue(sendData);
    boolean status = mBluetoothGatt.writeCharacteristic(mCharacteristic);
    Log.e("dataSend", status+"");
}

注意:寫入的資料長度是有限制的,如果要改變這個限制,可以通過MTU來改變。

2.讀取資料,通過mBluetoothGatt.readCharacteristic(characteristic),在BluetoothGattCallback 回撥方法onCharacteristicRead中獲取到資料。

//讀取資料時呼叫這個方法,資料會在回撥介面中(BluetoothGattCallback )獲取到
mBluetoothGatt.readCharacteristic(characteristic)


BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {     

        //呼叫mBluetoothGatt.readCharacteristic(characteristic)讀取資料回撥,在這裡面接收資料
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
            //這裡面就是資料
            characteristic.getValue();
        }
}

總結

首先,我們要判斷手機是否支援BLE,並且獲得各種許可權,才能讓我們之後的程式能正常執行。 
然後,我們去搜索BLE裝置,得到它的MAC地址。 
其次,我們通過這個MAC地址去連線,連線成功後,去遍歷得到Characteristic的uuid。 
在我們需要傳送資料的時候,通過這個uuid找到Characteristic,去設定其值,最後通過writeCharacteristic(characteristic)方法傳送資料。 
如果我們想知道手機與BLE裝置的距離,則可以通過readRemoteRssi()去得到rssi值,通過這個訊號強度,就可以換算得到距離。 
只要我們連線上,我們就可以用BluetoothGatt的各種方法進行資料的讀取等操作。

以上就是BLE(低功耗)藍芽的開發流程和部分程式碼,後期會提供demo下載。若有不當之處,請留言討論,一起學習進步。