关于Android4.0 BLE编程的一点总结(搜索篇)

从公司里面接过BLE蓝牙项目已经有一段时间了,虽然这个项目的复杂度不高,但是对于我这种第一次开发蓝牙的来说,发现里面有不少的坑,特此在这里进行记录,本来一直想总结的,但是手头上的东西太多了,就留到节假日了。

安卓4.3(API 18)为BLE的核心功能提供平台支持和API,App可以利用它来发现设备、查询服务和读写特性。相比传统的蓝牙,BLE更显著的特点是低功耗。这一优点使Android App可以与具有低功耗要求的BLE设备通信,如近距离传感器、心脏速率监视器、健身设备。

闲话不多说,让我总结一下,蓝牙的工作流程和使用。

蓝牙编程中,我们可以将蓝牙分为以下几个步骤:

  1. 搜索蓝牙
  2. 蓝牙搜索成功后,根据UUID,或者自己的选择进行连接。
  3. 蓝牙连接成功之后,我们可以开始传输数据。

这一章内容先总结蓝牙搜索时候,需要注意的问题以及初始的步骤。

在这之前我先要总结一下,蓝牙接下来会用到的一些术语:

  1. Generic Attribute Profile(GATT)—GATT配置文件是一个通用规范,用于在BLE链路上发送和接收被称为“属性”的数据块。目前所有的BLE应用都基于GATT。 蓝牙SIG规定了许多低功耗设备的配置文件。配置文件是设备如何在特定的应用程序中工作的规格说明。注意一个设备可以实现多个配置文件。例如,一个设备可能包括心率监测仪和电量检测。

  2. Attribute Protocol(ATT)—GATT在ATT协议基础上建立,也被称为GATT/ATT。ATT对在BLE设备上运行进行了优化,为此,它使用了尽可能少的字节。每个属性通过一个唯一的的统一标识符(UUID)来标识,每个String类型UUID使用128 bit标准格式。属性通过ATT被格式化为characteristics和services。

  3. Characteristic 一个characteristic包括一个单一变量和0-n个用来描述characteristic变量的descriptor,characteristic可以被认为是一个类型,类似于类。

  4. Descriptor Descriptor用来描述characteristic变量的属性。例如,一个descriptor可以规定一个可读的描述,或者一个characteristic变量可接受的范围,或者一个characteristic变量特定的测量单位。

  5. Service service是characteristic的集合。例如,你可能有一个叫“Heart Rate Monitor(心率监测仪)”的service,它包括了很多characteristics,如“heart rate measurement(心率测量)”等。你可以在bluetooth.org 找到一个目前支持的基于GATT的配置文件和服务列表。

看不懂没关系。实际上,我们在做项目的时候,硬件提供方会提供两种字符串,ServiceUUID,以及characteristic。我们是通过这个ServiceUUID进行寻找连接设备以及以及进行连接这两个步骤。而后,根据硬件提供方的characteristic和蓝牙协议,如开机是0X01之类的16进制代码(或者其他的要看厂家给啥),通过蓝牙发送的函数或者读取的函数,写入serviceuuid,characteristic,协议上的代码一起发送过去就完成蓝牙通信流程打,当然在其中有许许多多的点和坑需要注意。

正文

蓝牙搜索第一步:
先检测蓝牙是否开启,并且授权蓝牙权限:

获得权限:

<uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

初始化:



private BluetoothManager mBluetoothManager;
 private BluetoothAdapter mBluetoothAdapter;

   public boolean initialize() {
        // For API level 18 and above, get a reference to BluetoothAdapter through
        // BluetoothManager.
        if (mBluetoothManager == null) {
            mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
            if (mBluetoothManager == null) {
                Log.e(TAG, "Unable to initialize BluetoothManager.");
                return false;
            }
        }

        mBluetoothAdapter = mBluetoothManager.getAdapter();
        if (mBluetoothAdapter == null) {
            Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
            return false;
        }
        return true;
    }

注意从android API18 ble编程开始,我们开始使用BlueAdapter这个类里面获取蓝牙行为的函数。

再判断蓝牙时候开启:

  public boolean enableBluetooth(boolean enable) {
        if (enable) {
            if (!mBluetoothAdapter.isEnabled()) {
                mBluetoothAdapter.enable();
            }
            return true;
        } else {
            if (mBluetoothAdapter.isEnabled()) {
                mBluetoothAdapter.disable();
            }
            return false;
        }
    }
 if (mBleService.initialize()) {
                if (mBleService.enableBluetooth(true)) {
//                    mBleService.scanLeDevice(true);
                    Toast.makeText(MainActivity.this, "Bluetooth was opened", Toast.LENGTH_SHORT).show();
                }
            } else {
                Toast.makeText(MainActivity.this, "not support Bluetooth", Toast.LENGTH_SHORT).show();
            }

注意:在这里我们最好把蓝牙写成一个Service,将整个蓝牙的动作丢到后台进行工作。我们再到Activity中启动service的同时,启动蓝牙。

这里就完成了第一步,蓝牙的初始化。

蓝牙搜索第二步:
初始结束之后,开始搜索蓝牙:
我们开始调用调用两个函数:startLeScan(ScanCallback mScanCallback)以及 startLeScan(UUID[] uuids,ScanCallback mScanCallback)。
第一个函数是指搜索附近所有的蓝牙设备,第二个函数是指搜索指定serviceuuid的蓝牙设备。

 public void scanLeDevice(final boolean enable, long scanPeriod) {
        Log.i(TAG, "scanLeDevice: Build.VERSION.SDK_INT = " + Build.VERSION.SDK_INT);
        if (enable) {
            if (isScanning) return;
            //Stop scanning after a predefined scan period.
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    isScanning = false;
                    Log.e(TAG, "scanLeDevice---->扫描结束");
                    if (Build.VERSION.SDK_INT >= 21) {
                        mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
                    } else {
                        mBluetoothAdapter.stopLeScan(mLeScanCallback);
                    }
//                    broadcastUpdate(ACTION_SCAN_FINISHED);
                   /* if (mScanList != null) {
                        mScanList.clear();
                        mScanList = null;
                    }*/
//                    mBluetoothAdapter.getBluetoothLeScanner().stopScan(mLeScanCallback);
                }
            }, scanPeriod);
            if (mScanList == null) {
                mScanList = new ArrayList<>();
            }
            mScanList.clear();
            isScanning = true;
            if (Build.VERSION.SDK_INT >= 21) {
                mBluetoothAdapter.getBluetoothLeScanner().startScan(mScanCallback);
            } else {
                mBluetoothAdapter.startLeScan(mLeScanCallback);
            }
        } else {
            isScanning = false;
            if (Build.VERSION.SDK_INT >= 21) {
                mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
            } else {
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
            }
//            broadcastUpdate(ACTION_SCAN_FINISHED);
           /* if (mScanList != null) {
                mScanList.clear();
                mScanList = null;
            }*/
        }
    }

停止搜索:

 public void stopScanLe(){
        if (Build.VERSION.SDK_INT >= 21) {
            mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
        } else {
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }
    }

我们在API21之后,
搜索蓝牙的时候

startScan(final ScanCallback callback)`

搜索指定uuid的蓝牙设备的时候可以使用这个函数:startScan(List<ScanFilter> filters, ScanSettings settings,
final ScanCallback callback)

这个函数的第一个参数是指筛选条件,第二个参数是指设置。这样就很灵活的设置了我们搜索蓝牙的条件,但是当时我大致搜索了一下这个函数的两个函数究竟怎么用的时候,发现资料很少。还好我发现官方为了支持低版本,在API21之上也有startLeScan(UUID[] uuids,ScanCallback mScanCallback)函数,地城是用startScan实现的。看看官方怎么写的我们也知道如何完成的。

 @Deprecated
    public boolean startLeScan(final UUID[] serviceUuids, final LeScanCallback callback) {
        if (DBG) Log.d(TAG, "startLeScan(): " + serviceUuids);
        if (callback == null) {
            if (DBG) Log.e(TAG, "startLeScan: null callback");
            return false;
        }
        BluetoothLeScanner scanner = getBluetoothLeScanner();
        if (scanner == null) {
            if (DBG) Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner");
            return false;
        }

        synchronized(mLeScanClients) {
            if (mLeScanClients.containsKey(callback)) {
                if (DBG) Log.e(TAG, "LE Scan has already started");
                return false;
            }

            try {
                IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
                if (iGatt == null) {
                    // BLE is not supported
                    return false;
                }

                ScanCallback scanCallback = new ScanCallback() {
                    @Override
                    public void onScanResult(int callbackType, ScanResult result) {
                        if (callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES) {
                            // Should not happen.
                            Log.e(TAG, "LE Scan has already started");
                            return;
                        }
                        ScanRecord scanRecord = result.getScanRecord();
                        if (scanRecord == null) {
                            return;
                        }
                        if (serviceUuids != null) {
                            List<ParcelUuid> uuids = new ArrayList<ParcelUuid>();
                            for (UUID uuid : serviceUuids) {
                                uuids.add(new ParcelUuid(uuid));
                            }
                            List<ParcelUuid> scanServiceUuids = scanRecord.getServiceUuids();
                            if (scanServiceUuids == null || !scanServiceUuids.containsAll(uuids)) {
                                if (DBG) Log.d(TAG, "uuids does not match");
                                return;
                            }
                        }
                        callback.onLeScan(result.getDevice(), result.getRssi(),
                                scanRecord.getBytes());
                    }
                };
                ScanSettings settings = new ScanSettings.Builder()
                    .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
                    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();

                List<ScanFilter> filters = new ArrayList<ScanFilter>();
                if (serviceUuids != null && serviceUuids.length > 0) {
                    // Note scan filter does not support matching an UUID array so we put one
                    // UUID to hardware and match the whole array in callback.
                    ScanFilter filter = new ScanFilter.Builder().setServiceUuid(
                            new ParcelUuid(serviceUuids[0])).build();
                    filters.add(filter);
                }
                scanner.startScan(filters, settings, scanCallback);

                mLeScanClients.put(callback, scanCallback);
                return true;

            } catch (RemoteException e) {
                Log.e(TAG,"",e);
            }
        }
        return false;
    }

到这里就完成了蓝牙的搜索的动作,但是我们还需要实现对蓝牙的扫描后状态监听的回调

  protected ScanCallback mScanCallback;

    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mScanCallback = new ScanCallback() {
                @Override
                public void onScanResult(int callbackType, ScanResult result) {

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        String deviceName = (TextUtils.isEmpty(result.getDevice().getName())) ? "No Name" : result.getDevice().getName();

                        BlueToothModel blueToothModel = new BlueToothModel(MaxReConnectNum, result.getDevice().getAddress(), deviceName);
                        if (isContainBleDevice(blueToothModel)) return;
                        mScanList.add(blueToothModel);
                        if (mOnLeScanListener != null) {
                            mOnLeScanListener.onLeScan(result.getDevice(), result.getRssi(), result.getScanRecord().getBytes());
                        }
//                        broadcastUpdate(ACTION_BLUETOOTH_DEVICE, result.getDevice());
                        Log.e(TAG, "onScanResult: name: " + result.getDevice().getName() +
                                ", address: " + result.getDevice().getAddress() +
                                ", rssi: " + result.getRssi() + ", scanRecord: " + result.getScanRecord());
                    }
                }
            };
        }
    }

我们可以在自己编写的Service添加一个回调函数:

 public void setOnLeScanListener(OnLeScanListener l) {
        mOnLeScanListener = l;
    }

在自己的Activity中完成对回调函数重写:

mBleService.setOnLeScanListener(new BleService.OnLeScanListener() {
            @Override
            public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                      /扫描完成后的动作,如添加到列表
                            }
                        }
                    }
                });
            }
        });

至此就完成了蓝牙的扫描动作,记住回调里面的动作最好丢到Handler中异步完成,因为蓝牙本身这个动作有时间限制,如果超出了这个时间的话,怎么样不会走超出时间的部分,当时这里卡了我很久。

到这里蓝牙BLE搜索篇就大致上完成,后续还有蓝牙连接和数据通信。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值