android BLE 库

这个库我自己已经使用了很长一段时间了,我自己也经常维护。现在感觉已经比较稳定,所以就大胆拿出来分享一下,希望能帮助到正在准备进行BLE开发或进入BLE开发不久的安卓程序员们。

这个库最开始我只是为了自己做调试的时候使用,后来写着写着就把它写成了一个库,于是就慢慢的维护了起来。因为最开始的原因,所以到现在为止,这个库在发布到github上时,就叫做BleSample。是不是总感觉这不是依赖库呢?

源码地址

BleSample

BleSample

API文档

RecyclerView Adapter use BRVAH

由于更新维护会经常修改代码,下方的配置以及依赖有可能未及时更新,有需要可下载代码查看

配置:(Configure)

1.直接将library依赖到项目

2.gradle配置依赖
support版本,最高支持到API28,已经基本停止维护

implementation 'com.sscl.blelibrary:support:0.0.9'

androidx版本,可支持到最新版API,目前最高API29(截至2019-09-15)

implementation 'com.sscl.blelibrary:x:1.0.5'

一下所有示例均以androidx版本为准:

权限配置:

权限默认已经集成在依赖库中了,但是此处还是列出来做参考

<!--蓝牙权限-->
   <uses-permission android:name="android.permission.BLUETOOTH" />
   <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!--BLE权限-->
    <uses-feature
       android:name="android.hardware.bluetooth_le"
        android:required="true" />
<!-- 5.0以上的手机可能会需要这个权限 -->
<uses-feature android:name="android.hardware.location.gps" />
<!-- 6.0及以上的手机需要定位权限权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

    <application>
        <!--    这个服务用于BLE标准单个连接    -->
        <service
            android:name=".BluetoothLeService"
            android:enabled="true"
            android:exported="false" />
        <!--    这个服务用于BLE多设备连接    -->
        <service
            android:name=".BluetoothMultiService"
            android:enabled="true"
            android:exported="false" />
    </application>

初始化

在程序的Application类中初始化


public class MyApplication extends Application {
    @Override
    public void onCreate() {
         super.onCreate();
         //初始化
        BleManager.init(MyApplication.this);
        //开启调试信息打印
        com.sscl.blelibrary.DebugUtil.setDebugFlag(true);
    }
}

判断设备本身是否支持BLE:

if(!BleManager.isSupportBle()){
  //如果设备不支持BLE,在这里执行操作
  return;
}
//如果设备支持BLE,继续往下操作

BLE扫描:

    /**
     * 初始化扫描器
     */
    private void initBleScanner() {
    
        //检查当前设备是否支持LE_CODED属性(通常用于检测是否支持BLE 5.0)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (BleManager.isLeCodedPhySupported()) {
                ToastUtil.toastL(this, R.string.le_coded_supported);
            }
        }
        //创建(或获取)扫描器实例
        bleScanner = BleManager.getBleScannerInstance();
        //如果手机不支持蓝牙的话,这里得到的是null,所以需要进行判空
        if (bleScanner == null) {
            ToastUtil.toastL(DeviceListActivity.this, R.string.ble_not_supported);
            return;
        }
        //初始化扫描器
        bleScanner.init();
        //设置扫描周期,扫描会在自动在一段时间后自动停止,单位:毫秒
        bleScanner.setScanPeriod(10000);
        /*
         *设置是否一直持续扫描
         *true表示扫描周期到了之后,自动开启下一次扫描。
         *false表示在扫描结束后不再进行扫描
         */
        bleScanner.setAutoStartNextScan(true);
        //如果当前系统版本在android 5.0以上,可以设置扫描模式,这里设置的是默认值
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            bleScanner.setBleScanMode(BleScanMode.LOW_LATENCY);
        }
        //如果当前系统版本在Android6.0以上,可以设置匹配模式,这里设置的是默认值
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            bleScanner.setBleMatchMode(BleMatchMode.AGGRESSIVE);
        }
        //设置扫描状态相关回调
        bleScanner.setOnBleScanStateChangedListener(onBleScanStateChangedListener);
    }

BLE扫描进阶设置

设置过滤条件

    /**
     * 设置扫描器的过滤条件
     */
    private void setScanFilters() {
       bleScanner.addFilterxxx();
    }
    
    /**
     * 移除扫描器的过滤条件
     */
    private void setScanFilters() {
       bleScanner.removeFilterxxx();
    }

注销:

一定要记得在activity被销毁之前,注销扫描器

    /**
     * 在activity被销毁的时候关闭扫描器
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //关闭扫描器
        bleScanner.close();
        BleManager.releaseBleScanner();
    }

在程序完全退出的时候,一定要执行这一句,释放所有占用的内存

BleManager.releaseAll();

BLE设备的连接:



    /**
     * 初始化连接工具
     */
    private void initBleConnector() {
        //创建(或获取)BLE连接器实例
        bleConnector = BleManager.getBleConnectorInstance();
        //如果手机不支持蓝牙的话,这里得到的是null,所以需要进行判空
        if (bleConnector == null) {
            ToastUtil.toastL(ConnectActivity.this, R.string.ble_not_supported);
            return;
        }
        //设置连接超时的时间,单位:毫秒
        bleConnector.setConnectTimeOut(60000);
        //设置发送分包数据时,每一包数据之间的延时
        bleConnector.setSendLargeDataPackageDelayTime(500);
        //设置发送分包数据时,每一包数据发送超时的时间
        bleConnector.setSendLargeDataTimeOut(10000);        
        //设置相关的监听回调
        setConnectListener();
    }

    /**
     * 设置相关的监听回调
     */
    private void setConnectListener() {
        //设置 连接 相关的回调
        bleConnector.setOnBleConnectStateChangedListener(onBleConnectStateChangedListener);
    }

注意:请不要在“onConnectedListener”回调中判断为设备连接成功。有些(异常)情况下,在连接上设备之后会立刻断开。所以一定要在“onServicesDiscoveredListener”中判断为设备已经连接成功。

发起连接请求

    /**
     * 发起连接
     */
    private void startConnect() {
        if (bleConnector.connect(bluetoothDevice)) {
            DebugUtil.warnOut("发起连接,正在连接");
        } else {
            DebugUtil.warnOut("发起连接失败");
        }

         //connect方法有很多重载参数,请根据实际情况自行判断场景并调用相关的重载方法
    }

在连接成功之后,可以获取远端设备的服务列表,这一步操作在封装时已经自动操作了,一般情况下无需再手动调用

//手动获取设备的服务列表,通常情况下不需要执行
List<BluetoothGattService> deviceServices = bleConnector.getServices();

对目标进行数据的传输

向设备写入数据


//发送数据时,需要知道目标设备的服务UUID,特征UUID,传输的value是一个数组,一般情况下长度不能大于20
boolean succeed = bleConnector.writeData(serviceUUID,characteristicUUID,value);

//写入是否成功一般需要在回调中判断

从设备读取数据

//
boolean succeed = bleConnector.readData(serviceUUID,characteristicUUID);

//读取的结果在回调中获取

上面的发送与获取数据的方法返回的都是boolean类型,true代表命令执行中没有出错,false代表操作失败(其实bleConnector的函数基本上都是返回boolean类型的)

通知

打开通知:

 boolean succeed = bleConnector.enableNotification(serviceUUID, characteristicUUID, true);

关闭通知

 boolean succeed = bleConnector.enableNotification(serviceUUID, characteristicUUID, false);

只有开启了通知,才会触发通知的回调

bleConnector.setOnReceiveNotificationListener(onReceiveNotificationListener);

销毁

在Activity返回的时候,调用close方法。推荐在此处屏蔽super.onBackpressed()方法。

    @Override
    public void onBackPressed() {
        boolean closeResult = bleConnector.close();
        if(!closeResult){
          super.onBackPressed();
        }
    }

然后在回调中销毁activity

 OnBleConnectStateChangedListener onBleConnectStateChangedListener = new OnBleConnectStateChangedListener() {
   ......
   ......

        @Override
        public void onCloseComplete() {
            //结束当前Activity
            finish();
        }
 }

在销毁Activity的时候,释放内存

@Override
public void onDestroy() {
  super.onDestroy();
  BleManager.realseBleConnector();
}

在程序完全退出的时候,一定要执行这一句

BleManager.releaseAll();

BLE设备的绑定(也可以说是配对,要求API 19):


 int boundState = bleConnector.startBound(deviceAddress);
        /*
         * 绑定设备,除非必要,否则请直接调用连接的方法
         * 注意:如果该设备不支持绑定,会直接回调绑定成功的回调,在绑定成功的回调中发起连接即可
         * 第一次绑定某一个设备会触发回调,之后再次绑定,可根据绑定时的函数的返回值来判断绑定状态,以进行下一步操作
         */
        switch (boundState) {
            case BleConstants.DEVICE_BOND_START_SUCCESS:
                LogUtil.w(TAG, "开始绑定(Start binding)");
                break;
            case BleConstants.DEVICE_BOND_START_FAILED:
                LogUtil.w(TAG, "发起绑定失败(Failed to initiate binding)");
                break;
            case BleConstants.DEVICE_BOND_BONDED:
                LogUtil.w(TAG, "此设备已经被绑定了(This device is already bound)");
                startConnect();
                break;
            case BleConstants.DEVICE_BOND_BONDING:
                LogUtil.w(TAG, "此设备正在绑定中(This device is binding)");
                break;
            case BleConstants.BLUETOOTH_ADAPTER_NULL:
                LogUtil.w(TAG, "没有蓝牙适配器存在(No Bluetooth adapter exists)");
                break;
            case BleConstants.BLUETOOTH_ADDRESS_INCORRECT:
                LogUtil.w(TAG, "蓝牙地址错误(Bluetooth address is wrong)");
                break;
            case BleConstants.BLUETOOTH_MANAGER_NULL:
                LogUtil.w(TAG, "没有蓝牙管理器存在(No Bluetooth manager exists)");
                break;
            default:
                LogUtil.w(TAG, "default");
                break;
        }

多连接(Multi-connection)

获取多连接的连接器(Get multiple connectors)
```java
 bleMultiConnector = BleManager.getBleMultiConnectorInstance();

连接多个设备(Connect multiple devices)

    String device1Address = "00:02:5B:00:15:A4";
    String device2Address = "00:02:5B:00:15:A2";

    //使用默认的回调连接
//  bleMultiConnector.connect(device1Address);
//  bleMultiConnector.connect(device2Address);

    //发起连接,并在意外断开后尝试自动重连可用这个方法
    bleMultiConnector.connect(device1Address,true);
    bleMultiConnector.connect(device2Address,true);

   //在发起连接时传入相关设备的回调
  //  bleMultiConnector.connect(device1Address, device5BleCallback);
  //  bleMultiConnector.connect(device2Address, device5BleCallback);

上方的callback是继承自BaseBleConnectCallback


import com.sscl.blelibrary.BaseBleConnectCallback;

public class Device1BleCallback extends BaseBleConnectCallback {
   ........
   ........
}

同时连接多个设备后,如果想要对单独某一个设备进行操作

//获取某个设备的操作类
BleDeviceController bleDeviceController = bleMultiConnector.getBleDeviceController(address);
//执行操作
bleDeviceController.writData(serviceUUID,characteristicUUID,data);
...

和BleConnector类似,在Activity返回时彻底关闭多连接器

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        //关闭所有gatt
        bleMultiConnectorWeakReference.closeAll();
    }
 

蓝牙广播

这是在安卓5.0(API21)之后加入的库,用于蓝牙BLE广播,较常用与iBeacon数据广播。以下的用法都是在API21及以上的时候使用。(iBeacon此处我就不详细去说了,请看下方的用法即可)

获取蓝牙广播实例

 private BleBroadCastor bleBroadCastor;
 //获取单例
 //bleBroadCastor =  BleManager.getBleAdvertiserInstance();

初始化

    bleAdvertiser.init()

参数设置

      //广播包中可填充的数据
      AdvertiseData advertiseData = new AdvertiseData(0x0000FFFF, bytes);
      //设置广播包数据
      bleAdvertiser.addAdvertiseDataAdvertiseRecord(advertiseData);
      //设置广播包中是否包含设备名
      bleAdvertiser.setAdvertiseDataIncludeDeviceName(true);
      //设置响应包中是否包含设备名
      bleAdvertiser.setScanResponseIncludeDeviceName(false);
      //设置广播包中是否包含功率
      bleAdvertiser.setAdvertiseDataIncludeTxPowerLevel(true);
      //设置广播模式
      bleAdvertiser.setBleAdvertiseMode(BleAdvertiseMode.LOW_LATENCY);
      //设置是否可被连接
      bleAdvertiser.setConnectable(false);
      //设置广播时间,为0代表不自动停止广播
      bleAdvertiser.setTimeOut(20000);

回调设置

 bleAdvertiser.setOnBleAdvertiseStateChangedListener(defaultOnBleAdvertiseStateChangedListener);

当设置手机广播可被连接时,需要设置此回调来处理远端设备的通讯,设置不可连接时,可以不设置回调。(即便是可连接时,不设置此回调也不会有问题,但是这样会导致无法进行任何操作)

  bleAdvertiser.setOnBluetoothGattServerCallbackListener(defaultOnBluetoothGattServerCallbackListener);

开始广播(start advertising)

  if (bleBroadCastor != null) {
            boolean b = bleBroadCastor.startAdvertising();
            Tool.warnOut(TAG, "startAdvertising = " + b);
            if (b) {
                Tool.warnOut(TAG, "广播请求发起成功(是否真的成功,在回调中查看)");
            }else {
                Tool.warnOut(TAG, "广播请求发起失败(这是真的失败了,连请求都没有发起成功)");
            }
        }

停止广播并关闭实例(stop advertising and close broadcast instance)

 /**
     * Take care of popping the fragment back stack or finishing the activity
     * as appropriate.
     */
    @Override
    public void onBackPressed() {
        super.onBackPressed();
        if (bleBroadCastor != null) {
            //停止广播
            bleBroadCastor.stopAdvertising();
            //关闭广播实例
            bleBroadCastor.close();
        }
    }

特别注意

连接与扫描

安卓手机因为系统各个厂家定制的原因,可能会有一些莫名其妙的问题。如:UUID发现后跟设备本身不一致等。这种问题通常可以通过重启蓝牙解决。但是也有那种顽固无比的手机。如:三星盖乐世3.这个手机必须要回复出厂设置才能正确发现UUID,原因是:系统记录了同一个设备地址的UUID。一旦连接的是同一个地址,UUID第一次发现之后,后续不论怎么更改设备的UUID,系统的缓存都是不会更新的。对于这种手机,只想说:别用BLE了。没救了

广播

对于手机来说,广播的时候,广播的地址会不断的变化,且不同厂商对这个变化周期有不同的设置,所以这种广播一般不推荐别人连接。仅用于广播数据却非常合适。如果有谁知道怎么关掉地址切换或设置地址不变的高手请请留下您的建议与方案。我会尽力完善

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容