Android藍牙周邊設(shè)備開發(fā)

本文只講述實際開發(fā)中的最基本的概念、用法及代碼,不過多深入概念及源碼。

什么是周邊設(shè)備

  • BLE(藍牙4.0+) 有兩種狀態(tài)模式:中心(center)及peripheral(周邊)。
  • 大多數(shù)中心設(shè)備的扮演者是手機、電腦等能主動去連接別人的設(shè)備,而大多數(shù)周邊設(shè)備就等著這些中心設(shè)備連接,如手環(huán)、血糖儀等。
  • Android從 Lolipop 開始支持了BLE Peripheral(周邊設(shè)備)開發(fā)。
  • 如果你希望使Android設(shè)備開啟為周邊設(shè)備模式,請往下看。

配置

AndroidManifest.xml 中 添加以下權(quán)限:

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <!-- 6.0之后藍牙還需要地理位置權(quán)限 -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <!-- 只掃描ble設(shè)備 -->
    <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true" />

開啟藍牙

// 是否支持ble
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(mContext, "hasSystemFeature == false", Toast.LENGTH_SHORT).show();
    return false;
}
// 是否能獲取到藍牙服務(wù)
mBluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
    Toast.makeText(mContext, "mBluetoothManager == null", Toast.LENGTH_LONG).show();
    return false;
}
// 獲取藍牙適配器
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
    Toast.makeText(mContext, "BluetoothAdapter == null", Toast.LENGTH_LONG).show();
    return false;
}
// 藍牙是否打開
if (!mBluetoothAdapter.isEnabled()) {
    Toast.makeText(mContext, "BluetoothAdapter.isEnabled == false", Toast.LENGTH_LONG).show();
    return false;
}
// 獲取廣播者
mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
    if (mBluetoothLeAdvertiser == null) {
    Toast.makeText(mContext, "BluetoothLeAdvertiser == null ", Toast.LENGTH_LONG).show();
    return false;
}

到這里如果 mBluetoothLeAdvertiser 為空,請換設(shè)備再繼續(xù)(國內(nèi)部分機型不支持)

建立服務(wù)

// 給個風(fēng)騷的廣播名稱,默認是手機設(shè)置里藍牙的名稱
mBluetoothAdapter.setName("Bleoo");
// 這個Callback 是設(shè)備廣播成功后所有狀態(tài)的回調(diào),包括讀寫等
mGattServerCallback = new PeriServerCallBack();
// 打開GattServer
mGattServer = mBluetoothManager.openGattServer(mContext, mGattServerCallback);
// 創(chuàng)建一個特征通道用來寫
mWriteCharacter = new BluetoothGattCharacteristic(
        UUID.fromString(Constants.CHARACTERISTIC_WRITEABLE),
        BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_READ,
        BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);
// 創(chuàng)建一個特征通道用來讀
mReadCharacter = new BluetoothGattCharacteristic(
        UUID.fromString(Constants.CHARACTERISTIC_READABLE),
        BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_READ,
        BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);
// 創(chuàng)建一個Gatt服務(wù)
mGattService = new BluetoothGattService(
        UUID.fromString(Constants.GATT_SERVICE_PRIMARY),
        BluetoothGattService.SERVICE_TYPE_PRIMARY);
// 添加讀寫通道
mGattService.addCharacteristic(mWriteCharacter);
mGattService.addCharacteristic(mReadCharacter);
// 添加服務(wù)
if (mGattServer != null && mGattService != null)
    mGattServer.addService(mGattService);

開啟廣播

成功開啟廣播后,中心設(shè)備能夠掃描到你的設(shè)備,當(dāng)然前提是中心設(shè)備支持ble設(shè)備,并且在掃描ble設(shè)備。
中心設(shè)備能獲取到你ble設(shè)備的所有信息,包括 GattService 及其 BluetoothGattCharacteristic。

public void startAdvertising() {
    // 這里的Callback是是否開啟成功的回調(diào)
    mBluetoothLeAdvertiser.startAdvertising(createAdvSettings(true, 0), createAdvertiseData(), mAdvCallback);
}
private AdvertiseSettings createAdvSettings(boolean connectable, int timeoutMillis) {
    AdvertiseSettings.Builder builder = new AdvertiseSettings.Builder();
    //設(shè)置廣播的模式,跟功耗相關(guān)
    builder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED);
    builder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);
    builder.setConnectable(connectable);
    builder.setTimeout(timeoutMillis);
    return builder.build();
}
//設(shè)置廣播數(shù)據(jù)(可以攜帶廣播數(shù)據(jù),這里沒有攜帶)
private AdvertiseData createAdvertiseData() {
    AdvertiseData.Builder builder = new AdvertiseData.Builder();
    builder.setIncludeDeviceName(true);
    return builder.build();
}

廣播回調(diào)

    //發(fā)送廣播的回調(diào)
    private AdvertiseCallback mAdvCallback = new AdvertiseCallback() {
        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
            mOnCallBackListener.advertisingStatus(true);
            if (settingsInEffect != null) {
                LogUtil.e("onStartSuccess TxPowerLv=" + settingsInEffect.getTxPowerLevel()
                        + " mode=" + settingsInEffect.getMode() + " timeout=" + settingsInEffect.getTimeout());
            } else {
                LogUtil.e("onStartSuccess, settingInEffect is null");
            }
        }

        public void onStartFailure(int errorCode) {
            mOnCallBackListener.advertisingStatus(false);
            LogUtil.e("onStartFailure errorCode=" + errorCode);
        }
    };

Gatt服務(wù)回調(diào)

private class PeriServerCallBack extends BluetoothGattServerCallback {

    //當(dāng)添加一個GattService成功后會回調(diào)改接口。
    @Override
    public void onServiceAdded(int status, BluetoothGattService service) {
        if (status == BluetoothGatt.GATT_SUCCESS) {
            LogUtil.e("onServiceAdded status=GATT_SUCCESS service=" + service.getUuid().toString());
        } else {
            LogUtil.e("onServiceAdded status!=GATT_SUCCESS");
        }
    }
    
    //BLE連接狀態(tài)改變后回調(diào)的接口
    @Override
    public void onConnectionStateChange(android.bluetooth.BluetoothDevice device, int status, int newState) {
        mClientDevice = device;
        LogUtil.e("BLE連接狀態(tài)改變 status=" + status + "->" + newState + " ==== Address: " + device.getAddress());
    }
    
    //當(dāng)有客戶端來讀數(shù)據(jù)時回調(diào)的接口
    @Override
    public void onCharacteristicReadRequest(android.bluetooth.BluetoothDevice device,
                int requestId, int offset, BluetoothGattCharacteristic characteristic) {
        mClientDevice = device;
        LogUtil.e("客戶端讀數(shù)據(jù) requestId=" + requestId + " offset=" + offset);
        mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
    }
    
    //當(dāng)有客戶端來寫數(shù)據(jù)時回調(diào)的接口
    @Override
    public void onCharacteristicWriteRequest(android.bluetooth.BluetoothDevice device, int requestId,
                BluetoothGattCharacteristic characteristic, boolean preparedWrite,
                boolean responseNeeded, int offset, byte[] value) {
        mClientDevice = device;
        try {
            String msg = new String(value, "UTF-8");
            mOnCallBackListener.writeRequest(msg);
            LogUtil.e("客戶端寫數(shù)據(jù) + message= " + msg + " requestId= " + requestId + " offset= " + offset);
            } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        // 必須sendResponse用于響應(yīng)(具體原因也不清楚,似乎是為了保持連接)
        mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
    }
    //當(dāng)有客戶端來寫Descriptor時回調(diào)的接口
    @Override
    public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor,
                boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
        mClientDevice = device;
        LogUtil.e("onDescriptorWriteRequest === ");
        // now tell the connected device that this was all successfull
        mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
    }
}

寫出數(shù)據(jù)

與中心設(shè)備的寫出方法不同,周邊設(shè)備通過 notifyCharacteristicChanged 方法,類似通知的方法寫出數(shù)據(jù)。
當(dāng)然,sendResponse 也能用于返回數(shù)據(jù)。

    public boolean write(byte[] value) {
        if (mWriteCharacter == null)
            return false;
        if (mGattServer == null)
            return false;
        mWriteCharacter.setValue(value);
        return mGattServer.notifyCharacteristicChanged(mClientDevice, mWriteCharacter, false);
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容