Android BLE 蓝牙开发指南(二)中心设备端开发详解

这是系列文章的第二篇,第一篇 Android BLE开发指南(一)入门基础 主要介绍了BLE开发的一些基础知识。那么接下来的
这篇文章主要讲解BLE中心设备端程序的开发流程,让你的Android设备可以通过 BLE 进行数据的收发。

1. 权限配置

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <!-- 不包含蓝牙功能的设备不能安装此应用 -->
	<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
	<!-- 用于扫描BLE设备必须声明的权限 6.0以上版本需要动态申请-->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

Android 6.0 (API 23) 开始需要动态申请权限,这个不多说了。

2. 检查蓝牙开关

BLE的相关操作都需要在蓝牙开关打开的前提下进行。所以首先检查蓝牙是否打开。

    private BluetoothAdapter mBtAdapter;
    public void init(Application application) {
        ......
        mBtAdapter = BluetoothAdapter.getDefaultAdapter();
    }

    public boolean isBleEnabled() {
        return mBtAdapter != null && mBtAdapter.isEnabled();
    }

如果开关未开启,则向用户申请开启蓝牙开关

    public boolean setBleEnable(Activity activity, boolean enable) {
        boolean setSucceed = true;
        if (enable) {
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            activity.startActivityForResult(intent, BleManager.REQUEST_ENABLE_BT);
        } else {
            ......
        }
        return setSucceed;
    }

用户同意或者拒绝蓝牙开关申请后,会通过 onActivityResult 将结果进行反馈,如果蓝牙开启成功,则继续下一步操作。

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK && requestCode == BleManager.REQUEST_ENABLE_BT) {
            BleManager.getInstance().startScanLeDevice(mBleScanListener);
        }
    }

3. 扫描附近的BLE设备

通过调用 BluetoothAdapter.startLeScan 开启扫描,传入 BluetoothAdapter.LeScanCallback 回调作为参数,用以接收扫描结果。
由于扫描操作比较耗电,在找到目前设备后,应当及时调用 BluetoothAdapter.stopLeScan 停止扫描。并设置超时结束,避免扫描时间过长增加功耗。

    private static final int DEFAULT_SCAN_TIMEOUT = 10000; // 10s
    private BluetoothAdapter mBtAdapter;
    private BluetoothAdapter.LeScanCallback mLeScanCallback;
    private boolean mIsScanning;
    private Handler mHandler = new Handler();

    public void startScanLeDevice(BleScanListener scanListener) {
        scanLeDevice(true, scanListener);
    }

    public void stopScanLeDevice() {
        scanLeDevice(false, null);
    }

    private void scanLeDevice(boolean enable, BleScanListener scanListener) {
        if (enable) {
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    stopScanLeDevice();
                }
            }, DEFAULT_SCAN_TIMEOUT);

            if (!mIsScanning) {
                mBtAdapter.startLeScan(mLeScanCallback);
            }
            mIsScanning = true;
            ......
        } else {
            if (mIsScanning) {
                mBtAdapter.stopLeScan(mLeScanCallback);
            }
            mIsScanning = false;
            ......
        }
    }

在 Android 5.0 (API 21) 开始,Google提供了开启扫描的新接口——BluetoothLeScanner,功能更加强大,可以对扫描进行很多个性化设置,有兴趣的筒子们可以研究一下。

    @RequiresApi(api = Build.VERSION_CODES.M)
    public void scanLeDeviceNew() {
        BluetoothLeScanner leScanner = mBtAdapter.getBluetoothLeScanner();
        ScanSettings scanSettings = new ScanSettings.Builder()
                .setScanMode(ScanSettings.SCAN_MODE_BALANCED)
                .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
                .setMatchMode(ScanSettings.MATCH_MODE_STICKY)
                .build();
        List<ScanFilter> scanFilters = new ArrayList<>();
        ScanFilter scanFilter = new ScanFilter.Builder()
                .setDeviceAddress("device address")
                .setServiceUuid(ParcelUuid.fromString("service uuid"))
                .build();
        scanFilters.add(scanFilter);
        leScanner.startScan(scanFilters, scanSettings, new ScanCallback() {
            @Override
            public void onScanResult(int callbackType, ScanResult result) {
                super.onScanResult(callbackType, result);
            }

            @Override
            public void onBatchScanResults(List<ScanResult> results) {
                super.onBatchScanResults(results);
            }

            @Override
            public void onScanFailed(int errorCode) {
                super.onScanFailed(errorCode);
            }
        });
    }

4. 接收扫描结果

在扫描到BLE设备后,通过 BluetoothAdapter.LeScanCallback 接口的 onLeScan 方法接收回调通知,每找到一个设备,会回调一次该函数。从回调的数据中可以获取到外围设备的名称、UUID和MAC地址等信息。其中,MAC地址是用于连接目标外围设备的唯一标识。

这里需要注意的是,此MAC地址非彼MAC地址。为什么这么说呢? 其实就是该MAC地址并非外围设备真实的MAC地址,而是根据一定规则进行处理后的地址,可能是处于安全考虑的一种保护措施吧。

    private BleScanListener mBleScanListener;
    private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice bluetoothDevice, int rssi, byte[] bytes) {
            if (mBleScanListener != null) {
                mBleScanListener.onBleDeviceFind(new BleDeviceInfo(bluetoothDevice.getName(),
                        UUIDUtil.parseUuidStr(bytes),
                        bluetoothDevice.getAddress()));
            }
        }
    };

5. 与外围设备建立连接

首先,通过扫描时获取到的目标设备的MAC地址,新建一个 BluetoothDevice 实例对象, 再调用 connectGatt 发起连接操作。上面我们说过,这个MAC地址,是经过一定规则生成而来的,所以在连接外围设备时,直接把它的真实MAC地址拿来进行连接是行不通的,必须先通过扫描来拿到目标设备的虚拟MAC地址才行。

    private BluetoothGattCallback mGattCallback;
    public void connectDevice(BleDeviceInfo deviceInfo, BleDeviceListener gattListener) {
        BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(deviceInfo.getAddress());
        BluetoothGatt btGatt = btDevice.connectGatt(mContext, false, mGattCallback);
    }

从上面可以看到,connectGatt 需要传入一个 BluetoothGattCallback 接口作为参数,其连接结果也会通过该接口的 onConnectionStateChange 函数进行回调。

如果 newState 的值等于 BluetoothProfile.STATE_CONNECTED,表示设备连接成功。

    private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            switch (newState) {
                case BluetoothProfile.STATE_CONNECTED:
                    gatt.discoverServices();
                    break;

                case BluetoothProfile.STATE_DISCONNECTED:
                    gatt.close();
                    break;
            }
        }

        ......
    };

连接成功后,是不是可以开始尽情的操作了呢?还不是时候,接着往下走哈~

6. 查找BLE服务

gatt.discoverServices();

在成功建立连接后,需要调用 discoverServices 查找可用的BLE服务。扫描结果将会通过 BluetoothGattCallback 接口的 onServicesDiscovered 函数进行回调。调用 gatt.getServices 函数即可拿到查找出来的所有BLE服务。

    private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            switch (status) {
                case BluetoothGatt.GATT_SUCCESS:
                    List<BluetoothGattService> serviceList = gatt.getServices();
                    for (BluetoothGattService btGattService : serviceList) {
                        // 遍历服务中包含的所有Characteristic
						List<BluetoothGattCharacteristic> charics = btGattService.getCharacteristics()......
                    }
                    break;
            }
        }
        ......
    };

7. 获取可用的Characteristic

在第一篇文章 Android BLE开发指南(一)入门基础 中,我们介绍过,每个BLE服务中包含一个或多个 Characteristic。BLE数据通信 Characteristic 发挥了关键的作用。

这里定义的两个 UUID,对应外围设备端BLE服务中定义好的两个 Characteristic 的 UUID(一个可写,一个可读)。通过这两个UUID,就可以获取到对应的 Characteristic 实例。一般BLE数据通信都有可写和可读的两个 Characteristic,分别用于数据通信时的数据发送和数据接收。

    public static final UUID UUID_CHARIC_WRITE =
            UUID.fromString("000057a8-0000-1000-8000-00805f9b34fb");
    public static final UUID UUID_CHARIC_READ =
            UUID.fromString("000057a9-0000-1000-8000-00805f9b34fb");

    private BluetoothGattCharacteristic mWritableCharacter = null;
    private BluetoothGattCharacteristic mReadableCharacter = null;
    private boolean findAvailableCharic(BluetoothGatt gatt) {
        List<BluetoothGattService> services = gatt.getServices();
        for (BluetoothGattService service : services) {
            String curServiceUUID = service.getUuid().toString();
            for (UUID serviceUuid : UUIDUtil.AVAILABLE_UUIDS) {
                if (serviceUuid.toString().equals(curServiceUUID)) {
                    mReadableCharacter = service.getCharacteristic(UUID_CHARIC_READ);
                    mWritableCharacter = service.getCharacteristic(UUID_CHARIC_WRITE);
                    if (mWritableCharacter != null && mReadableCharacter != null) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

来到这里,离我们发送数据的终极目的已经很近啦~

8. 发送数据

发送数据通过可写的 Characteristic 来完成。
把需要发送的数据通过 mWritableCharacter.setValue 保存起来,再通过 mBluetoothGatt.writeCharacteristic 进行发送。前面提到过,BLE数据通信每次只能传输不大于20byte的数据(一般我们会定义一个传输的协议,以充分利用好这个20个字节,这个等到分包发送数据时再讲解)。
这里直接传入一个字符串作为数据,方便程序的演示。

    private BluetoothGatt mBluetoothGatt;
    private BluetoothGattCharacteristic mWritableCharacter = null;
    private void sendDataByCharacter(String value) {
        mWritableCharacter.setValue(value);
        mBluetoothGatt.writeCharacteristic(mWritableCharacter);
    }

发送完成后,外围设备端的Android程序中 BluetoothGattServerCallback 接口的 onCharacteristicWriteRequest 函数会被回调,具体在 Android BLE开发指南(三)外围设备端程序开发详解 中说明。

9. 接收数据

接收数据通过可读的 Characteristic 来完成。
首先需要设置一个通知的监听,这一步很关键,不然外围设备端发送的数据将无法接收到。

mBluetoothGatt.setCharacteristicNotification(characteristic, true);

通知监听设置完成后,如果外围设备端有发送数据过来,BluetoothGattCallback 接口的 onCharacteristicChanged 函数会被回调,数据可以通过 characteristic.getValue 获取到。

    private BluetoothGattCallback mBtGattCallback = new BluetoothGattCallback() {
        ......
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            LogUtil.d("receive data: "+ characteristic.getStringValue(0));
            ......
        }
        ......
    };

10. 断开连接

数据通信完毕后,如果需要断开连接,则调用 disconnect 即可。在连接断开完成后,会回调 onConnectionStateChange ,这时调用 close 关闭。

    private BluetoothGatt mBluetoothGatt;
    public void disconnectDevice() {
        if (mBluetoothGatt != null) {
            mBluetoothGatt.disconnect();
        }
    }

    private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            switch (newState) {
                ......
                case BluetoothProfile.STATE_DISCONNECTED:
                    gatt.close();
                    break;
            }
        }
    }

11. 程序运行示例图

程序运行示例图如下,左边是中心设备端程序的界面,右边是外围设备端程序的界面。
中心设备发送 “central dev msg” 到外围设备,外围设备发送 ”peripheral dev msg“ 到中心设备,两个设备之间实现了数据的互相收发。
接下来,请开始你的表(撸)演(码)~

以上,希望对大家有帮助哈~

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值