Android 蓝牙的一些知识

低功耗蓝牙

Android BLE API 简介

BluetoothAdapter

BluetoothAdapter 拥有基本的蓝牙操作,例如开启蓝牙扫描,使用已知的 MAC 地址 (BluetoothAdapter#getRemoteDevice)实例化一个 BluetoothDevice 用于连接蓝牙设备的操作等等。

BluetoothDevice

代表一个远程蓝牙设备。这个类可以让你连接所代表的蓝牙设备或者获取一些有关它的信息,例如它的名字,地址和绑定状态等等。

BluetoothGatt

这个类提供了 Bluetooth GATT 的基本功能。例如重新连接蓝牙设备,发现蓝牙设备的 Service 等等。

BluetoothGattService

这一个类通过 BluetoothGatt#getService 获得,如果当前服务不可见那么将返回一个 null。这一个类对应上面说过的 Service。我们可以通过这个类的 getCharacteristic(UUID uuid) 进一步获取 Characteristic 实现蓝牙数据的双向传输。

BluetoothGattCharacteristic

这个类对应上面提到的 Characteristic。通过这个类定义需要往外围设备写入的数据和读取外围设备发送过来的数据。

  • 扫描蓝牙

在 BluetoothAdapter 中,我们可以看到有两个扫描蓝牙的方法。第一个方法可以指定只扫描含有特定 UUID Service 的蓝牙设备,第二个方法则是扫描全部蓝牙设备。

boolean startLeScan(UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback)

boolean startLeScan(BluetoothAdapter.LeScanCallback callback)
@Override public void onLeScan(BluetoothDevice bluetoothDevice, int rssi, byte[] scanRecord) { }
 bluetoothDevice:蓝牙设备的类,可以通过这个类建立蓝牙连接获取关于这一个设备的一系列详细的参数,例如名字,MAC 地址等等。 rssi:蓝牙的信号强弱指标。 scanRecord:蓝牙广播出来的数据。

//停止扫描 void stopLeScan(BluetoothAdapter.LeScanCallback callback)

5.0 系统后添加

public void startScan(final ScanCallback callback)

mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner(); mBLEScanner.startScan(mScanCallback); //停止扫描 mBLEScanner.stopScan(mScanCallback); 
private ScanCallback mScanCallback = 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); //扫描失败后的处理。 } };
  • 连接
public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback) contex:You know autoConnect:表示是否需要自动连接。如果设置为 true, 表示如果设备断开了,会不断的尝试自动连接。设置为 false 表示只进行一次连接尝试。 callback:连接后进行的一系列操作的回调。

void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) 
status:嵌入式可以自定义的比如:19 蓝牙门锁主动断开。 newState:2(STATE_CONNECTED)连接成功。0(STATE_DISCONNECTED)连接失败。 只能是 2 或者0 
//-------源码部分---------- 
/**
             * Client connection state changed
             * @hide
             */
            @Override
            public void onClientConnectionState(int status, int clientIf,
                                                boolean connected, String address) {
                if (DBG) Log.d(TAG, "onClientConnectionState() - status=" + status
                                 + " clientIf=" + clientIf + " device=" + address);
                if (!address.equals(mDevice.getAddress())) {
                    return;
                }
                int profileState = connected ? BluetoothProfile.STATE_CONNECTED :
                                               BluetoothProfile.STATE_DISCONNECTED;

                runOrQueueCallback(new Runnable() {
                    @Override
                    public void run() {
                        if (mCallback != null) {
                            mCallback.onConnectionStateChange(BluetoothGatt.this, status,
                                                              profileState);
                        }
                    }
                });

                synchronized(mStateLock) {
                    if (connected) {
                        mConnState = CONN_STATE_CONNECTED;
                    } else {
                        mConnState = CONN_STATE_IDLE;
                    }
                }

                synchronized(mDeviceBusy) {
                    mDeviceBusy = false;
                }
            }

  • 发现服务
gatt.discoverServices() 连接成功后调用。
@Override public void onServicesDiscovered(BluetoothGatt gatt, int status) {} 
在蓝牙设备中, 其包含有多个BluetoothGattService, 而每个BluetoothGattService中又包含有多个BluetoothGattCharacteristic。
 当onServicesDiscovered()回调的 status == BluetoothGatt.GATT_SUCCESS, 可以进行获取service,根据嵌入式定义的 读写UUID 获取到readCharacteristic。 
gatt.setCharacteristicNotification(readCharacteristic, true); 不设置不会接受到数据。 此处可获取到writeCharacteristic 用于发送数据的操作。 
-------其他方式获取方法-------- BluetoothGattService getService(UUID uuid) BluetoothGattCharacteristic getCharacteristic(UUID uuid)
  • 蓝牙收到数据

app 获取到蓝牙数据的出口

@Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic ch) {} 
mReceivedData = ch.getValue(); 此处可以根据gatt 的uuid 来区分不同特征值的数据。
  • 发送数据
boolean ret = writeCharacteristic.setValue(tempData); 
gatt.writeCharacteristic(writeCharacteristic); 分包20个字节发送
  • MTU

mtu20的来源:GATT是基于ATT Protocol的,而它的 core spec里面定义了ATT的默认MTU为23个bytes,除去ATT的opcode一个字节以及ATT的handle2个字节之后,剩下的20个字节便是留给GATT的了。

public boolean requestMtu (int mtu) Added in API level 21 Request an MTU size used for a given connection. When performing a write request operation (write without response), the data sent is truncated to the MTU size. This function may be used to request a larger MTU size to be able to send more data at once. A onMtuChanged(BluetoothGatt, int, int) callback will indicate whether this operation was successful. Requires BLUETOOTH permission. Returns true, if the new MTU value has been requested successfully @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { super.onMtuChanged(gatt, mtu, status); if (status == BluetoothGatt.GATT_SUCCESS) { } }

由于十分不稳定和兼容问题,很少使用。

  • AndroidQ BLE 新增COC, 可以用于传输大数据,比如用于ota升级。

Bluetooth LE Connection Oriented Channels (CoC) Android 10 enables your app to use BLE CoC connections to transfer larger data streams between two BLE devices. This interface abstracts Bluetooth and connectivity mechanics to simplify implementation.

  • 断开连接
gatt.disconnect(); gatt.close(); gatt=null;

Read More

https://www.bluetooth.com/

https://developer.android.com/guide/topics/connectivity/bluetooth-

le.html

https://developer.android.google.cn/reference/android/bluetooth/le/

其他摘要:

Dealing with errors

Now that we dealt with successful operations we need to look at the errors. There are a bunch of cases that are actually very normal that will present themselves as ‘errors’:

  • The device disconnected itself on purpose. For example, because all data has been transferred and there is nothing else to to. You will receive status 19 (GATT_CONN_TERMINATE_PEER_USER).
  • The connection timed out and the device disconnected itself. In this case you’ll get a status 8 (GATT_CONN_TIMEOUT)
  • There was an low-level error in the communication which led to the loss of the connection. Typically you would receive a status 133 (GATT_ERROR) or a more specific error code if you are lucky!
  • The stack never managed to connect in the first place. In this case you will also receive a status 133 (GATT_ERROR)
  • The connection was lost during service discovery or bonding. In this case you will want to investigate why this happened and perhaps retry the connection.

The first two cases are totally normal and there is nothing else to do than call close() and perhaps do some internal cleanup like disposing of the BluetoothGatt object.

In the other cases, you may want to do something like informing other parts of your app or showing something in the UI. If there was a communication error you might be doing something wrong yourself. Alternatively, the device might be doing something wrong. Either way, something to deal with! It is a bit up to you to what extend you want to deal with all possible cases.

Have a look at my version in my Blessed library how I did it.

Status 133 when connecting

It is very common to see a status 133 when trying to connect to a device, especially while you are developing your code. The status 133 can have many causes and some of them you can control:

  • Make sure you always call close() when there is a disconnection. If you don’t do this you’ll get a 133 for sure next time you try.
  • Make sure you always use TRANSPORT_LE when calling connectGatt()
  • Restart your phone if you see this while developing. You may have corrupted the stack by debugging and it is in a state where it doesn’t behave normal again. Restarting your phone may fix things.
  • Make sure your device is advertising. The connectGatt with autoconnect set to false times out after 30 seconds and you will receive a 133.
  • Change the batteries of your device. Devices typically start behaving erratically when they battery level is very low.

If you have tried all of the above and still see status 133 you need to simply retry the connection! This is one of the Android bugs I never managed to understand or find a workaround for. For some reason, you sometimes get a 133 when connecting to a device but if you call close() and retry it works without a problem! I suspect there is an issue with the Android cache that causes all this and the close() call puts it back in a proper state. But I am really just guessing here…If anybody figures out how to solve this one, let me know!

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

推荐阅读更多精彩内容