低功耗蓝牙Ble的详细使用流程

概述

  • 中心角色和外围角色
    在BLE中存在两个角色,一个是中心角色(Central),一个是外围角色(Peripheral),蓝牙设备或手机都可以单独作为Central或Peripheral角色。外设角色的作用是为中心角色提供各种数据,中心角色可以扫描并接收多个外设角色数据( 外围角色中的设备进行广播,中心角色的设备扫描寻找广播),数据以服务(Service)和特征(Characteristic)的形式呈现。其中Ble中心角色的API在Android 4.3得到支持,而外围角色的API在Android 5.0才得到支持
  • 协议、服务、特征、描述符
    一份协议(BluetoothGatt)由一个或多个服务(BluetoothGattService)构成,一个服务由零个或多个特征(BluetoothGattCharacteristic)构成,一个特征可以包含零个或多个描述符(BluetoothGattDescriptor)。每一个服务、特征、描述符都有一个UUID作为唯一识别符,识别符有通用的,也可以自定义,也可以随机生成,固定格式00000000-0000-0000-0000-000000000000(8-4-4-4-12),一般来说自定义的UUID只有前8位有变化,后面的基本是固定的0000-1000-8000-00805f9b34fb,所以一个自定义的UUID一般看起来就像这样 “0000????-0000-1000-8000-00805f9b34fb” 通配符就表示4个16进制数。每一个特征都有其属性和权限(Read | Write | Notify | Indicate),特征根据属性可读可写。在每个Ble蓝牙设备中,都会有两个默认的服务如下:
//Generic Access(Generic Attribute Profile 通用属性规范GATT)
service:00001801-0000-1000-8000-00805f9b34fb
characteristic:00002a05-0000-1000-8000-00805f9b34fb

//Generic Attribute (Generic Access Profile 通用接入规范GAP)
service:00001800-0000-1000-8000-00805f9b34fb
characteristic:00002a00-0000-1000-8000-00805f9b34fb
characteristic:00002a01-0000-1000-8000-00805f9b34fb
characteristic:00002aa6-0000-1000-8000-00805f9b34fb
  • 适配器,扫描器:
    每一台支持蓝牙的手机中都会有一个蓝牙适配器,由蓝牙管理器管理着,从其中获得蓝牙适配器。适配器中自带扫描器,使用扫描器可以扫描周边的蓝牙设备。

Ble中常用类

  • BluetoothDeivce:蓝牙设备,代表一个具体的蓝牙外设
  • BluetoothManager:蓝牙管理器,主要用于获取蓝牙适配器和管理所有和蓝牙相关的东西
  • BluetoothAdapter:蓝牙适配器,每一台支持蓝牙功能的手机都有一个蓝牙适配器,一般来说,只有一个,可以通过BluetoothManager获取
  • BluetoothLeScanner:蓝牙适配器里面的扫描器,用于扫描BLE外设,可以通过BluetoothAdapter获取
  • BluetoothGatt:通用属性协议,定义了BLE通讯的基本规则,就是通过把数据包装成服务和特征的约定过程,可以通过建立连接获取
  • BluetoothGattService:服务,描述了一个BLE设备的一项基本功能,由零或多个特征组构成,可以通过BluetoothGatt获取并自定义(现有的约定服务可以在bluetooth.org上找到)
  • BluetoothGattCallback:作为中央的回调类,用于回调GATT通信的各种状态和结果
  • BluetoothGattServerCallback:作为周边的回调类,用于回调GATT通信的各种状态和结果
  • BluetoothGattCharacteristic:特征,里面包含了一组或多组数据,是GATT通信中的最小数据单元。
  • BluetoothGattDescriptor:特征描述符,对特征的额外描述,包括但不仅限于特征的单位,属性等。
  • BluetoothProfile:一个通用的规范,按照这个规范来收发数据

使用流程

中心设备:判断蓝牙是否可用->打开蓝牙->开始扫描->获取被扫描到的设备->连接设备->发现服务->获取到指定特征->写入特征值
外围设备:判断蓝牙是否可用->打开蓝牙->创建广播数据->发送广播->添加服务至广播->根据监听获取写入的数据
下图是中心设备的使用流程图 来源

image.png

权限声明

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
//获取扫描结果权限,Android6.0以上需要动态权限(这两个权限是在经典蓝牙的扫描中需要声明的)
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
//或者
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 //当android:required为true的时候,app只能强制运行在支持BLE功能的设备商,为false的时候,可以运行在所有设备上,但某些方法需要手动检测,否则可能存在隐性BUG 
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>

Ble可用性判断

不是任何设备都支持BLE,最开始要确定设备是否支持,还要确定蓝牙已经打开。

  • Android系统从4.3以后才支持,这是先决条件
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
    ... ...
}
  • 判断蓝牙是否可用
//小于等于API17时直接使用BluetoothAdapter.getDefaultAdapter()来获取Adapter
private BluetoothAdapter getAdapter(){
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
            mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
            mBluetoothAdapter = mBluetoothManager.getAdapter();
        } else {
            mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        }
    return mBluetoothAdapter;
}
if(mBluetoothAdapter == null){
    //蓝牙不可用
}

//Ble不可用
private boolean checkIfSupportBle(){
    return getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
}

蓝牙开启

//开启蓝牙功能需要一小段时间,所以不能执行开启蓝牙立即执行其他操作,这时蓝牙实际还没有开启,回出现异常,所以后续操作应该在蓝牙状态广播中处理
private void enableBluetooth(){
    if (mBluetoothAdapter == null && !mBluetoothAdapter.isEnabled()) {
        //使用系统弹框来启动蓝牙,REQUEST_ENABLE_BT为自定义的开启蓝牙请求码
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);

        //或者静默打开
        bluetoothAdapter.enable();
    }
}

//静默关闭
bluetoothAdapter.disable();

蓝牙开关广播 更多蓝牙相关广播

//广播的Action
BluetoothAdapter.ACTION_STATE_CHANGED

//四种状态值    
BluetoothAdapter.STATE_ON  //已开启
BluetoothAdapter.STATE_OFF //已关闭
BluetoothAdapter.STATE_TURNING_ON //正在开启
BluetoothAdapter.STATE_TURNING_OFF //正在关闭

//当前状态值的获取
int curState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON); 

扫描具体使用流程

  • 经典通用方式
//此过程大概持续10秒,当扫描到蓝牙设备后,会发出广播,只要在需要的地方注册接收广播,就可以获得扫描结果。这种方法可以扫描出所有蓝牙设备,包括BLE,但貌似不同手机有不同体验。
private void startDiscover(){ 
    mBluetoothAdapter.startDiscover();
} 

//注册此广播,监听BluetoothDevice.ACTION_FOUND,以接收系统消息取得扫描结果 
private class DeviceReceiver extends BroadcastReceiver { 
  @Override 
  public void onReceive(Context context, Intent intent) { 
    String action = intent.getAction(); 
    if(BluetoothDevice.ACTION_FOUND.equals(action)){ 
      //这个就是所获得的蓝牙设备。
      BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 
      mDevices.add(device ); 
    } 
  } 
}
  • Ble扫描方式
//Ble蓝牙扫描方式在5.0之后发生了变更
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
    BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
    //过滤器设置
    ArrayList<ScanFilter> filters = new ArrayList<>();
    ScanFilter.Builder filterBuilder = new ScanFilter.Builder();
    filters.add(filterBuilder);

    //扫描结果回调设置
    ScanCallback scanCallback = new ScanCallback(){        
        public void onScanResult(int callbackType, ScanResult result){
            //scanResult.getDevice()取得device
        }
    };

    //扫描参数设置
    ScanSettings.Builder settingsBuilder = new ScanSettings.Builder();
    //这种扫描方式占用资源比较高,建议当应用处于前台时使用该模式
    settingsBuilder.setScanMode(SCAN_MODE_LOW_LATENCY);
    // 开启扫描,第三项参数见下面,该操作为异步操作的,但是会消耗大量资源,一般扫描时长为12秒,建议找到需要的设备后,执行取消扫描
    bluetoothLeScanner.startScan(scanCallback, filter, settingsBuilder.build());
}else{
    //扫描结果回调设置  
    BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback(){
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord){}
    };
    //该方法已被声明为过时方法 
    adapter.startLeScan(leScanCallback)
}
  • 停止扫描
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
   bluetoothLeScanner.stopScan(mScanCallback);
}else{
    bluetoothAdapter.stopLeScan(mLeScanCallback);
}

设备信息获取

  • 根据蓝牙地址获取蓝牙设备
BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(address);
  • 已绑定设备获取
private void getBoundDevices(){
    Set<BluetoothDevice> boundDevices = mBluetoothAdapter.getBondedDevices();
    for(BluetoothDevice device : boundDevices){
        //对device进行其他操作,比如连接等。
    }
}
  • 设备详细信息获取
private void showDetailOfDevice(){
    //获得设备名称,多个设备可以有同一个名称。
    String deviceName = bluetoothDevice.getName();
    //获取设备物理地址,一个设备只能有一个物理地址,每个设备都有每个设备的物理地址,无法相同。
    String deviceMacAddress =bluetoothDevice.getAddress();
    //绑定设备
   bluetoothDevice.createBond();
}

连接

  • 建立连接
//扫描回调的BluetoothDevice用于建立GATT连接,参数2是控制是否自动链接,为true的时候,当设备进入中心范围,会进行自动连接,为false时是主动连接,一般推荐使用主动连接,因为这样连接速度更快。
BluetoothGatt bluetoothGatt = bluetoothDevice.connectGatt(context, false, bluetoothGattCallback);

//如果已经连接后,但是又断开了连接,需要重新连接时
bluetoothGatt.connect();
  • 连接状态的获取
//连接状态改变会在BluetoothGattCallback的onConnectionStateChange方法中回调,同时也可以随时主动去查询
int state = bluetoothManager.getConnectionState(bluetoothDevice, BluetoothGatt.GATT);
  • 发现服务
//在建立连接成功后进行发现服务,onServicesDiscovered为发现服务的回调
bluetoothGatt.discoverServer();
  • 特征改变
//该方法一般是在发现服务后,进行设置的,设置该方法的目的是让硬件在特征数据改变的时候,发送数据给app,app则通过onCharacteristicChanged方法回调给用户,从参数中可获取到回调回来的数据
bluetoothGatt.setCharacteristicNotification(characteristic, true);
  • 连接数据回调(在非UI线程
//mBluetoothGattCallback 为所有蓝牙数据回调的处理者,也是整个蓝牙操作当中最为核心的一部分。它里面有很多方法,但并非所有都需要在开发当中用到,这里列出来只是作为部分解析,需要哪个方法,就重写哪个方法,不需要的,直接去掉
private BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        super.onConnectionStateChange(gatt, status, newState);
        //当设备与中心连接状态发生改变时,下面是已连接的判断
        if (status == BluetoothGatt.GATT_SUCCESS && newState== BluetoothProfile.STATE_CONNECTED){
            ...
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        super.onServicesDiscovered(gatt, status);
        //当发现设备服务时,会回调到此处,下面是遍历所有发现的服务
        if (status == BluetoothGatt.GATT_SUCCESS) {
            //gatt.getServices()可以获得外设的所有服务
            for (BluetoothGattService service : gatt.getServices()) {
                //每发现一个服务,我们再次遍历服务当中所包含的特征,service.getCharacteristics()可以获得当前服务所包含的所有特征
                for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
                    //通常可以把所发现的特征放进一个列表当中以便后续操作,如果你想知道每个特征都包含哪些描述符,很简单,再用一个循环去遍历每一个特征的getDescriptor()方法。
                    mCharacteristics.add(characteristic);
                    //UUID获取,或者根据UUID判断是否是自己需要的服务/特征
                    Log.i("", characteristic.getUuid().toString());//打印特征的UUID。
                }
            }
        }
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        //读取特征后回调到此处。
    }

    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        //写入特征后回调到此处,status == BluetoothGatt.GATT_SUCCESS代表写入成功
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        //当特征(值)发生变法时回调到此处。
    }

    @Override
    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        //读取描述符后回调到此处。
    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        //写入描述符后回调到此处
    }

    @Override
    public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
        //暂时没有用过。
    }

    @Override
    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
        //Rssi表示设备与中心的信号强度,发生变化时回调到此处。
    }

    @Override
    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
        //暂时没有用过。
    }
};

数据传输

  • 写入特征值,会回调onCharacteristicWrite
BluetoothGattService service = bluetoothGatt.getService(SERVICE_UUID);
BluetoothGattCharacteristic characteristic = service.getCharacteristic(CHARACTERISTIC_UUID);
characteristic.setValue(writeString); 
//设置回复方式
bluetoothGattCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
bluetoothGatt.writeCharacteristic(characteristic);
  • 写入描述符
bluetoothGattDescriptor.setValue(writeContent.getBytes());
bluetoothGatt.writeDescriptor(bluetoothGattDescriptor);
  • 读取特征值
bluetoothGatt.readCharacteristic(characteristic);

外围设备实现(被连接者/服务端 )

  • 广播的设定
private AdvertiseSettings buildAdvertiseSettings() {
    AdvertiseSettings.Builder builder = new AdvertiseSettings.Builder()
            //设置广播模式:低功耗、平衡、低延时,广播间隔时间依次越来越短
            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
            //设置是否可以连接,一般不可连接广播应用在iBeacon设备上
            .setConnectable(true)
            //设置广播的最长时间,最大时长为LIMITED_ADVERTISING_MAX_MILLIS(180秒)
            .setTimeout(10*1000)
            //设置广播的信号强度 ADVERTISE_TX_POWER_ULTRA_LOW, ADVERTISE_TX_POWER_LOW,
            //ADVERTISE_TX_POWER_MEDIUM, ADVERTISE_TX_POWER_HIGH 信号强度依次增强
            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);

    return builder.build();
}
  • 对外广播的数据(数据限制 31 Bytes)
private AdvertiseData buildAdvertiseData() {
    AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();
            //添加厂家信息,第一个参数为厂家ID(不足两个字节会自动补0,例如这里为0x34,实际数据则为34,00)
            //一般情况下无需设置,否则会出现无法被其他设备扫描到的情况
            .addManufacturerData(0x34, new byte[]{0x56})
            //添加服务进行广播,即对外广播本设备拥有的服务
            .addServiceData(...);
            //是否广播信号强度
            .setIncludeTxPowerLevel(true)
            //是否广播设备名称
            .setIncludeDeviceName(true);

    return dataBuilder.build();
}
  • 对外广播(即允许被扫描到)
private void advertise() {
 BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
    BluetoothAdapter mAdapter = bluetoothManager.getAdapter(); 
    AdvertiseCallback advertiseCallback = new AdvertiseCallback() {
        @Override
        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
            //广播成功,建议在这里开启服务
        }
        @Override
        public void onStartFailure(int errorCode) {
             //广播失败
        }
    };
    BluetoothLeAdvertiser mAdvertiser = mAdapter.getBluetoothLeAdvertiser();
    mAdvertiser.startAdvertising(buildAdvertiseSettings(), buildAdvertiseData(), mAdvertiseCallback);
}
  • 开启服务
//声明需要广播的服务的UUID和特征的UUID,注意不要占用蓝牙设备默认的UUID
UUID UUID_SERVICE = UUID.fromString("00001354-0000-1000-8000-00805f9b34fb");
UUID UUID_CHARACTERISTIC = UUID.fromString("00001355-0000-1000-8000-00805f9b34fb");
UUID UUID_DESCRIPTOR = UUID.fromString("00001356-0000-1000-8000-00805f9b34fb");

//外围设备状态、数据回调,详情见后面
BluetoothGattServerCallback serverCallback = new BluetoothGattServerCallback() {...};
//GATT协议服务
BluetoothGattServer server = bluetoothManager.openGattServer(this, serverCallback);

//创建一个服务
BluetoothGattService service = new BluetoothGattService(UUID_SERVICE,
        BluetoothGattService.SERVICE_TYPE_PRIMARY);

//创建一个特征,首先此characteristic属性满足BluetoothGattCharacteristic.PROPERTY_WRITY或BluetoothGattCharacteristic.PROPERTY_WRITY_NO_RESPONSE,
//如果其property都不包含这两个,写特征值writeCharacteristic()函数直接返回false,什么都不做处理。其次此characteristic权限应满足
//BluetoothGattCharacteristic.PERMISSION_WRITE,否则onCharacteristicWrite()回调收到GATT_WRITE_NOT_PERMITTED回应
//如果需要既能读,又能写,则可以参考如下写法
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(Constants.UUID_CHARACTERISTIC,
        BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE,
        BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);

//创建一个描述符
BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(UUID_DESCRIPTOR,
        BluetoothGattDescriptor.PERMISSION_READ);

//将描述符添加到特征中,一个特征可以包含0至多个描述符
characteristic.addDescriptor(descriptor);
//将特征添加到服务中,一个服务可以包含1到多个特征
service.addCharacteristic(characteristic);
server.addService(service);
  • BluetoothGattServerCallback
public abstract class BluetoothGattServerCallback {
    public BluetoothGattServerCallback() {
    }

    public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
        //连接状态被改变
    }

    public void onServiceAdded(int status, BluetoothGattService service) {
        //添加服务
    }

    public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
        //被读取特征
    }

    public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
        //被写入数据,其中device是写入的设备,value是写入的值,responseNeeded指是否需要恢复,如果需要恢复则调用gattServer.sendResponse()方法回复
    }

    public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
        //被读取描述符
    }

    public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
        //被写入描述符
    }

    public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
    }

    @Override
    public void onNotificationSent(BluetoothDevice device, int status) {
    }

    @Override
    public void onMtuChanged(BluetoothDevice device, int mtu) {
    }

    @Override
    public void onPhyUpdate(BluetoothDevice device, int txPhy, int rxPhy, int status) {
    }

    @Override
    public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) {
    }
}
  • 广播数据格式
    广播数据(或者扫描应答数据)由一个一个的AD Structure组成,对于未满31bytes的其它数据,则填充为0;每个AD Structure由两部分组成:1byte的长度信息(Data的长度),和剩余的Data信息;
    Data信息又由两部分组成:AD Type(长度不定)指示该AD Structure的类型,以及具体的AD Data。


    image.png

    例如:

02 01 06 03 03 aa fe 17 16 aa fe 00 -10 00 01 02 03 04 05 06 07 08 09 0a 0b 0e 0f 00 00 00 00

02 01 06是一个AD Structure:Data的长度是02;Data是01 06;AD Type是01(Flags);AD Data是06,表明支持General Discoverable Mode、不支持BR/EDR。
03 03 aa fe是一个AD Structure:Data的长度是03;Data是03 aa fe;AD Type是03(16 bits的Service UUID);AD Data是aa fe,是Eddystone profile的Service UUID。AD Type查询

注意

  • 外围设备广播的数据最多31 byte
  • 写特征一次最多写入20 byte
  • 单次扫描时间不宜过长(建议10~15秒)
  • BluetoothGattCallback的回调是在非UI线程
  • 每个设备所能使用的Gatt连接是有限个数的,所以应该断开连接时也关闭GattbluetoothGatt.close(),然后重新连接
  • 外围设备蓝牙关闭后,中心设备的BluetoothGattCallback的onConnectionStateChange会回调状态码status = 19newState = 0(断开连接)
  • 外围设备蓝牙关闭后,中心设备再进行连接,BluetoothGattCallback的onConnectionStateChange会回调状态码133,并且当外围设备蓝牙开启后再调用中心设备的bluetoothGatt.connect()不能重新连接,仍然是133错误,所以建议针对133错误进行关闭gatt并释放资源

源码地址

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

推荐阅读更多精彩内容

  • 背景 蓝牙历史说到蓝牙,就不得不说下蓝牙技术联盟(Bluetooth SIG),它负责蓝牙规范制定和推广的国际组织...
    徐正峰阅读 12,227评论 6 33
  • 因为自己的项目中有用到了蓝牙相关的功能,所以之前也断断续续地针对蓝牙通信尤其是BLE通信进行了一番探索,整理出了一...
    陈利健阅读 113,529评论 172 294
  • 前言: 本文主要描述Android BLE的一些基础知识及相关操作流程,不牵扯具体的业务实现,其中提供了针对广播包...
    幻影宇寰阅读 5,310评论 6 19
  • 蓝牙 蓝牙的波段为2400-2483.5MHz(包括防护频带)。这是全球范围内无需取得执照(但定不是无管制的)的工...
    苏永茂阅读 6,103评论 0 11
  • 安卓4.3(API 18)为BLE的核心功能提供平台支持和API,App可以利用它来发现设备、查询服务和读写特性。...
    风雨byt阅读 13,991评论 3 43