iOS中的蓝牙 CoreBluetooth蓝牙系列

前言

此篇作为对iOS蓝牙模块一个框架上的理解和概念 适合初次接触iOS蓝牙开发的同学 主要也是我为了对工作项目中的蓝牙开发做一个梳理和总结
后面我会封装一个蓝牙Demo 对CB API层做一层Block的封装 使用更方便 对外设数据的常见进制、大小端、文件解析等做一定Category的支持 对硬件DFU升级模式做一些处理等

iOS蓝牙.png

iOS中的蓝牙

BLE:(Bluetooth low energy)蓝牙4.0设备因为低耗电,也叫BLE
iOS中提供了4个框架用于实现蓝牙连接。

  • 1.GameKit.framework(用法简单)
    只能用于iOS设备之间的同个应用内连接,多用于游戏(eg:棋牌类),从iOS7开始过期
  • 2.MultipeerConnectivity.framework(代替1)
    只能用于iOS设备之间的连接,从iOS7开始引入,主要用于非联网状态下,通过wifi或者蓝牙进行文件共享(仅限于沙盒的文件),多用于附近无网聊天
  • 3.ExternalAccessory.framework(MFi)
    可用于第三方蓝牙设备交互,但是蓝牙设备必须经过苹果MFi认证(国内很少)
  • 4.CoreBluetooth.framework(常用 Apple推行蓝牙的核心)
    可用于第三方蓝牙设备交互,必须要支持蓝牙4.0
    硬件至少是4s,系统至少是iOS6 (现iPhoneX 蓝牙5.0)
    蓝牙4.0以低功耗著称,一般也叫BLE(Bluetooth Low Energy)
    目前应用比较多的案例:运动手环,嵌入式设备,智能家居
    我们公司对接的外设比较多 心率计、踏频、码表等运动蓝牙设备

涉及到使用的框架HealthKit/物联网HomeKit/wathOS1,2/iBeacon

使用CoreBluetooth框架做蓝牙连接传输

在iPhone上使用蓝牙有两种模式 可以一对一 也可以一对多(需服务通道不同)
现一般主要使用于第三方/自家蓝牙硬件智能产品(如:心率计设备、蓝牙音箱、蓝牙手环、蓝牙车锁等等)
蓝牙传输上限是20字节 所以大数据传输会涉及到拆包、拼包、校验等

蓝牙连接流程
  • 建立中心设备管理者
  • 扫描外设
  • 连接外设
  • 扫描外设中的服务
  • 扫描外设中的特征
  • 订阅或读取特征值
  • 获取外设中的数据或将数据写入外设
中心模式 CenterManager (一般Phone作为中心设备)

手机可以作为中心设备也可以作为外设来使用 CenterManage主要处理对蓝牙状态的控制和对外围设备的状态处理

  • 建立中心角色
  • 监听蓝牙状态
  • 扫描外设(Discover Peripheral) 接收外设蓝牙广播 (可以扫描包含制定服务的设备)
  • 连接外设(Connect Peripheral)
  • 断开连接(Disconnect)
外设模式

CenterManager 扫描链接外设成功后 启动一个Peripheral外设管理对象 负责外设数据的操作处理

  • 启动一个Peripheral外设管理对象 负责外设数据的操作处理
  • 扫描外设中的服务和特征(Discover Services And Characteristics)
  • 获取外设的services (基本服务(电池信息和设备信息)、硬件自定服务)
  • Discover指定Service下的特征 获取外设的Characteristics,
  • 通过指定特征( Characteristics)订阅(Notiy)/读取(Read)/写入(Write) 等操作
  • 获取Characteristics的Descriptor和Descriptor的值
  • 根据业务做处理

Characteristics作为蓝牙数据传输操作的做小单元

什么是服务和特征(service and characteristic)
每个设备都会有1个or多个服务
每个服务里都会有1个or多个特征
特征就是具体键值对,提供数据的地方
每个特征属性分为:读、写、通知等等

代码Coding 使用CoreBluetooth相关API

建议先看一下官方文档Core Bluetooth Communicate with Bluetooth 4.0 low-energy devices.
调试iOS蓝牙的时候,可以下个LightBlue,非常方便,网上也有仿写LightBlue的Demo,参考这两处:DarkBlue
Lightblue
工具我使用的是LightBlue 可以直接去App Store下载 硬件同事也钟爱这个App 调试服务/特征很方便

  • 导入CoreBluetooth头文件
#import <CoreBluetooth/CoreBluetooth.h>
CBCentralManager 中心设备的初始化和外设的链接
  • 初始化CBCentralManager 中心设备管理者 注意初始化的参数
    设置Delegate
    设置制定队列Queue
    设置可选参数options:可以设为nil
    也可以查看CBCentralManagerOptionShowPowerAlertKey蓝牙提醒弹窗和CBCentralManagerOptionRestoreIdentifierKey字符串,一个唯一的标示符,用来蓝牙的恢复连接的。在后台的长连接中可能会用到
    其他key可以查看这篇文章CBCentralManagerConstants
- (instancetype)init
{
    if (self = = [super init]) {
        dispatch_queue_t queue = dispatch_queue_create("com.imxingzhe.bici", 0);
        _centralManager = [[CBCentralManager alloc] initWithDelegate:self
                                                               queue:queue
                                                             options:@{CBCentralManagerOptionShowPowerAlertKey:[NSNumber numberWithBool:NO]}];
        _discoveredPeripherals = [[NSMutableArray alloc] init];
    }
    return self;
}
  • 基本API 所有的状态回调都在delegate里做处理
//主动发起扫描外设 开始扫描符合服务serviceUUIDs的外设
- (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options;
//主动停止扫描
- (void)stopScan;
//主动连接指定外设
- (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options;
//主动断开指定外设
- (void)cancelPeripheralConnection:(CBPeripheral *)peripheral;
  • Delegate的回调使用
#pragma mark - CBCentralManagerDelegate
//更新蓝牙状态
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
    // Determine the state of the peripheral
    if ([central state] == CBCentralManagerStatePoweredOff){
        NSLog(@"CoreBluetooth BLE hardware is powered off");
    }else if ([central state] == CBCentralManagerStatePoweredOn){
        NSLog(@"CoreBluetooth BLE hardware is powered on and ready");
    }else if ([central state] == CBCentralManagerStateUnauthorized){
        NSLog(@"CoreBluetooth BLE state is unauthorized");
    }else if ([central state] == CBCentralManagerStateUnknown) {
        NSLog(@"CoreBluetooth BLE state is unknown");
    }else if ([central state] == CBCentralManagerStateUnsupported) {
        NSLog(@"CoreBluetooth BLE hardware is unsupported on this platform");
    }
   
}

// 扫到设备会进入到此代理方法 对应Scan方法 advertisementData:广播包 RSSI:信号强度
- (void)centralManager:(CBCentralManager *)central
 didDiscoverPeripheral:(CBPeripheral *)peripheral
     advertisementData:(NSDictionary<NSString *,id> *)advertisementData
                  RSSI:(NSNumber *)RSSI
{

       NSLog(@"%s, line = %d, per = %@, data = %@, rssi = %@", __FUNCTION__, __LINE__, peripheral, advertisementData, RSSI);

}

//代理返回已经链接成功的peripheral 外设
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
      NSLog(@"获取链接成功的外设 可以操作外设处理数据传输");
}

//外设链接断开时会回调
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
      NSLog(@"获取断开链接的外设 处理释放业务逻辑");
}

- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
         NSLog(@"连接失败 获取错误信息 处理");
}

CBPeripheral外设的状态和数据传输处理
//外设连接状态state
typedef NS_ENUM(NSInteger, CBPeripheralState) {
    CBPeripheralStateDisconnected = 0,//断开
    CBPeripheralStateConnecting,//正在连接
    CBPeripheralStateConnected,//已连接
    CBPeripheralStateDisconnecting NS_AVAILABLE(10_13, 9_0),//正在断开连接
} NS_AVAILABLE(10_9, 7_0);

//设备写入服务类型 一般情况下都是CBCharacteristicWriteWithResponse 
typedef NS_ENUM(NSInteger, CBCharacteristicWriteType) {
    CBCharacteristicWriteWithResponse = 0,//有响应
    CBCharacteristicWriteWithoutResponse,//无响应
};

//CBPeripheral继承自CBPeer,独一的标示该设备的id
@property(readonly, nonatomic) NSUUID *identifier NS_AVAILABLE(10_13, 7_0);

//用来接收设备事件的代理
@property(weak, nonatomic, nullable) id<CBPeripheralDelegate> delegate;

//只读属性 设备名称
@property(retain, readonly, nullable) NSString *name;

//外设连接状态CBPeripheralState类型
@property(readonly) CBPeripheralState state;

//数组,内含扫描到的设备的服务
@property(retain, readonly, nullable) NSArray<CBService *> *services;

/* 
    * 1、如果值为YES,远程设备有空间发送一个没有响应的写服务.  
    *  2、如果值为 NO,如果值被设置为YES时,当前写服务被冲刷,方法peripheralIsReadyToSendWriteWithoutResponse:将会被调用
 */
@property(readonly) BOOL canSendWriteWithoutResponse;

/*!
 *  @method readRSSI
 *
 *  @discussion 当连接成功,检索当前连接的信号强度。回调方法为 peripheral:didReadRSSI:error:
 *
 *  @see        peripheral:didReadRSSI:error:
 */
- (void)readRSSI;

/*!
 *  @method discoverServices:
 *
 *  @param serviceUUIDs 需要扫描的设备的服务的id,如果为nil,则扫描所有的服务
 *
 *  @discussion         扫描发现设备所有可用的服务
 *
 *  @see            :扫描回调方法peripheral:didDiscoverServices:
 */
- (void)discoverServices:(nullable NSArray<CBUUID *> *)serviceUUIDs;

/*!
 *  @method discoverIncludedServices:forService:
 *
 *  @param includedServiceUUIDs  需要发现的服务service中的服务id列表,如果为nil,则扫描服务内所有的服务,这样的话会比较慢,不推荐
 *  @param service              服务
 *
 *  @discussion                 发现指定服务service内的服务
 *
 *  @see        回调方法:               peripheral:didDiscoverIncludedServicesForService:error:
 */
- (void)discoverIncludedServices:(nullable NSArray<CBUUID *> *)includedServiceUUIDs forService:(CBService *)service;

/*!
 *  @method discoverCharacteristics:forService:
 *
 *  @param characteristicUUIDs  数组,内含需要被发现的所有特征值类型,如果为nil,则为所有特征值。
 *  @param service              服务
 *
 *  @discussion                 发现指令服务的服务特征值
 *
 *  @see            回调方法为:          peripheral:didDiscoverCharacteristicsForService:error:
 */
- (void)discoverCharacteristics:(nullable NSArray<CBUUID *> *)characteristicUUIDs forService:(CBService *)service;

/*!
 *  @method readValueForCharacteristic:
 *
 *  @param characteristic   需要读取的服务特征值
 *
 *  @discussion             读取服务特征值的值,调用该方法则读取该方法前最新的蓝牙系统缓存的从外设读取的数据
 *
 *  @see        回调方法:           peripheral:didUpdateValueForCharacteristic:error:
 */
- (void)readValueForCharacteristic:(CBCharacteristic *)characteristic;

/*!
*  @method     maximumWriteValueLengthForType:
*
*  @discussion 获取向一个写服务可发送的最大字节数
*
*  @see        该写服务可通过调用writeValue:forCharacteristic:type:写数据
*/
- (NSUInteger)maximumWriteValueLengthForType:(CBCharacteristicWriteType)type NS_AVAILABLE(10_12, 9_0);

/*!
 *  @method writeValue:forCharacteristic:type:
 *
 *  @param data             待写数据
 *  @param characteristic   写服务特征
 *  @param type             写服务的类型(有/无响应)
 *
 *  @discussion             向指定服务写数据,
 *   1、如果指定CBCharacteristicWriteWithResponse类型,写入结果将会回调 peripheral:didWriteValueForCharacteristic:error:方法
 * 2、如果指定为CBCharacteristicWriteWithoutResponse类型,同时canSendWriteWithoutResponse为NO时,则数据将尽最大努力,但不会被保证成功。
 *                      
 */
- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type;

Delegate
/*!
*  @method peripheralDidUpdateName:
*
*  @param peripheral      需要更新名称的设备
*
*  @discussion         该方法被触发当设备的名称改变
*/
- (void)peripheralDidUpdateName:(CBPeripheral *)peripheral NS_AVAILABLE(10_9, 6_0);

/*!
 *  @method peripheral:didModifyServices:
 *
 *  @param peripheral     需要更新的设备
 *  @param invalidatedServices  The services that have been invalidated
 *
 *  @discussion         该方法触发当设备的服务改变时候

 *  服务可以被重新发现通过discoverServices: 方法
 */
- (void)peripheral:(CBPeripheral *)peripheral didModifyServices:(NSArray<CBService *> *)invalidatedServices NS_AVAILABLE(10_9, 7_0);

/*!
 *  @method peripheralDidUpdateRSSI:error:
 *
 *  @param peripheral   需要更新的设备.
 *  @param error    返回错误原因.
 *
 *  @discussion     该方法是readRSSI: 的回调
 *
 *  @deprecated         使 {@link peripheral:didReadRSSI:error:}代替了
 */
- (void)peripheralDidUpdateRSSI:(CBPeripheral *)peripheral error:(nullable NSError *)error NS_DEPRECATED(10_7, 10_13, 5_0, 8_0);

/*!
 *  @method peripheral:didReadRSSI:error:
 *
 *  @param peripheral   需要更新的设备
 *  @param RSSI         设备的RSSI.
 *
 *  @discussion         该方法是readRSSI: 的回调
 */
- (void)peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(nullable NSError *)error NS_AVAILABLE(10_13, 8_0);

/*!
 *  @method peripheral:didDiscoverServices:
 *
 *  @param peripheral   当前设备.
 *  @param error        错误原因
 *
 *  @discussion         该发放为 discoverServices:回调
 *
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error;

/*!
 *  @method peripheral:didDiscoverIncludedServicesForService:error:
 *
 *  @param peripheral   当前设备
 *  @param service      设备服务
 *  @param error        错误原因.
 *
 *  @discussion         该方法为 discoverIncludedServices:forService: 的回调
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(nullable NSError *)error;

/*!
*  @method peripheral:didDiscoverCharacteristicsForService:error:
*
*  @param peripheral   The peripheral providing this information.
*  @param service      The <code>CBService</code> object containing the characteristic(s).
*  @param error        If an error occurred, the cause of the failure.
*
*  @discussion         This method returns the result of a @link discoverCharacteristics:forService: @/link call. If the characteristic(s) were read successfully, 
*                      they can be retrieved via <i>service</i>'s <code>characteristics</code> property.
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error;

/*!
 *  @method peripheral:didUpdateValueForCharacteristic:error:
 *
 *  @param peripheral       The peripheral providing this information.
 *  @param characteristic   A <code>CBCharacteristic</code> object.
 *  @param error            If an error occurred, the cause of the failure.
 *
 *  @discussion             This method is invoked after a @link readValueForCharacteristic: @/link call, or upon receipt of a notification/indication.
 */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;

/*!
 *  @method peripheral:didWriteValueForCharacteristic:error:
 *
 *  @param peripheral       The peripheral providing this information.
 *  @param characteristic   A <code>CBCharacteristic</code> object.
 *  @param error            If an error occurred, the cause of the failure.
 *
 *  @discussion             This method returns the result of a {@link writeValue:forCharacteristic:type:} call, when the <code>CBCharacteristicWriteWithResponse</code> type is used.
 */
 - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;

/*!
 *  @method peripheral:didUpdateNotificationStateForCharacteristic:error:
 *
 *  @param peripheral       The peripheral providing this information.
 *  @param characteristic   A <code>CBCharacteristic</code> object.
 *  @param error            If an error occurred, the cause of the failure.
 *
 *  @discussion             This method returns the result of a @link setNotifyValue:forCharacteristic: @/link call. 
 */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;

/*!
 *  @method peripheral:didDiscoverDescriptorsForCharacteristic:error:
 *
 *  @param peripheral       The peripheral providing this information.
 *  @param characteristic   A <code>CBCharacteristic</code> object.
 *  @param error            If an error occurred, the cause of the failure.
 *
 *  @discussion             This method returns the result of a @link discoverDescriptorsForCharacteristic: @/link call. If the descriptors were read successfully, 
 *                          they can be retrieved via <i>characteristic</i>'s <code>descriptors</code> property.
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;

/*!
 *  @method peripheral:didUpdateValueForDescriptor:error:
 *
 *  @param peripheral       The peripheral providing this information.
 *  @param descriptor       A <code>CBDescriptor</code> object.
 *  @param error            If an error occurred, the cause of the failure.
 *
 *  @discussion             This method returns the result of a @link readValueForDescriptor: @/link call.
 */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(nullable NSError *)error;

/*!
 *  @method peripheral:didWriteValueForDescriptor:error:
 *
 *  @param peripheral       The peripheral providing this information.
 *  @param descriptor       A <code>CBDescriptor</code> object.
 *  @param error            If an error occurred, the cause of the failure.
 *
 *  @discussion             This method returns the result of a @link writeValue:forDescriptor: @/link call.
 */
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForDescriptor:(CBDescriptor *)descriptor error:(nullable NSError *)error;

/*!
 *  @method peripheralIsReadyToSendWriteWithoutResponse:
 *
 *  @param peripheral   当前设备
 *
 *  @discussion        该方法被调用,当 writeValue:forCharacteristic:type失败,设备再次可以发送服务特征值时调用
 *
 */
- (void)peripheralIsReadyToSendWriteWithoutResponse:(CBPeripheral *)peripheral;

/*!
 *  @method peripheral:didOpenL2CAPChannel:error:
 *
 *  @param peripheral       当前设备
 *  @param channel          CBL2CAPChanne
 *  @param error            错误信息
 *
 *  @discussion             该方法为openL2CAPChannel: 回调
 */
- (void)peripheral:(CBPeripheral *)peripheral didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel error:(nullable NSError *)error;

当外设与手机连接成功后 会生成一个CBPeripheral对象 用以操作外设数据等读写和监听操作
上面说过 每个外设有多个service service里有包含多个特征 层层递进下来找到我们需要操作的特征按照硬件需求进行读写或监听操作 处理外设于中心设备手机的数据命令交互

写入格式 例如
//拼接命令参数 写入硬件  一般会有文档告诉你怎么拼接 (命令串(0xB2):数据(0x04))
    NSMutableData *basicData = [[NSMutableData alloc]init];
    UInt8 head = 0xB2;
    UInt8 leng = 0x04;
    UInt8 cmd = 0x0B;
    UInt8 checksum = 0xC1;
    
    [basicData appendBytes:&head length:1];
    [basicData appendBytes:&leng length:1];
    [basicData appendBytes:&cmd length:1];
    [basicData appendBytes:&checksum length:1];
//写入命参数数据
    [self.peripheral writeValue: basicData forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithResponse];

//对应的回调代理
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error

//对应notify监听通道 和 read读取通道 一般都是固定命令发送 写法一样 比较简单 对于外设蓝牙通讯的处理其实就是围绕着读写听这三个操作来处理的

特殊机制处理(自动重连、多设备连接等)

关于Mac地址的获取

自iOS7之后,苹果不支持获取Mac地址,只能用UUID来标识设备,要注意的是同一个设备在不同手机上显示的UUID不相同,但有的设备可以通过 “180A”这个服务来发现特征,再来读取 “2A23”这个特征值,可以获得Mac地址。如果你的蓝牙设备不支持这样获取,你可以跟硬件工程师沟通,来获得Mac地址,添加一个获取地址命令或者增加一个含地址的特征值都可以很容易的获取。上面获取地址的前提都是需要先建立连接,如果一定要在扫描的时候获得Mac地址,让硬件工程师把数据写入广播包里,根据需求决定;

未完待续....

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

推荐阅读更多精彩内容

  • 这里我们具体说明一下中心模式的应用场景。主设备(手机去扫描连接外设,发现外设服务和属性,操作服务和属性的应用。一般...
    丶逝水流年阅读 2,243评论 3 4
  • 本文主要以蓝牙4.0做介绍,因为现在iOS能用的蓝牙也就是只仅仅4.0的设备 用的库就是core bluetoot...
    暮雨飞烟阅读 833评论 0 2
  • (一) iOS蓝牙开发蓝牙相关基础知识 蓝牙常见名称和缩写 MFI ======= make for ipad ...
    雷鸣1010阅读 4,999评论 2 12
  • 孔子的生平 父亲六十岁才有他,第二个儿子。母亲拜尼山后怀孕,字仲尼。 三十而立,开办学堂,(有教无类),一生弟子3...
    xiaolubobo阅读 624评论 0 2
  • 乐自万籁声, 胸生千里风。 雨落芭蕉藤, 点滴入春梦。 剑舞行云腾, 笔走流水锋。 文武双全能, 妙音助鹏程。 韵...
    北塔雪松阅读 716评论 20 105