Android BLE开发详解和FastBle源码解析,大专生三面蚂蚁金服

Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);

startActivityForResult(intent, 0x01);

  • 需要注意的是,第一种方法是异步的,打开蓝牙需要一段时间,调用此方法后,蓝牙不会立刻就处于开启状态。如果使用此方法后紧接者就需要进行扫描,建议维护一个阻塞线程,内部每隔一段时间查询蓝牙是否处于开启状态,外部显示等待UI引导用户等待,直至开启成功。使用第二种方法,会通过系统弹出框的形式引导用户开启,最终通过onActivityResult的形式回调通知是否开启成功。

  • 6.0及以上机型动态获取位置权限。

蓝牙打开之后,进行扫描之前,需要判断下当前设备是否是6.0及以上,如果是,需要动态获取之前在Manifest中声明的位置权限。

  • 配置扫描规则

扫描规则可以配置1个或多个,也可以不配置使用默认(扫描10秒)。扫描的时候,会根据配置的过滤选项,对扫描到的设备进行过滤,结果返回过滤后的设备。扫描时间配置为小于等于0,会实现无限扫描,直至调用BleManger.getInstance().cancelScan()来中止扫描。

BleScanRuleConfig scanRuleConfig = new BleScanRuleConfig.Builder()

.setServiceUuids(serviceUuids) // 只扫描指定的服务的设备,可选

.setDeviceName(true, names) // 只扫描指定广播名的设备,可选

.setDeviceMac(mac) // 只扫描指定mac的设备,可选

.setAutoConnect(isAutoConnect) // 连接时的autoConnect参数,可选,默认false

.setScanTimeOut(10000) // 扫描超时时间,可选,默认10秒

.build();

BleManager.getInstance().initScanRule(scanRuleConfig);

以上准备工作完成后,就可以开始进行扫描。

BleManager.getInstance().scan(new BleScanCallback() {

@Override

public void onScanStarted(boolean success) {

}

@Override

public void onLeScan(BleDevice bleDevice) {

}

@Override

public void onScanning(BleDevice bleDevice) {

}

@Override

public void onScanFinished(List scanResultList) {

}

});

onScanStarted(boolean success): 会回到主线程,参数表示本次扫描动作是否开启成功。由于蓝牙没有打开,上一次扫描没有结束等原因,会造成扫描开启失败。

onLeScan(BleDevice bleDevice):扫描过程中所有被扫描到的结果回调。由于扫描及过滤的过程是在工作线程中的,此方法也处于工作线程中。同一个设备会在不同的时间,携带自身不同的状态(比如信号强度等),出现在这个回调方法中,出现次数取决于周围的设备量及外围设备的广播间隔。

onScanning(BleDevice bleDevice):扫描过程中的所有过滤后的结果回调。与onLeScan区别之处在于:它会回到主线程;同一个设备只会出现一次;出现的设备是经过扫描过滤规则过滤后的设备。

onScanFinished(List<BleDevice> scanResultList):本次扫描时段内所有被扫描且过滤后的设备集合。它会回到主线程,相当于onScanning设备之和。

1.4. 设备信息

扫描得到的BLE外围设备,会以BleDevice对象的形式,作为后续操作的最小单元对象。它本身含有这些信息:

String getName():蓝牙广播名

String getMac():蓝牙Mac地址

byte[] getScanRecord(): 被扫描到时候携带的广播数据

int getRssi() :被扫描到时候的信号强度

后续进行设备连接、断开、判断设备状态,读写操作等时候,都会用到这个对象。可以把它理解为外围蓝牙设备的载体,所有对外围蓝牙设备的操作,都通过这个对象来传导。

1.5. 连接、断连、监控连接状态

拿到设备对象之后,可以进行连接操作。

BleManager.getInstance().connect(bleDevice, new BleGattCallback() {

@Override

public void onStartConnect() {

}

@Override

public void onConnectFail(BleException exception) {

}

@Override

public void onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status) {

}

@Override

public void onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status) {

}

});

onStartConnect():开始进行连接。

onConnectFail(BleException exception):连接不成功。

onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status):连接成功并发现服务。

onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status):连接断开,特指连接后再断开的情况。在这里可以监控设备的连接状态,一旦连接断开,可以根据自身情况考虑对BleDevice对象进行重连操作。需要注意的是,断开和重连之间最好间隔一段时间,否则可能会出现长时间连接不上的情况。此外,如果通过调用disconnect(BleDevice bleDevice)方法,主动断开蓝牙连接的结果也会在这个方法中回调,此时isActiveDisConnected将会是true。

1.6. GATT协议

BLE连接都是建立在 GATT (Generic Attribute Profile) 协议之上。GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很短的数据段被称为属性(Attribute)。它定义两个 BLE 设备通过Service 和 Characteristic 进行通信。GATT 就是使用了 ATT(Attribute Protocol)协议,ATT 协议把 Service, Characteristic以及对应的数据保存在一个查找表中,次查找表使用 16 bit ID 作为每一项的索引。

关于GATT这部分内容会在下面重点讲解。总之,中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。当连接成功之后,外围设备与中心设备之间就建立起了GATT连接。

上面讲到的connect(BleDevice bleDevice, BleGattCallback bleGattCallback)方法其实是有返回值的,这个返回值就是BluetoothGatt。当然还有其他方式可以获取BluetoothGatt对象,连接成功后,调用:

BluetoothGatt gatt = BleManager.getInstance().getBluetoothGatt(BleDevice bleDevice);

通过BluetoothGatt对象作为连接桥梁,中心设备可以获取外围设备的很多信息,以及双向通信。

首先,就可以获取这个蓝牙设备所拥有的Service和Characteristic。每一个属性都可以被定义作不同的用途,通过它们来进行协议通信。下面的方法,就是通过BluetoothGatt,查找出所有的Service和Characteristic的UUID:

List serviceList = bluetoothGatt.getServices();

for (BluetoothGattService service : serviceList) {

UUID uuid_service = service.getUuid();

List characteristicList= service.getCharacteristics();

for(BluetoothGattCharacteristic characteristic : characteristicList) {

UUID uuid_chara = characteristic.getUuid();

}

}

1.7. 协议通信

APP与设备建立了连接,并且知道了Service和Characteristic(需要与硬件协议沟通确认)之后,我们就可以通过BLE协议进行通信了。通信的桥梁,主要就是是通过 标准的或者自定义的Characteristic,中文我们称之为“特征”。我们可以从 Characteristic 读数据和写数据。这样就实现了双向的通信。站在APP作为中心设备的角度,常用于数据交互的通信方式主要有3种:接收通知、写、读,此外还有设置最大传输单元,获取实时信号强度等通信操作。

  • 接收通知

有两种方式可以接收通知,indicate和notify。indicate和notify的区别就在于,indicate是一定会收到数据,notify有可能会丢失数据。indicate底层封装了应答机制,如果没有收到中央设备的回应,会再次发送直至成功;而notify不会有central收到数据的回应,可能无法保证数据到达的准确性,优势是速度快。通常情况下,当外围设备需要不断地发送数据给APP的时候,比如血压计在测量过程中的压力变化,胎心仪在监护过程中的实时数据传输,这种频繁的情况下,优先考虑notify形式。当只需要发送很少且很重要的一条数据给APP的时候,优先考虑indicate形式。当然,从Android开发角度的出发,如果硬件放已经考虑了成熟的协议和发送方式,我们需要做的仅仅是根据其配置的数据发送方式进行相应的对接即可。

//打开notify

BleManager.getInstance().notify(

bleDevice,

uuid_service,

uuid_characteristic_notify,

new BleNotifyCallback() {

@Override

public void onNotifySuccess() {

// 打开通知操作成功

}

@Override

public void onNotifyFailure(BleException exception) {

// 打开通知操作失败

}

@Override

public void onCharacteristicChanged(byte[] data) {

// 打开通知后,设备发过来的数据将在这里出现

}

});

//关闭notify

BleManager.getInstance().stopNotify(uuid_service, uuid_characteristic_notify);

//打开indicate

BleManager.getInstance().indicate(

bleDevice,

uuid_service,

uuid_characteristic_indicate,

new BleIndicateCallback() {

@Override

public void onIndicateSuccess() {

// 打开通知操作成功

}

@Override

public void onIndicateFailure(BleException exception) {

// 打开通知操作失败

}

@Override

public void onCharacteristicChanged(byte[] data) {

// 打开通知后,设备发过来的数据将在这里出现

}

});

//关闭indicate

BleManager.getInstance().stopIndicate(uuid_service, uuid_characteristic_indicate);

  • 这里的通知操作用到了两个关键的参数,uuid_serviceuuid_characteristic_notify(或uuid_characteristic_indicate),就是上面提到的Service和Characteristic,此处以字符串的形式体现,不区分大小写。

  • 读写

BleManager.getInstance().read(

bleDevice,

uuid_service,

uuid_characteristic_read,

new BleReadCallback() {

@Override

public void onReadSuccess(byte[] data) {

// 读特征值数据成功

}

@Override

public void onReadFailure(BleException exception) {

// 读特征值数据失败

}

});

BleManager.getInstance().write(

bleDevice,

uuid_service,

uuid_characteristic_write,

data,

new BleWriteCallback() {

@Override

public void onWriteSuccess(int current, int total, byte[] justWrite) {

// 发送数据到设备成功(分包发送的情况下,可以通过方法中返回的参数可以查看发送进度)

}

@Override

public void onWriteFailure(BleException exception) {

// 发送数据到设备失败

}

});

  • 进行BLE数据相互发送的时候,一次最多能发送20个字节。如果需要发送的数据超过20个字节,有两种方法,一种是主动尝试拓宽MTU,另一种是采用分包传输的方式。框架中的write方法,当遇到数据超过20字节的情况时,默认是进行分包发送的。

  • 设置最大传输单元MTU

BleManager.getInstance().setMtu(bleDevice, mtu, new BleMtuChangedCallback() {

@Override

public void onSetMTUFailure(BleException exception) {

// 设置MTU失败

}

@Override

public void onMtuChanged(int mtu) {

// 设置MTU成功,并获得当前设备传输支持的MTU值

}

});

  • 获取设备的实时信号强度Rssi

BleManager.getInstance().readRssi(

bleDevice,

new BleRssiCallback() {

@Override

public void onRssiFailure(BleException exception) {

// 读取设备的信号强度失败

}

@Override

public void onRssiSuccess(int rssi) {

// 读取设备的信号强度成功

}

});

在BLE设备通信过程中,有几点经验分享给大家:

  • 两次操作之间最好间隔一小段时间,如100ms(具体时间可以根据自己实际蓝牙外设自行尝试延长或缩短)。举例,onConnectSuccess之后,延迟100ms再进行notify,之后再延迟100ms进行write

  • 连接及连接后的过程中,时刻关注onDisConnected方法,然后做处理。

  • 断开后如果需要重连,也请延迟一段时间,否则会造成阻塞。

2. BLE开发实践方面的理解


在分解FastBle源码之前,我首先介绍一下BLE通信一些理论知识。

2.1 蓝牙简介

蓝牙是一种近距离无线通信技术。它的特性就是近距离通信,典型距离是 10 米以内,传输速度最高可达 24 Mbps,支持多连接,安全性高,非常适合用智能设备上。

2.2 蓝牙技术的版本演进

  • 1999年发布1.0版本,目前市面上已很少见到;

  • 2002年发布1.1版本,目前市面上已很少见到;

  • 2004年发布2.0版本,目前市面上已很少见到;

  • 2007年发布的2.1版本,是之前使用最广的,也是我们所谓的经典蓝牙。

  • 2009年推出蓝牙 3.0版本,也就是所谓的高速蓝牙,传输速率理论上可高达24 Mbit/s;

  • 2010年推出蓝牙4.0版本,它是相对之前版本的集大成者,它包括经典蓝牙、高速蓝牙和蓝牙低功耗协议。经典蓝牙包括旧有蓝牙协议,高速蓝牙基于Wi-Fi,低功耗蓝牙就是BLE。

  • 2016年蓝牙技术联盟提出了新的蓝牙技术标准,即蓝牙5.0版本。蓝牙5.0针对低功耗设备速度有相应提升和优化,结合wifi对室内位置进行辅助定位,提高传输速度,增加有效工作距离,主要是针对物联网方向的改进。

2.3 Android上BLE功能的逐步演进

在Android开发过程中,版本的碎片化一直是需要考虑的问题,再加上厂商定制及蓝牙本身也和Android一样一直在发展过程中,所以对于每一个版本支持什么功能,是我们需要知道的。

  • Android 4.3 开始,开始支持BLE功能,但只支持Central Mode(中心模式)

  • Android 5.0开始,开始支持Peripheral Mode(外设模式)

中心模式和外设模式是什么意思?

  • Central Mode: Android端作为中心设备,连接其他外围设备。

  • Peripheral Mode:Android端作为外围设备,被其他中心设备连接。在Android 5.0支持外设模式之后,才算实现了两台Android手机通过BLE进行相互通信。

2.4 蓝牙的广播和扫描

以下内容部分参考自BLE Introduction

关于这部分内容,需要引入一个概念,GAP(Generic Access Profile),它用来控制设备连接和广播。GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与设备进行交互。例如 Beacon 设备就只是向外发送广播,不支持连接;小米手环就可以与中心设备建立连接。

在 GAP 中蓝牙设备可以向外广播数据包,广播包分为两部分: Advertising Data Payload(广播数据)和 Scan Response Data Payload(扫描回复),每种数据最长可以包含 31 byte。这里广播数据是必需的,因为外设必需不停的向外广播,让中心设备知道它的存在。扫描回复是可选的,中心设备可以向外设请求扫描回复,这里包含一些设备额外的信息,例如设备的名字。在 Android 中,系统会把这两个数据拼接在一起,返回一个 62 字节的数组。这些广播数据可以自己手动去解析,在 Android 5.0 也提供 ScanRecord 帮你解析,直接可以通过这个类获得有意义的数据。广播中可以有哪些数据类型呢?设备连接属性,标识设备支持的 BLE 模式,这个是必须的。设备名字,设备包含的关键 GATT service,或者 Service data,厂商自定义数据等等。

外围设备会设定一个广播间隔,每个广播间隔中,它会重新发送自己的广播数据。广播间隔越长,越省电,同时也不太容易扫描到。

刚刚讲到,GAP决定了你的设备怎样与其他设备进行交互。答案是有2种方式:

  • 完全基于广播的方式

也有些情况是不需要连接的,只要外设广播自己的数据即可。用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。使用广播这种方式最典型的应用就是苹果的 iBeacon。这是苹果公司定义的基于 BLE 广播实现的功能,可以实现广告推送和室内定位。这也说明了,APP 使用 BLE,需要定位权限。

基于非连接的,这种应用就是依赖 BLE 的广播,也叫作 Beacon。这里有两个角色,发送广播的一方叫做 Broadcaster,监听广播的一方叫 Observer。

  • 基于GATT连接的方式

大部分情况下,外设通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。这里有且仅有两个角色,发起连接的一方,叫做中心设备—Central,被连接的设备,叫做外设—Peripheral。

  • 外围设备:这一般就是非常小或者简单的低功耗设备,用来提供数据,并连接到一个更加相对强大的中心设备,例如小米手环。

  • 中心设备:中心设备相对比较强大,用来连接其他外围设备,例如手机等。

GATT 连接需要特别注意的是:GATT 连接是独占的。也就是一个 BLE 外设同时只能被一个中心设备连接。一旦外设被连接,它就会马上停止广播,这样它就对其他设备不可见了。当设备断开,它又开始广播。中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。

GATT 通信的双方是 C/S 关系。外设作为 GATT 服务端(Server),它维持了 ATT 的查找表以及 service 和 characteristic 的定义。中心设备是 GATT 客户端(Client),它向 Server 发起请求。需要注意的是,所有的通信事件,都是由客户端发起,并且接收服务端的响应。

2.5 BLE通信基础

BLE通信的基础有两个重要的概念,ATT和GATT。

  • ATT

全称 attribute protocol,中文名“属性协议”。它是 BLE 通信的基础。ATT 把数据封装,向外暴露为“属性”,提供“属性”的为服务端,获取“属性”的为客户端。ATT 是专门为低功耗蓝牙设计的,结构非常简单,数据长度很短。

  • GATT

全称 Generic Attribute Profile, 中文名“通用属性配置文件”。它是在ATT 的基础上,对 ATT 进行的进一步逻辑封装,定义数据的交互方式和含义。GATT是我们做 BLE 开发的时候直接接触的概念。

  • GATT 层级

GATT按照层级定义了4个概念:配置文件(Profile)、服务(Service)、特征(Characteristic)和描述(Descriptor)。他们的关系是这样的:Profile 就是定义了一个实际的应用场景,一个 Profile包含若干个 Service,一个 Service 包含若干个 Characteristic,一个 Characteristic 可以包含若干 Descriptor。

  • Profile

Profile 并不是实际存在于 BLE 外设上的,它只是一个被 Bluetooth SIG 或者外设设计者预先定义的 Service 的集合。例如心率Profile(Heart Rate Profile)就是结合了 Heart Rate Service 和 Device Information Service。所有官方通过 GATT Profile 的列表可以从这里找到。

  • Service

Service 是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识。 UUID 有 16 bit 的,或者 128 bit 的。16 bit 的 UUID 是官方通过认证的,需要花钱购买,128 bit 是自定义的,这个就可以自己随便设置。官方通过了一些标准 Service,完整列表在这里。以 Heart Rate Service为例,可以看到它的官方通过 16 bit UUID 是 0x180D,包含 3 个 Characteristic:Heart Rate Measurement, Body Sensor LocationHeart Rate Control Point,并且定义了只有第一个是必须的,它是可选实现的。

  • Characteristic

需要重点提一下Characteristic, 它定义了数值和操作,包含一个Characteristic声明、Characteristic属性、值、值的描述(Optional)。通常我们讲的 BLE 通信,其实就是对 Characteristic 的读写或者订阅通知。比如在实际操作过程中,我对某一个Characteristic进行读,就是获取这个Characteristic的value。

  • UUID

Service、Characteristic 和 Descriptor 都是使用 UUID 唯一标示的。

UUID 是全局唯一标识,它是 128bit 的值,为了便于识别和阅读,一般以 “8位-4位-4位-4位-12位”的16进制标示,比如“12345678-abcd-1000-8000-123456000000”。

但是,128bit的UUID 太长,考虑到在低功耗蓝牙中,数据长度非常受限的情况,蓝牙又使用了所谓的 16 bit 或者 32 bit 的 UUID,形式如下:“0000XXXX-0000-1000-8000-00805F9B34FB”。除了 “XXXX” 那几位以外,其他都是固定,所以说,其实 16 bit UUID 是对应了一个 128 bit 的 UUID。这样一来,UUID 就大幅减少了,例如 16 bit UUID只有有限的 65536(16的四次方) 个。与此同时,因为数量有限,所以 16 bit UUID 并不能随便使用。蓝牙技术联盟已经预先定义了一些 UUID,我们可以直接使用,比如“00001011-0000-1000-8000-00805F9B34FB”就一个是常见于BLE设备中的UUID。当然也可以花钱定制自定义的UUID。


3. FastBle源码解析


通过上面BLE的基础理论,我们可以分析到,BLE通信实际上就是先由客户端发起与服务端的连接,再通过服务端的找到其Characteristic进行两者间的数据交互。

在FastBle源码中,首先看BleManager中的connect()方法:

public BluetoothGatt connect(BleDevice bleDevice, BleGattCallback bleGattCallback) {

if (bleGattCallback == null) {

throw new IllegalArgumentException(“BleGattCallback can not be Null!”);

}

if (!isBlueEnable()) {

BleLog.e(“Bluetooth not enable!”);

bleGattCallback.onConnectFail(new OtherException(“Bluetooth not enable!”));

return null;

}

if (Looper.myLooper() == null || Looper.myLooper() != Looper.getMainLooper()) {

BleLog.w(“Be careful: currentThread is not MainThread!”);

}

if (bleDevice == null || bleDevice.getDevice() == null) {

bleGattCallback.onConnectFail(new OtherException(“Not Found Device Exception Occurred!”));

} else {

BleBluetooth bleBluetooth = new BleBluetooth(bleDevice);

boolean autoConnect = bleScanRuleConfig.isAutoConnect();

return bleBluetooth.connect(bleDevice, autoConnect, bleGattCallback);

}

return null;

}

这个方法将扫描到的外围设备对象传入,通过一些必要的条件判断之后,调用bleBluetooth.connect()进行连接。我们去看一下BleBluetooth这个类:

public BleBluetooth(BleDevice bleDevice) {

this.bleDevice = bleDevice;

}

上面的BleBluetooth的构造方法是传入一个蓝牙设备对象。由此可见,一个BleBluetooth可能代表你的Android与这一个外围设备整个交互过程,从开始连接,到中间数据交互,一直到断开连接的整个过程。在多连接情况下,有多少外围设备,设备池中就维护着多少个BleBluetooth对象。

MultipleBluetoothController就是控制多设备连接的。它里面有增加和移除设备的方法,如下图的addBleBluetoothremoveBleBluetooth,传入的参数就是BleBluetooth对象,验证了上面的说法。

public synchronized void addBleBluetooth(BleBluetooth bleBluetooth) {

if (bleBluetooth == null) {

return;

}

if (!bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) {

bleLruHashMap.put(bleBluetooth.getDeviceKey(), bleBluetooth);

}

}

public synchronized void removeBleBluetooth(BleBluetooth bleBluetooth) {

if (bleBluetooth == null) {

return;

}

if (bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) {

bleLruHashMap.remove(bleBluetooth.getDeviceKey());

}

}

回到BleBlutoothconnect方法:

public synchronized BluetoothGatt connect(BleDevice bleDevice,

boolean autoConnect,

BleGattCallback callback) {

addConnectGattCallback(callback);

isMainThread = Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper();

BluetoothGatt gatt;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

gatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),

autoConnect, coreGattCallback, TRANSPORT_LE);

} else {

gatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),

autoConnect, coreGattCallback);

}

if (gatt != null) {

if (bleGattCallback != null)

bleGattCallback.onStartConnect();

connectState = BleConnectState.CONNECT_CONNECTING;

}

return gatt;

}

可见,最终也是调用了原生API中的BluetoothDeviceconnectGatt()方法。在蓝牙原理分析中讲到,连接过程中要创建一个BluetoothGattCallback,用来作为回调,这个类非常重要,所有的 GATT 操作的回调都在这里。而此处的coreGattCallback应该就扮演着这个角色,它是BluetoothGattCallback的实现类对象,对操作回调结果做了封装和分发。

private BluetoothGattCallback coreGattCallback = new BluetoothGattCallback() {

@Override

public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {

super.onConnectionStateChange(gatt, status, newState);

if (newState == BluetoothGatt.STATE_CONNECTED) {

gatt.discoverServices();

} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {

closeBluetoothGatt();

BleManager.getInstance().getMultipleBluetoothController().removeBleBluetooth(BleBluetooth.this);

if (connectState == BleConnectState.CONNECT_CONNECTING) {

connectState = BleConnectState.CONNECT_FAILURE;

if (isMainThread) {

Message message = handler.obtainMessage();

message.what = BleMsg.MSG_CONNECT_FAIL;

message.obj = new BleConnectStateParameter(bleGattCallback, gatt, status);

handler.sendMessage(message);

} else {

if (bleGattCallback != null)

bleGattCallback.onConnectFail(new ConnectException(gatt, status));

}

} else if (connectState == BleConnectState.CONNECT_CONNECTED) {

connectState = BleConnectState.CONNECT_DISCONNECT;

if (isMainThread) {

Message message = handler.obtainMessage();

message.what = BleMsg.MSG_DISCONNECTED;

BleConnectStateParameter para = new BleConnectStateParameter(bleGattCallback, gatt, status);

para.setAcitive(isActiveDisconnect);

para.setBleDevice(getDevice());

message.obj = para;

handler.sendMessage(message);

} else {

if (bleGattCallback != null)

bleGattCallback.onDisConnected(isActiveDisconnect, bleDevice, gatt, status);

}

}

}

}

@Override

public void onServicesDiscovered(BluetoothGatt gatt, int status) {

super.onServicesDiscovered(gatt, status);

BleLog.i("BluetoothGattCallback:onServicesDiscovered "

  • ‘\n’ + "status: " + status

  • ‘\n’ + "currentThread: " + Thread.currentThread().getId());

if (status == BluetoothGatt.GATT_SUCCESS) {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

总结:

各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。

  • BAT大厂面试题、独家面试工具包,

  • 资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img
是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中…(img-Y59GqY5f-1712766582309)]
[外链图片转存中…(img-BBgqwsS9-1712766582309)]
[外链图片转存中…(img-vZ10RrfJ-1712766582310)]
[外链图片转存中…(img-UZLVHdK2-1712766582310)]
[外链图片转存中…(img-1Q6WqLC1-1712766582310)]
[外链图片转存中…(img-JgZg6FCO-1712766582311)]
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-MEgbJm5B-1712766582311)]

总结:

各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。

  • BAT大厂面试题、独家面试工具包,

  • 资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter
    [外链图片转存中…(img-m0Phik9i-1712766582311)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-9VcMvaCN-1712766582311)]

  • 30
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值