Android Bluetooth BLE相关开发资源汇总

从选型到量产:U-blox BLE模块低功耗温度监测设备开发全记录 蓝牙低功耗(BLE)协议是物联网无线通信领域的核心技术之一,凭借极低的待机电流与灵活的广播/连接机制,成为电池供电设备实现无线数据交互的首选方案。BLE的低功耗特性依赖于连接间隔、从机延迟、广播周期等参数的联合优化,同时受模块射频性能与整机电源管理的影响。在冷链物流、环境监测、农业物联网等典型场景中,基于BLE的温度记录仪既能支持手机直连读取数据,又能通过网关实现云端汇聚,在部署成本与工程便利性上具有明显优势。围绕该类设备的工程化落地,U-blox NINA-B系列模块以成熟协议栈、认证齐全和射频一致性好的 阅读详情

Android开启蓝牙开关

转载自Android:Bluetooth 的打开和关闭

检查系统蓝牙是否开启
BluetoothManager bluetoothManager = (BluetoothManager) this.
getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();
mBluetoothAdapter.isEnabled();
开启系统蓝牙方式:
静默打开:
  • 注册权限:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
  • 开启方式:
mBluetoothAdapter.enable();

以上静默打开 Bluetooth 开关是调用了 BluetoothAdapter.enable() 方法,首先需要获取 BluetoothAdapter 对象,如果这个对象为 null 的话,说明当前设备不支持 Bluetooth 功能。还有以下几点需要注意:
1, 在 Nexus 5 Android 4.4.4 原生系统中,在没有任何其它管理 Bluetooth 权限的应用情况下,调用强制打开 Bluetooth 的方法,没有任何提示就直接打开 Bluetooth 了。
2,在小米手机 MI 2SC / MIUI-4.7.11 (Android 4.1.1 JRO03L) 上系统自带的 “安全中心” – “应用权限管理” – “开启蓝牙” 中,有三种设置:
允许:调用强制打开 Bluetooth 代码,没有任何提示,Bluetooth 被成功打开。
提示:会弹出提示框,提示安全警告 “ ***应用尝试开启蓝牙”,可以选择“拒绝”或“允许”,还有记住此次选择(备注:如果不记住的话,下次还会弹出同样的提示框,除非你自己去修改了应用开启蓝牙的权限)。
拒绝:调用强制打开 Bluetooth 代码,没有任何提示,Bluetooth 强制打开失败。
备注:各种手机自带的权限管理功能或者第三方权限管理应用略有不同。
3,对于 BluetoothAdapter.enable() 这个方法,API 中有以下说明 (备注:初始 API 中,如 Android 2.0 Eclair / API Level 5 中并没有这段提示)
Bluetooth should never be enabled without direct user consent. If you want to turn on Bluetooth in order to create a wireless connection, you should use the ACTION_REQUEST_ENABLE Intent, which will raise a dialog that requests user permission to turn on Bluetooth. The enable() method is provided only for applications that include a user interface for changing system settings, such as a “power manager” app.
没有直接的用户的允许绝不要开启 Bluetooth。如果你想要打开 Bluetooth 创建一个无线连接,你应当使用 ACTION_REQUEST_ENABLE Intent,这样会弹出一个提示框提示用户是否开启 Bluetooth,enable() 方法仅提供给有 UI 、更改系统设置的应用来使用,例如“电源管理”应用。
从以上官方 API 提示可以看出:不建议你调用此方法来打开 Bluetooth,至少是在没有任何用户提醒的前提下!

调用系统弹出框提示用户打开
  • 注册权限:
<uses-permission android:name="android.permission.BLUETOOTH" />
  • 开启方式:
Intent requestBluetoothOn = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
this.startActivityForResult(requestBluetoothOn, REQUEST_CODE_BLUETOOTH_ON);

对于以上弹出系统弹框提示用户打开 Bluetooth 的代码,有以下几点需要注意:
1,这种调用系统的弹出框提示用户打开 Bluetooth 的方式,一般不会受到系统或者第三方权限管理应用的阻止。只有当你不提示用户的情况下,可以理解为“偷偷摸摸”的打开 Bluetooth ,这个是被认为侵犯用户的知情权,系统或者第三方权限管理应用可能加以阻止:直接禁止不提示用户的情况下打开 Bluetooth,或者提示用户,又或者是让用户自己选择哪些应用可以强制开启 Bluetooth。而在 Nexus 5 / Android 4.4.4 原生系统强制打开 Bluetooth 是没有任何提示,并且可以成功打开。
2,弹出系统的提示框提醒用户打开 Bluetooth 的主要代码:
this.startActivityForResult(requestBluetoothOn, REQUEST_CODE_BLUETOOTH_ON);
注意:这个方法是需要 Activity 的对象来调用的!并且需要在 Activity 中重写 onActivityResult 方法来获取用户操作弹出提示框的结果!
3,这种弹出的系统弹框,根据系统的不同,UI 会有所不同。会导致用户app视觉不统一。

跳转到系统设置中让用户自己打开:
startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));

考虑到涉及用户隐私和用户体验,推荐以下方式开启 Bluetooth :
1,采用强制开启 Bluetooth 的方式打开 Bluetooth ,但是调用强制开启 Bluetooth 代码之前,我们自己在应用中提示用户,我们的应用需要开启 Bluetooth ,让用户自己选择是否开启 Bluetooth 。自己在应用中提示用户我们需要开启 Bluetooth 相对于弹出系统的提示框提示用户当前应用需要开启 Bluetooth 的优势在于我们可以控制提示的内容和提示的方式以及 UI。
2,假若用户选择了开启 Bluetooth,但是强制开启 Bluetooth 失败,比如系统自带的权限管理禁止你的应用开启 Bluetooth ,我们不去提示用户说当前系统禁止了应用开启 Bluetooth,让用户自己去解除禁止。这样显然用户体验很差。这种情况下,我们再去调用弹出系统提示框提醒用户打开 Bluetooth 即可。这种方式一般系统或者第三方应用不会禁止。
3,如果弹出系统提示框提醒用户打开 Bluetooth 有问题的话,最后采用提示用户自己去系统 Bluetooth 设置中打开 Bluetooth,跳转到系统的 Bluetooth 设置界面。

PS:在目前Android手机中,是不支持在飞行模式下开启蓝牙的。如果蓝牙已经开启,那么蓝牙的开关状态会随着飞行模式的状态而发生改变。

BLE广播数据解析

转载自BLE 广播数据解析

BLE中peripheral设备处于被发现状态时会发送广播包,peripheral设备通过广播被中心设备发现,广播中带有peripheral设备自身的相关信息。

广播包有两种: 广播包 (Advertising Data)和 响应包 (Scan Response),其中广播包是每个设备必须广播的,而响应包是可选的。 数据包的格式如下图所示(图片来自官方 Spec):
在这里插入图片描述
每个包都是 31 字节,数据包中分为有效数据(significant)和无效数据(non-significant)两部分。

  • 有效数据部分 :包含若干个广播数据单元,称为 AD Structure 。如图中所示,AD Structure 的组成是:第一个字节是长度值 Len ,表示接下来的 Len 个字节是数据部分。数据部分的第一个字节表示数据的类型 AD Type ,剩下的 Len - 1 个字节是真正的数据 AD data 。其中 AD type 非常关键,决定了 AD Data 的数据代表的是什么和怎么解析,这个在后面会详细讲;
  • 无效数据部分 :因为广播包的长度必须是 31 个 byte,如果有效数据部分不到 31 自己,剩下的就用 0 补全。这部分的数据是无效的,解释的时候,忽略即可。
AD type

所有的 AD type 的定义在文档 Core Specification Supplement 中。 AD Type 包括如下类型:

  • Flags: TYPE = 0x01。这个数据用来标识设备 LE 物理连接的功能。DATA 是 0 到多个字节的 Flag 值,每个 bit 上用 0 或者 1 来表示是否为 True。如果有任何一个 bit 不为 0,并且广播包是可连接的,就必须包含此数据。各 bit 的定义如下:

    • bit 0: LE 有限发现模式
    • bit 1: LE 普通发现模式
    • bit 2: 不支持 BR/EDR
    • bit 3: 对 Same Device Capable(Controller) 同时支持 BLE 和 BR/EDR
    • bit 4: 对 Same Device Capable(Host) 同时支持 BLE 和 BR/EDR
    • bit 5…7: 预留
  • Service UUID: 广播数据中一般都会把设备支持的 GATT Service 广播出来,用来告诉外面本设备所支持的 Service。有三种类型的 UUID:16 bit, 32bit, 128 bit。广播中,每种类型类型有有两个类别:完整和非完整的。这样就共有 6 种 AD Type。

    • 非完整的 16 bit UUID 列表: TYPE = 0x02;
    • 完整的 16 bit UUID 列表: TYPE = 0x03;
    • 非完整的 32 bit UUID 列表: TYPE = 0x04;
    • 完整的 32 bit UUID 列表: TYPE = 0x05;
    • 非完整的 128 bit UUID 列表: TYPE = 0x06;
    • 完整的 128 bit UUID 列表: TYPE = 0x07;
  • Local Name: 设备名字,DATA 是名字的字符串。 Local Name 可以是设备的全名,也可以是设备名字的缩写,其中缩写必须是全名的前面的若干字符。

    • 设备全名: TYPE = 0x08
    • 设备简称: TYPE = 0x09
  • TX Power Level: TYPE = 0x0A,表示设备发送广播包的信号强度。DATA 部分是一个字节,表示 -127 到 + 127 dBm。

  • 带外安全管理(Security Manager Out of Band):TYPE = 0x11。DATA 也是 Flag,每个 bit 表示一个功能:

    • bit 0: OOB Flag,0 表示没有 OOB 数据,1 表示有
    • bit 1: 支持 LE
    • bit 2: 对 Same Device Capable(Host) 同时支持 BLE 和 BR/EDR
    • bit 3: 地址类型,0 表示公开地址,1 表示随机地址
  • 外设(Slave)连接间隔范围:TYPE = 0x12。数据中定义了 Slave 最大和最小连接间隔,数据包含 4 个字节:

    • 前 2 字节:定义最小连接间隔,取值范围:0x0006 ~ 0x0C80,而 0xFFFF 表示未定义;
    • 后 2 字节:定义最大连接间隔,同上,不过需要保证最大连接间隔大于或者等于最小连接间隔。
  • 服务搜寻:外围设备可以要请中心设备提供相应的 Service。其数据定义和前面的 Service UUID 类似:

    • 16 bit UUID 列表: TYPE = 0x14
    • 32 bit UUID 列表: TYPE = 0x??
    • 128 bit UUID 列表: TYPE = 0x15
  • Service Data: Service 对应的数据。

    • 16 bit UUID Service: TYPE = 0x16, 前 2 字节是 UUID,后面是 Service 的数据;
    • 32 bit UUID Service: TYPE = 0x??, 前 4 字节是 UUID,后面是 Service 的数据;
    • 128 bit UUID Service: TYPE = 0x??, 前 16 字节是 UUID,后面是 Service 的数据;
  • 公开目标地址:TYPE = 0x17,表示希望这个广播包被指定的目标设备处理,此设备绑定了公开地址,DATA 是目标地址列表,每个地址 6 字节。

  • 随机目标地址:TYPE = 0x18,定义和前一个类似,表示希望这个广播包被指定的目标设备处理,此设备绑定了随机地址,DATA 是目标地址列表,每个地址 6 字节。

  • Appearance:TYPE = 0x19,DATA 是表示了设备的外观。

  • 厂商自定义数据: TYPE = 0xFF,厂商自定义的数据中,前两个字节表示厂商 ID,剩下的是厂商自己按照需求添加,里面的数据内容自己定义。

还有一些其他的数据,我这里就不一一列举了,有需要的可以从这个文档查阅 Core Specification Supplement 。

GATT

BLE技术是基于GATT进行通信的,GATT是一种属性传输协议,简单的讲可以认为是一种属性传输的应用层协议。它的结构非常简单:
在这里插入图片描述
你可以把他看成xml来理解:

  • 每个GATT由完成不同功能的Service组成;
  • 每个Service由不同的Characteristic组成;
  • 每个Characteristic由一个value和一个或者多个Descriptor组成;
    Service、Characteristic相当于标签(Service相当于他的类别,Characteristic相当于它的名字),而value才真正的包含数据,Descriptor是对这个value进行的说明和描述,当然我们可以从不同角度来描述和说明,因此可以有多个Descriptor.

这样子理解可能不够准确,下面我们来举一个简单的例子进行说明:

常见的小米手环是一个BLE设备,(假设)它包含三个Service,分别是提供设备信息的Service、提供步数的Service、检测心率的Service;
而设备信息的service中包含的characteristic包括厂商信息、硬件信息、版本信息等;而心率Service则包括心率characteristic等,而心率characteristic中的value则真正的包含心率的数据,而descriptor则是对该value的描述说明,比如value的单位啊,描述啊,权限啊等。

作者:小时不识月z
链接:https://www.jianshu.com/p/29a730795294
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

BluetoothAdapter类介绍

摘录自Android中蓝牙的基本使用----BluetoothAdapter类简介

由于网络上关于BluetoothAdapter的一些常用API函数都有了介绍,因此,我着重介绍一些BluetoothAdapter类疏忽的地方。

1,BluetoothAdapter STATE 状态值 , 即开关状态

int STATE_OFF                 蓝牙已经关闭
int STATE_ON                  蓝牙已经打开
int STATE_TURNING_OFF         蓝牙处于关闭过程中 ,关闭ing
int STATE_TURNING_O           蓝牙处于打开过程中 ,打开ing

2,BluetoothAdapter SCAN_MOD状态值 ,即扫描状态

首先说明,可以扫描其他设备的,当然它同时能被其他蓝牙设备扫码。

int  SCAN_MODE_CONNECTABLE               表明该蓝牙可以扫描其他蓝牙设备
int  SCAN_MODE_CONNECTABLE_DISCOVERABLE  表明该蓝牙设备同时可以扫码其他蓝牙设备,并且可以被其他蓝牙设备扫描到。
int  SCAN_MODE_NONE                      该蓝牙不能扫描以及被扫描。

3,获得蓝牙适配器实例

public static synchronized BluetoothAdapter getDefaultAdapter ()
功能:获得本设备的蓝牙适配器实例。
返回值:如果设备具备蓝牙功能,返回BluetoothAdapter 实例;否则,返回null对象。

4,扫描蓝牙设备

public boolean startDiscovery () 
功能: 扫描蓝牙设备
注意: 如果蓝牙没有开启,该方法会返回false,即不会开始扫描过程。

public  boolean cancelDiscovery ()
功能: 取消扫描过程。
注意: 如果蓝牙没有开启,该方法会返回false。

public boolean isDiscovering ()
功能: 是否正在处于扫描过程中。
注意: 如果蓝牙没有开启,该方法会返回false。

5,获取蓝牙相关信息

public String getName ()
功能:获取蓝牙设备Name

public String getAddress ()
功能:获取蓝牙设备的硬件地址(MAC地址),例如:00:11:22:AA:BB:CC  

public boolean setName (String name)
功能:设置蓝牙设备的Name,

public Set<BluetoothDevice> getBondedDevices ()
功能:获取与本机蓝牙所有绑定的远程蓝牙信息,以BluetoothDevice类实例(稍后讲到)返回。
注意:如果蓝牙为开启,该函数会返回一个空集合 。

public static boolean checkBluetoothAddress (String address)
功能: 验证蓝牙设备MAC地址是否有效。所有设备地址的英文字母必须大写,48位,形如:00:43:A8:23:10:F1 。
返回值: true 设备地址有效
false 设备地址无效

public BluetoothDevice getRemoteDevice (String address)
功能:以给定的MAC地址去创建一个 BluetoothDevice 类实例(代表远程蓝牙实例)。即使该蓝牙地址不可见,也会产生一个BluetoothDevice 类实例。
返回:BluetoothDevice 类实例 。注意,如果该蓝牙设备MAC地址不能被识别,其蓝牙Name为null。
异常:如果MAC  address无效,抛出IllegalArgumentException。

Android Bluetooth Low Energy (BLE) Example

转载自Android Bluetooth Low Energy (BLE) Example

Since BLE was introduced in API 18 and cannot be used on old devices, due to change of Bluetooth specifications. I suggest you to specify the min SDK version to 18 in your app. Next add these permissions and feature tags in the manifest tag of your app manifest:

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

Since I will be printing all the data in logs, there is no need for a layout file, lets have a look at the code for using Bluetooth low energy on Android:

package com.truiton.bleexample;

import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

@TargetApi(21)
public class MainActivity extends ActionBarActivity {
    private BluetoothAdapter mBluetoothAdapter;
    private int REQUEST_ENABLE_BT = 1;
    private Handler mHandler;
    private static final long SCAN_PERIOD = 10000;
    private BluetoothLeScanner mLEScanner;
    private ScanSettings settings;
    private List<ScanFilter> filters;
    private BluetoothGatt mGatt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new Handler();
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, "BLE Not Supported",
                    Toast.LENGTH_SHORT).show();
            finish();
        }
        final BluetoothManager bluetoothManager =
                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();

    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        } else {
            if (Build.VERSION.SDK_INT >= 21) {
                mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
                settings = new ScanSettings.Builder()
                        .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                        .build();
                filters = new ArrayList<ScanFilter>();
            }
            scanLeDevice(true);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
            scanLeDevice(false);
        }
    }

    @Override
    protected void onDestroy() {
        if (mGatt == null) {
            return;
        }
        mGatt.close();
        mGatt = null;
        super.onDestroy();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_ENABLE_BT) {
            if (resultCode == Activity.RESULT_CANCELED) {
                //Bluetooth not enabled.
                finish();
                return;
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    private void scanLeDevice(final boolean enable) {
        if (enable) {
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (Build.VERSION.SDK_INT < 21) {
                        mBluetoothAdapter.stopLeScan(mLeScanCallback);
                    } else {
                        mLEScanner.stopScan(mScanCallback);

                    }
                }
            }, SCAN_PERIOD);
            if (Build.VERSION.SDK_INT < 21) {
                mBluetoothAdapter.startLeScan(mLeScanCallback);
            } else {
                mLEScanner.startScan(filters, settings, mScanCallback);
            }
        } else {
            if (Build.VERSION.SDK_INT < 21) {
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
            } else {
                mLEScanner.stopScan(mScanCallback);
            }
        }
    }

    private ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            Log.i("callbackType", String.valueOf(callbackType));
            Log.i("result", result.toString());
            BluetoothDevice btDevice = result.getDevice();
            connectToDevice(btDevice);
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            for (ScanResult sr : results) {
                Log.i("ScanResult - Results", sr.toString());
            }
        }

        @Override
        public void onScanFailed(int errorCode) {
            Log.e("Scan Failed", "Error Code: " + errorCode);
        }
    };

    private BluetoothAdapter.LeScanCallback mLeScanCallback =
            new BluetoothAdapter.LeScanCallback() {
                @Override
                public void onLeScan(final BluetoothDevice device, int rssi,
                                     byte[] scanRecord) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Log.i("onLeScan", device.toString());
                            connectToDevice(device);
                        }
                    });
                }
            };

    public void connectToDevice(BluetoothDevice device) {
        if (mGatt == null) {
            mGatt = device.connectGatt(this, false, gattCallback);
            scanLeDevice(false);// will stop after first device detection
        }
    }

    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            Log.i("onConnectionStateChange", "Status: " + status);
            switch (newState) {
                case BluetoothProfile.STATE_CONNECTED:
                    Log.i("gattCallback", "STATE_CONNECTED");
                    gatt.discoverServices();
                    break;
                case BluetoothProfile.STATE_DISCONNECTED:
                    Log.e("gattCallback", "STATE_DISCONNECTED");
                    break;
                default:
                    Log.e("gattCallback", "STATE_OTHER");
            }

        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            List<BluetoothGattService> services = gatt.getServices();
            Log.i("onServicesDiscovered", services.toString());
            gatt.readCharacteristic(services.get(1).getCharacteristics().get
                    (0));
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic
                                                 characteristic, int status) {
            Log.i("onCharacteristicRead", characteristic.toString());
            gatt.disconnect();
        }
    };
}
disconnect() 和close()的区别

在进行BLE开发过程中可能会遇到操作失败等情况,这个时候可能需要断开与BLE的连接或者清理相关资源.在BluetoothGatt类中有两个相关的方法 。

  • disconnect()
  • close()

那么这个两个方法有什么区别,又该如何使用呢。
disconnect()方法:如果调用了该方法之后可以调用connect()方法进行重连,这样还可以继续进行断开前的操作。

close()方法:一但调用了该方法, 如果你想再次连接,必须调用BluetoothDevice的connectGatt()方法。 因为close()方法将释放BluetootheGatt的所有资源。

需要注意的问题:
当你需要手动断开时,调用disconnect()方法,此时断开成功后会回调onConnectionStateChange方法,在这个方法中再调用close方法释放资源。
如果在disconnect后立即调用close,会导致无法回调onConnectionStateChange方法。

常见问题:
BLE连接之后onServicesDiscovered不被调用

摘录自ble连接之后onServicesDiscovered 不被调用
问题:onServicesDiscovered never called while connecting to GATT Server

Something that has been really useful for me is to wait for about 600ms after the connection has been established and then start the service discovery.

项目中出现蓝牙连接上之后,始终不进onServicesDiscovered回调,mBluetoothGatt.discoverServices()做如下延时即可

if (newState == BluetoothProfile.STATE_CONNECTED) {
    intentAction = ACTION_GATT_CONNECTED;
    broadcastUpdate(intentAction);
    Log.i(TAG, "Connected to GATT server.");
    // Attempts to discover services after successful connection.
    //有时候发现服务不回调,需延时 https://stackoverflow.com/questions/41434555/onservicesdiscovered-never-called-while-connecting-to-gatt-server#comment70285228_41526267
    try {
        Thread.sleep(600);
        Log.i(TAG, "Attempting to start service discovery:"
                + mBluetoothGatt.discoverServices());
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

相关资源:

Android低功耗蓝牙的那点事儿

BLE低功耗蓝牙技术解析:从协议栈到实战开发全流程 无线通信协议是物联网设备实现互联互通的基础技术,其核心在于定义设备间数据交换的规则与格式。BLE(低功耗蓝牙)作为一种专为物联网优化的协议,通过分层架构和间歇性工作模式,在保证连接可靠性的同时大幅降低功耗。其协议栈包含物理层、链路层、L2CAP、ATT、GATT与GAP等关键层级,共同构建了从广播扫描到稳定连接、数据交换的完整通信流程。在工程实践中,开发者需关注连接参数优化、信道跳频同步、多设备连接管理等核心问题,并借助ESP32、nRF52等主流硬件平台与调试工具进行开发。BLE技术为智能穿戴、智能家居等 阅读详情

相关推荐

VersaLogic Android评估套件解析:从AOSP到工业嵌入式实战

在工业嵌入式领域,Android正从消费端走向工控端,其底层Linux内核与成熟应用框架的结合,让设备交互、生态复用和开发效率获得质的提升。AOSP作为开源底座,配合BSP定制、驱动适配和系统裁剪,构成了一套完整的技术链路。x86平台跑Android在性能与外设兼容性上具备独特优势,尤其适合工业HMI、医疗终端、边缘计算网关等场景。面对具体项目,开发人员还需掌握fastboot烧录、ADB调试、SELinux策略配置、OTA升级等实操要点。VersaLogic推出的Android Demo/Eval Kit

weixin_30800807的博客 268

蓝牙学习笔记之实例广播数据的解析

BLE 设备工作的第一步就是向外广播数据。广播数据中带有设备相关的信息。本文主要说一下 BLE 的广播中的数据的规范以及广播包的解析,这是我们项目中的广播数据,实际操作广播数据在程序广播初始化函数中,根据自己的需要定义广播类型和广播数据。 广播模式 BLE 中有两种角色 Central 和 Peripheral ,也就是中心设备和外围设备。中心设备可以主动连接外围设备,

一颗偏执的心 1万+

Android BLE 源码

网上找到的蓝牙安卓BLE的APP源码,解决了安卓手机BLE连接难或连接不上的问题,比安卓BLE调试助手好。

低功耗蓝牙BLE - 学习笔记

目录 前言 蓝牙的分类 BLE协议框架 关键术语及概念 GAT 简介 广播数据格式 广播网络拓扑 GATT 简介 GATT 连接的网络拓扑 GATT 通讯事务 服务和特征 简介 Service Characteristic 参考鸣谢 前言 最近由于项目需求在学习 BLE,网上Android BLE开发 的资料相比其他 Android 资料显得有些匮乏...

Hsy的博客 4507

Android 蓝牙BLE官方demo

android手机支持蓝牙4.0,scan,connection,disconnect,discover service等功能,参考官网地址:http://developer.android.com/guide/topics/connectivity/bluetooth-le.html

Android BLE 官方DEMO

本人是在AndroidStuido上导入的,此代码提供源码及本人运行后的代码。注意源码导入容易出现错误不过很容易处理。代码支持android系统4.3以上,手机支持蓝牙4.0,搜索,配对,连接,发现服务及特征值,断开连接等功能。

【Android】蓝牙开发入门笔记

开头语 该文章是我在学习蓝牙开发后自己总结出来的一些常见的方法。由于是笔记,因此可能会缺少一些细节说明,大体上仅记录一下思路。 文章目录开头语1. 经典蓝牙服务端开启服务端和客户端通信关闭客户端扫描服务端连接服务端和服务端通信关闭2. 低功耗蓝牙BLE基础知识GattServiceCharacteristics服务端开启服务端开启广播广播设置广播内容扫描回复包新增服务新增特性回复客户端请求主动通知...

大东的博客 819

Delphi 蓝牙 BLEScanner

Delphi 蓝牙 BLEScanner

Android开发之蓝牙(Bluetooth)操作(一)--扫描已经配对的蓝牙设备

一.什么是蓝牙(Bluetooth)? 1.1BuleTooth是目前使用最广泛的无线通信协议 1.2主要针对短距离设备通讯(10m) 1.3常用于连接耳机,鼠标和移动通讯设备等. 二.与蓝牙相关的API 2.1BluetoothAdapter: 代表了本地的蓝牙适配器 2.2BluetoothDevice 代表了一个远程的Bluetooth设备 三....

mmdev 714

delphi xe android 蓝牙扫描

自己写的ble 蓝牙检索程序,用于寻找蓝牙的服务uuid 和特征uuid

nordic BLE广播模式的实验

文档使用nrf52832对BLE4种模式的广播进行了实验及抓包分析,详细描述了nordic芯片如何使用通用广播,白名单广播,定向广播,不可连接广播等广播类型

5分钟掌握:Bluetooth-LE-Spam安卓蓝牙设备模拟测试全攻略

Bluetooth-LE-Spam是一款功能强大的Android蓝牙测试工具,专门用于蓝牙设备模拟和BLE广告测试。通过这款应用,你可以轻松创建虚拟蓝牙设备广告,测试目标设备的响应能力,为蓝牙设备安全检测提供专业支持。本文将从项目亮点到进阶技巧,为你提供完整的入门指南。 ## 🚀 项目核心亮点 ### 多平台设备模拟支持 - **Apple设备弹窗**:模拟新款Apple设备连接弹窗 - *

gitblog_01132的博客 806

DMTF相关协议(一):Redfish和SMBIOS

Redfish 是一种管理标准,使用超媒体 RESTful 接口中的数据模型表示。该模型以标准的、机器可读的模式表示,消息的有效负载以 JSON 表示。该协议本身利用了 OData v4。由于它是一个超媒体 API,因此能够通过一致的接口表示各种实现。它具有管理数据中心资源、处理事件、长时间任务和发现的机制。

weixin_44032634的博客 2265

Android蓝牙BLE的详细讲解PDF下载:掌握核心功能,开启智能开发新篇章

Android蓝牙BLE的详细讲解PDF下载:掌握核心功能,开启智能开发新篇章 去发现同类优质开源项目:https://gitcode.com/ 在移动应用开发领域,蓝牙技术一直是连接设备与用户的重要桥梁。今天,我们将为您推荐一款开源项目——《Android蓝牙BLE的详细讲解》PDF下载,该项目深度解析了Android平台上的蓝牙低功耗(BLE)技术,帮助开发者轻松掌握核心功能。 项目介绍 《A...

gitblog_06725的博客 555

时间卷积网络(TCN):结构+pytorch代码

文章目录TCNTCN结构1-D FCN的结构因果卷积(Causal Convolutions)膨胀因果卷积(Dilated Causal Convolutions)膨胀非因果卷积(Dilated Non-Causal Convolutions)残差块结构pytorch代码讲解 TCN   TCN(Temporal Convolutional Network)是由Shaojie Bai et al....

Leon_winter的博客 11万+

基于CNN网络的轴承故障诊断

内容参考:《基于卷积神经网络的轴承故障 诊断算法研究》–张伟 代码参考: https://github.com/AaronCosmos/wdcnn_bearning_fault_diagnosis 1 背景: 基于信号处理的特征提取+分类器的传统智能诊断算法,对专家经验要求高,设计耗时且不能保证通用性,已经不能满足机械大数据的要求。提出使用基于卷积神经网络智能诊断算法来自动完成特征提取以及故障识别。 1.1 挑战 机电产品故障诊断面临的挑战,有三大特点: (1)数据量大,专业分析人员的数量严重不足,仅依靠人

weixin_45679938的博客 7万+

BLE资料应用笔记 -- 持续更新(转载)

简而言之,蓝牙无处不在,易于使用,低耗能和低使用成本。’让我们’更深入地探索这些方面吧。 蓝牙无处不在—,您可以在几乎每一台电话、笔记本电脑 、台式电脑和平板电脑中找到蓝牙。因此,您可以便利地连接键盘、鼠标、扬声器或健身带到电话或计算机。 蓝牙低耗能—,随着 Bluetooth Smart(BLE 或蓝牙低耗能)的诞生,现在开发者可以创造小型传感器,能够依靠硬币型电池运...

weixin_30757793的博客 122

RKNN3588——YOLOv8的PT模型转RKNN模型

1. 首先克隆rknn修改后的ultralytics版本项目到本地主要是修改了源码的ultralytics/nn/modules/head.py和ultralytics/engine/exporter.py两个文件。2. 使用修改后的ultralytics对pt模型进行模型转换,此处的format=rknn代表支持rknn后续的转换,而不是用onnx,一定要注意!!!

张飞飞的博客 7172

蓝牙BLE方案选型:SoC、HCI与OS托管架构深度对比

蓝牙低功耗(BLE)技术是实现物联网设备无线通信的关键,其核心在于协议栈的分层设计与高效调度。协议栈作为设备与射频硬件间的“翻译官”,通过物理层、链路层、主机控制器接口(HCI)、属性协议(ATT)及通用属性配置文件(GATT)等协同工作,确保了数据在低功耗下的可靠传输。不同的协议栈部署方式,即架构方案,直接决定了产品的功耗、性能与开发模式。本文聚焦于三种主流实现:高度集成的SoC方案、灵活解耦的HCI分离方案以及便捷高效的操作系统托管方案,深入剖析其底层原理与技术价值。对于需要极致功耗的穿戴设备,SoC方

weixin_33755649的博客 303

YOLO26优化:原创自研 | 自研独家创新DSAM注意力 ,基于BiLevelRoutingAttention注意力升级

提出新颖的注意力DSAM(Deformable Bi-level SpatialAttention Module),创新度极佳,适合科研创新,效果秒杀CBAM,Channel Attention+Spartial Attention升级为新颖的Deformable Bi-level Attention+Spartial Attention

①答疑群聊服务;②YOLO大模型知识问答系统;③计算机视觉论文生成智能体; 365

利用baostock获取上市公司财务指标

利用baostock获取上市公司的财务指标 1、导入模块,登录baostock import baostock as bs import pandas as pd import time # 登陆系统 lg = bs.login() # 显示登陆返回信息 print('login respond error_code:'+lg.error_code) print('login respond error_msg:'+lg.error_msg) 2、查询股票代码、行业板块 # 查询股票代码 rs=.

firefox_yau的博客 2098

ESP32 BLE GATT服务端开发避坑指南:从零开始手把手教你创建自定义服务与特征

本文详细解析了ESP32 BLE GATT服务端开发的全过程,从基础架构设计到高级特性应用,涵盖UUID配置、特征属性设置、事件处理机制及长数据写入等关键环节。特别针对自定义服务开发中的常见陷阱提供解决方案,帮助开发者高效构建稳定的低功耗蓝牙服务,适用于物联网设备开发场景。

weixin_30307921的博客 468

RK3576 调试ov2735 mipi-csi摄像头

摘要:本文记录了基于RK3576平台调试OV2735 MIPI摄像头的全过程。开发环境采用Linux 6.1内核和Buildroot文件系统。调试步骤包括:1)硬件检查(电源、晶振和引脚连接);2)驱动配置(设备树节点、ISP通道设置);3)驱动验证(通过ID检测和I2C工具测试);4)推流测试(使用v4l2和gstreamer工具)。重点解决了复位引脚配置、时钟命名匹配等关键问题,并通过寄存器检查确保初始化正常。最终实现了摄像头数据流的成功输出,为嵌入式视觉应用开发提供了完整参考方案。

qq_45147279的博客 1061

Ice:3 分钟把 macOS 菜单栏图标藏好找得到的免费管理工具

Mac 上的应用越装越多,菜单栏就越来越挤:电池、蓝牙、输入法、虚拟机、开发工具……图标堆在一起,该用的找不到,不想看的又赶不走。Ice 是一个免费开源的 macOS 菜单栏管理工具,核心能力就是"藏住不用的图标、需要时一键取回",并且支持拖拽排布、搜索定位、外观定制。它只支持 macOS 14 及以上,适合被菜单栏图标困扰的 Mac 用户,尤其是 MacBook 带刘海屏、菜单栏空间本就局促的场

gitblog_01118的博客 428

边缘计算与产业生态系统

目录 文章目录目录边缘计算产业参与者边缘计算产业生态系统 边缘计算产业参与者 边缘计算领域参与者众多,主流公司以打造生态优势为目标,基于自身服务层级向产业链上下两端进行拓展,目前市场主要参与者有: 系统集成商:在产业头部,具有广泛的能力,可以集成不同生态系统公司的产品,具有一定的全球性属性,在边缘计算领域大多由云服务商、电信运营商担任。 云服务商:具有领先的云技术优势与应用生态体系,同时拥有全球性资源布局与市场资源生态,并渴望成为边缘计算主导者。 电信运营商:具有边缘基础设施优势,以及不可替代的

烟云的计算 2103

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

如何用32feet轻松实现跨平台蓝牙开发:2025年终极指南

32feet是一个强大的开源.NET库,专为个人区域网络(PAN)打造,提供跨Windows、iOS和Android平台的完整蓝牙API解决方案。无论是新手开发者还是专业团队,都能通过这个免费工具快速构建稳定高效的蓝牙应用,轻松实现设备间的数据通信与智能硬件控制。 ## ???? 为什么选择32feet进行蓝牙开发? 作为.NET生态中最成熟的蓝牙开发框架之一,32feet凭借三大核心优势脱颖而出...

gitblog_00415的博客 908

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

上一篇: 如何revert一个merged branch上所有的改动
下一篇: Android应用开发——文件目录
voidreturn
博客等级 码龄10年 53粉丝 45原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值