安卓低功耗蓝牙

来源:互联网 发布:一亿玉碎 知乎 编辑:程序博客网 时间:2024/06/06 07:05

谷歌官方文档:
https://developer.android.google.cn/guide/topics/connectivity/bluetooth-le.html
谷歌官方demo:
https://github.com/googlesamples/android-BluetoothLeGatt/
参考:
http://blog.csdn.net/chenfengdejuanlian/article/details/45787123
http://blog.csdn.net/z957250254/article/details/52411556

第一次接触蓝牙方面的知识,仅此记录,大家多多交流啊

先按照下图走一遍流程
这里写图片描述

检查、开启权限

开启蓝牙权限

    <uses-permission android:name="android.permission.BLUETOOTH" />    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />    <!-- 声明此应用仅适用于具有低功耗蓝牙设备,若想适用于不支持BLE的设备,required设置为false -->    <uses-feature        android:name="android.hardware.bluetooth_le"        android:required="true" />

检查设备是否支持低功耗蓝牙

if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {            Toast.makeText(this, "此设备不支持BLE(低功耗蓝牙)", Toast.LENGTH_LONG).show();            finish();        }

需要注意的是还要开启位置权限
这里写图片描述
在manifest添加

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

并在activity中动态申请权限(本例使用RxPermission请求权限)

    /**     * 请求定位权限     */    private void checkPermission() {        new RxPermissions((Activity) mContext).request(Manifest.permission.ACCESS_COARSE_LOCATION)                .subscribe(new Observer<Boolean>() {                    @Override                    public void onSubscribe(Disposable d) {                    }                    @Override                    public void onNext(Boolean granted) {                        if (granted) {                            initBlueTooth();                        } else {                            // 未获取权限                            Toast.makeText(mContext, "未获取定位权限", Toast.LENGTH_LONG).show();                        }                    }                    @Override                    public void onError(Throwable e) {                    }                    @Override                    public void onComplete() {                    }                });    }

搜索蓝牙设备

搜索蓝牙设备是需要一个BluetoothAdapter来扫描设备的,这里又要进行设备是否支持蓝牙功能的判断和是否开启蓝牙(若未开启,提示用户或强制开启蓝牙)

private void initBlueTooth() {        //BluetoothAdapter代表设备自己的蓝牙适配器(蓝牙无线电)        BluetoothManager bluetoothManager =                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);        mBluetoothAdapter = bluetoothManager.getAdapter();        //设备是否支持蓝牙        if (mBluetoothAdapter == null) {            Toast.makeText(mContext, "该设备不支持蓝牙功能", Toast.LENGTH_LONG).show();            finish();        }        //蓝牙是否启用,若未启用,请求用户启用        if (!mBluetoothAdapter.isEnabled()) {            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);            return;        }        //若开启蓝牙,直接扫描低功耗蓝牙设备;若未开启,提示用户开启,用户开启后在onActivityResult开启扫描        scanLeDevice(true);    }

扫描或关闭扫描

注意:扫描不能一直进行,本例SCAN_PERIOD=10000毫秒后停止扫描

     /**     * 扫描低功耗蓝牙设备     * 您只能扫描蓝牙LE设备或扫描经典蓝牙设备,如蓝牙中所述  您无法同时扫描Bluetooth LE和传统设备。     *     * @param enable     */    private void scanLeDevice(final boolean enable) {        if (enable) {            //Because scanning is battery-intensive, you should observe the following guidelines:            //As soon as you find the desired device, stop scanning.找到所需设备停止扫描            //Never scan on a loop, and set a time limit on your scan.切勿扫描循环,并在扫描上加上时间限制            mHandler.postDelayed(new Runnable() {                @Override                public void run() {                    mScanning = false;                    mBluetoothAdapter.stopLeScan(mLeScanCallback);                    mTextView.setText("扫描结束");                    invalidateOptionsMenu();                }            }, SCAN_PERIOD);            mScanning = true;            //如果只想扫描特定类型的外设,则可以改为调用startLeScan(UUID [],BluetoothAdapter.LeScanCallback)            //提供指定您的应用程序支持的GATT服务的UUID对象数组            mBluetoothAdapter.startLeScan(mLeScanCallback);            mTextView.setText("扫描中...");        } else {            mScanning = false;            mBluetoothAdapter.stopLeScan(mLeScanCallback);            mTextView.setText("扫描结束");        }        invalidateOptionsMenu();    }

mBluetoothAdapter.startLeScan()的参数mLeScanCallback是扫描结果的回调

mLeScanCallback =                new BluetoothAdapter.LeScanCallback() {                    @Override                    public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {                        runOnUiThread(new Runnable() {                            @Override                            public void run() {                                if (device != null) {                                    //过滤同一设备                                    if (!mDevices.contains(device)) {                                        //将设备添加到列表中                                        mDeviceListAdapter.addData(device);                                    }                                }                            }                        });                    }                };

其中mDeviceListAdapter为列表适配器,mDevices为列表适配器中的数据,若不进行同一设备的过滤,会返回很多相同设备

连接ble设备

本demo是基于谷歌的示例代码,就直接将谷歌连接设备的相关类拷过来了,这里直接对其分析

在activity中绑定一个BluetoothLeService服务,此服务是用于对指定蓝牙设备进行连接或数据通信的服务

        Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);        bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);

其中mServiceConnection管理BluetoothLeService服务的生命周期

    /**     * 管理蓝牙服务的生命周期     */    private final ServiceConnection mServiceConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName componentName, IBinder service) {            mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();            if (!mBluetoothLeService.initialize()) {                Log.e(TAG, "Unable to initialize Bluetooth");                finish();            }            //成功启动初始化后自动连接到设备            mBluetoothLeService.connect(mDeviceAddress);        }        @Override        public void onServiceDisconnected(ComponentName componentName) {            mBluetoothLeService = null;        }    };

在连接设备之前,要判断设备是否支持低功耗蓝牙

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;    }

BluetoothLeService绑定成功后,将要连接的设备地址传入,在service中连接设备

    /**     * 连接到Bluetooth LE设备上托管的GATT服务器     *     * @param address 设备地址     * @return 返回是否连接是否成功启动(注意:是启动,并不是连接结果)     * 连接结果是在BluetoothGattCallback onConnectionStateChange异步返回的     */    public boolean connect(final String address) {        if (mBluetoothAdapter == null || address == null) {            Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");            return false;        }        // 以前连接的设备,尝试重连        if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)                && mBluetoothGatt != null) {            Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");            if (mBluetoothGatt.connect()) {                mConnectionState = STATE_CONNECTING;                return true;            } else {                return false;            }        }        //通过传入的设备地址获取设备        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);        if (device == null) {            Log.w(TAG, "Device not found.  Unable to connect.");            return false;        }        // 我们要直接连接到设备,所以我们正在设置autoConnect参数为false (此处autoConnect为true可能不是立即连接)        mBluetoothGatt = device.connectGatt(this, false, mGattCallback);        Log.d(TAG, "Trying to create a new connection.");        mBluetoothDeviceAddress = address;        mConnectionState = STATE_CONNECTING;        return true;    }

其中device.connectGatt()的第三个参数mGattCallback为蓝牙设备与GATT服务端连接和数据通信的监听,此方法的返回值mBluetoothGatt 稍后介绍

private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {        //回调指示何时GATT客户端连接到远程GATT服务器/从远程GATT服务器断开连接        @Override        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {            String intentAction;            //连接成功            if (newState == BluetoothProfile.STATE_CONNECTED) {                intentAction = ACTION_GATT_CONNECTED;                mConnectionState = STATE_CONNECTED;                broadcastUpdate(intentAction);                Log.i(TAG, "Connected to GATT server.");                // 连接成功后尝试发现服务                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) {            if (status == BluetoothGatt.GATT_SUCCESS) {                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);            } else {                Log.w(TAG, "onServicesDiscovered received: " + status);            }        }        //回调报告特征性读取操作的结果        @Override        public void onCharacteristicRead(BluetoothGatt gatt,                                         BluetoothGattCharacteristic characteristic,                                         int status) {            if (status == BluetoothGatt.GATT_SUCCESS) {                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);            }        }        //由于远程特征通知而触发的回调        @Override        public void onCharacteristicChanged(BluetoothGatt gatt,                                            BluetoothGattCharacteristic characteristic) {            broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);        }    };

其中,连接成功、断开或发现服务或数据通信的回调均发送了广播,在activity中接收广播进行管理,这里先不关注每一个方法,尽量从一个流程分析

看到onConnectionStateChange回调中,判断状态为连接成功,调用了如下代码发现服务

mBluetoothGatt.discoverServices()

注意:

上文中mBluetoothGatt相当于手机与设备通信的管道
通过BluetoothGatt

  • 发现服务(discoverServices)
  • 获取服务(getServices)
  • 开启指定指定特征的通知(setCharacteristicNotification)
  • 对指定特征写入数据(writeCharacteristic)
  • 把相应地属性返回到BluetoothGattCallback

demo中发现服务后发送广播到activity中,在activity中接收广播调用service(此service为BluetoothLeService,是自己写的对指定蓝牙设备进行连接或数据通信的服务)的获取服务(此处的服务为**蓝牙设备所拥有的服务**BluetoothGattService)方法getServices,然后在activity中对服务进行遍历,并存储每个服务下的特征值

此处分析下流程:调用mBluetoothGatt.discoverServices方法后,发现服务后会回调给BluetoothGattCallback的onServicesDiscovered,在调用mBluetoothGatt.getServices()返回一个包含BluetoothGattService服务的集合,再对集合进行遍历,获取每一个BluetoothGattService服务的特征

注意:(这里有点绕)

  • BluetoothLeService 自己写的对指定蓝牙设备进行连接或数据通信的服务
  • BluetoothGattService 蓝牙设备所拥有的服务
  • BluetoothGattCharacteristic 某一BluetoothGattService的某一特征
  • BluetoothGattDescriptor 某一BluetoothGattCharacteristic下的属性,用来描述characteristic变量的属性

下面这张图为BluetoothGattService 和 BluetoothGattCharacteristic 和 BluetoothGattDescriptor的关系

这里写图片描述

遍历服务(这里尤其绕)使用了ExpandableListView展示服务和服务下的特征

private void displayGattServices(List<BluetoothGattService> gattServices) {        if (gattServices == null) return;        String uuid = null;        String unknownServiceString = getResources().getString(R.string.unknown_service);        String unknownCharaString = getResources().getString(R.string.unknown_characteristic);        ArrayList<HashMap<String, String>> gattServiceData = new ArrayList<HashMap<String, String>>();        ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData                = new ArrayList<ArrayList<HashMap<String, String>>>();        mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>();        //遍历所有的服务        for (BluetoothGattService gattService : gattServices) {            //每一个服务的name 和 UUID保存在map中            HashMap<String, String> currentServiceData = new HashMap<String, String>();            uuid = gattService.getUuid().toString();            currentServiceData.put(                    LIST_NAME, SampleGattAttributes.lookup(uuid, unknownServiceString));            currentServiceData.put(LIST_UUID, uuid);            //将 每一个 包含服务信息的map 都添加到集合中            gattServiceData.add(currentServiceData);            ArrayList<HashMap<String, String>> gattCharacteristicGroupData =                    new ArrayList<HashMap<String, String>>();            List<BluetoothGattCharacteristic> gattCharacteristics =                    gattService.getCharacteristics();            ArrayList<BluetoothGattCharacteristic> charas =                    new ArrayList<BluetoothGattCharacteristic>();            //遍历服务中的特征            for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {                //将此服务中的特征添加到集合中                charas.add(gattCharacteristic);                //将此特征的UUID和name保存在map中                HashMap<String, String> currentCharaData = new HashMap<String, String>();                uuid = gattCharacteristic.getUuid().toString();                currentCharaData.put(                        LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString));                currentCharaData.put(LIST_UUID, uuid);                //将 每一个 包含特征信息的map 都添加到集合中                gattCharacteristicGroupData.add(currentCharaData);            }            //将  每个服务中包含的所有特征的集合  添加到总集合中            mGattCharacteristics.add(charas);            //将  每个服务中包含的所有特征信息的集合  添加到总集合中            gattCharacteristicData.add(gattCharacteristicGroupData);        }        SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter(                this,                gattServiceData,                android.R.layout.simple_expandable_list_item_2,                new String[]{LIST_NAME, LIST_UUID},                new int[]{android.R.id.text1, android.R.id.text2},                gattCharacteristicData,                android.R.layout.simple_expandable_list_item_2,                new String[]{LIST_NAME, LIST_UUID},                new int[]{android.R.id.text1, android.R.id.text2}        );        mGattServicesList.setAdapter(gattServiceAdapter);    }

上面的代码如果不太明白可以慢慢看的,先看下面的
我们只需要知道,点击ExpandableListView group为此设备下的所有service,点击group会展开此service下的所有特征

/**     * ExpandableListView 子条目点击监听 点击条目,会把正在通信的特征关闭通知,再把点击条目的特征打开通知     */    private final ExpandableListView.OnChildClickListener servicesListClickListener =            new ExpandableListView.OnChildClickListener() {                @Override                public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,                                            int childPosition, long id) {                    if (mGattCharacteristics != null) {                        final BluetoothGattCharacteristic characteristic =                                mGattCharacteristics.get(groupPosition).get(childPosition);                        /**                         * 正常流程                         * 打开  指定接收特征  (指定UUID)通知                         * 在  指定写入特征  写入数据                         */                        //从指定UUID的特征打开接收(测试用)                        if (characteristic.getUuid().toString().equals("0000ff02-0000-1000-8000-00805f9b34fb")) {                            mBluetoothLeService.setCharacteristicNotification(                                    characteristic, true);                        }//                        //从指定UUID的特征写入(测试用)                        if (characteristic.getUuid().toString().equals("0000ff01-0000-1000-8000-00805f9b34fb")) {                            mBluetoothLeService.write(characteristic);                        }                        return true;                    }                    return false;                }            };

本例中
UUID为0000ff02-0000-1000-8000-00805f9b34fb的特征为读取特征
UUID为0000ff01-0000-1000-8000-00805f9b34fb的特征为写入特征

点击0000ff02-0000-1000-8000-00805f9b34fb条目打开读取通知

mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

点击0000ff01-0000-1000-8000-00805f9b34fb条目写入数据

//写入数据(测试用)    public void write(BluetoothGattCharacteristic characteristic) {        //握手        characteristic.setValue(hexStringToByteArray("A2"));        mBluetoothGatt.writeCharacteristic(characteristic);        //握手和命令不能超过4秒,但又不能马上        SystemClock.sleep(500);        //命令        characteristic.setValue("SINSAM");        mBluetoothGatt.writeCharacteristic(characteristic);    }

数据写入后,就会在mGattCallback的onCharacteristicRead回调里通过characteristic.getValue()拿到数据了
ok基本总结完了

源码点我

最后一张美图镇楼。。(按照自己对低功耗蓝牙的理解画的图)
这里写图片描述

原创粉丝点击