GVKun编程网logo

[iOS/swift]蓝牙连接(swift 蓝牙连接)

2

在本文中,我们将给您介绍关于[iOS/swift]蓝牙连接的详细内容,并且为您解答swift蓝牙连接的相关问题,此外,我们还将为您提供关于AcerSwift3笔记本怎么样AcerSwift3笔记本上手

在本文中,我们将给您介绍关于[iOS/swift]蓝牙连接的详细内容,并且为您解答swift 蓝牙连接的相关问题,此外,我们还将为您提供关于Acer Swift 3笔记本怎么样 Acer Swift 3笔记本上手图赏、android – 如何最好地实现共享一项服务的两项活动(w.蓝牙连接)?、Android 蓝牙连接 ESC/POS 热敏打印机打印实例(ESC/POS指令篇)、Android 蓝牙连接 ESC/POS 热敏打印机打印实例(蓝牙连接篇)的知识。

本文目录一览:

[iOS/swift]蓝牙连接(swift 蓝牙连接)

[iOS/swift]蓝牙连接(swift 蓝牙连接)

之前做过蓝牙连接的功能,但是要么是直接在别人的基础上更改和新增功能,要么就是囫囵吞枣直接按别人的文章一步步写过去,写完了自己也还是没有头绪,仅仅是能用而已。这次借设计YModem升级工具及蓝牙多设备同时升级功能的机会,从头梳理一遍蓝牙连接的功能,并记之以文字。

一、原理

更详细信息可以查看官方蓝牙框架文档:《Core Bluetooth Programming Guide》

文章链接:蓝牙连接(swift版)

1.基本概念

1.1 蓝牙版本

蓝牙2.0

蓝牙2.0是传统蓝牙,也叫做经典蓝牙。

蓝牙2.0如要上架需进行MFI认证,使用ExternalAccessory框架。

蓝牙4.0

蓝牙4.0因为耗电低,也叫做低功耗蓝牙(BLE)。它将三种规格集于一体,包括传统蓝牙技术、高速技术和低耗能技术。

蓝牙4.0使用CoreBluetooth框架。

1.2 参数

基本参数
  • CBCentralManager:中心设备,连接硬件的设备。
  • CBPeripheral:外围设备,被连接的硬件。
  • service:服务
  • characteristic:特征

一个外设包含多个服务,而每一个服务中又包含多个特征,特征包括特征的值和特征的描述.每个服务包含多个字段,字段的权限有read(读)、write(写)、notify(通知/订阅)。

characteristic的三种操作:
  • write:发送信息给外围设备
  • notify:接收外围设备的通知
  • read:获取外围设备的信息。当部分设备为只读时,无法使用write发送信息给外围设备,但可以使用read去获取外围设备的信息。

正常多使用write和notify。

2.模式

蓝牙开发分为两种模式,中心模式(central),和外设模式(peripheral)。一般来讲,我们需要在软件内连接硬件,通过连接硬件给硬件发送指令以完成一些动作的蓝牙开发都是基于中心模式(central)模式的开发,也就是说我们开发的app是中心,我们要连接的硬件是外设。如果需要其他设备连接手机蓝牙,并对手机进行一些操作,那就是基于外设模式(peripheral)的开发。

中心模式流程

swift版:

  1. 创建中心设备(CBCentralManager)
  2. 中心设备开始扫描(scanForPeripherals)
  3. 扫描到外围设备之后, 自动调用中心设备的代理方法(didDiscoverPeripheral)
  4. 如果设备过多, 可以将扫描到的外围设备添加到数组
  5. 开始连接, 从数组中过滤出自己想要的设备, 进行连接(connectPeripheral)
  6. 连接上之后, 自动调用中心设备的代理方法(didConnectPeripheral), 在代理中, 进行查找外围设备的服务(peripheral.discoverServices)
  7. 查找到服务之后, 自动调用外围设备的代理(didDiscoverServices), 可通过UUID,查找具体的服务,查找服务(discoverCharacteristics)
  8. 查找到特征之后, 自动调用外围设备的代理(didDiscoverCharacteristics), 通过UUID找到自己想要的特征, 读取特征(readValueForCharacteristic)
  9. 读取到特征之后, 自动调用外设的代理方法(didUpdateValueForCharacteristic),在这里打印或者解析自己想要的特征值.

oc版:

  1. 建立中心角色 [[CBCentralManager alloc] initWithDelegate:self queue:nil]
  2. 扫描外设 cancelPeripheralConnection
  3. 发现外设 didDiscoverPeripheral
  4. 连接外设 connectPeripheral

    • 4.1连接失败 didFailToConnectPeripheral
    • 4.2连接断开 didDisconnectPeripheral
    • 4.3连接成功 didConnectPeripheral
  5. 扫描外设中的服务 discoverServices

    • 5.1发现并获取外设中的服务 didDiscoverServices
  6. 扫描外设对应服务的特征 discoverCharacteristics

    • 6.1发现并获取外设对应服务的特征 didDiscoverCharacteristicsForService
    • 6.2给对应特征写数据 writeValue:forCharacteristic:type:
  7. 订阅特征的通知 setNotifyValue:forCharacteristic:

    • 7.1根据特征读取数据 didUpdateValueForCharacteristic

外设模式流程

  1. 建立外设设备
  2. 设置本地外设的服务和特征
  3. 发布外设和特征
  4. 广播服务
  5. 响应中心的读写请求
  6. 发送更新的特征值,订阅中心


二、实现

常用方法记录用到的方法,代码是具体运用

2.1 前置

  • 设备的特征包含UUID,可通过UUID区分对应的特征。
  • 要用到蓝牙连接相关类的页面需导入头文件:import CoreBluetooth

变量

private let BLE_WRITE_UUID = "xxxx"
private let BLE_NOTIFY_UUID = "xxxx"

var centralManager:CBCentralManager?
///扫描到的所有设备
var aPeArray:[CBPeripheral] = []
//当前连接的设备
var pe:CBPeripheral?
var writeCh: CBCharacteristic?
var notifyCh: CBCharacteristic?

常用方法

实例化

var centralManager = CBCentralManager(delegate: self, queue: nil, options: nil)

开始扫描

centralManager.scanForPeripherals(withServices: serviceUUIDS, options: nil)

连接设备

centralManager.connect(peripheral, options: nil)
  • 连接设备之前要先设置代理,正常情况,当第一次获取外设peripheral的时候就会同时设置代理

断开连接

centralManager.cancelPeripheralConnection(peripheral)

停止扫描

centralManager.stopScan()

发现服务

peripheral.discoverServices(nil)
  • 外设连接成功的时候使用该方法发现服务
  • 一般在此处同步设置代理:peripheral.delegate = self

发现特征

peripheral.discoverCharacteristics(nil, for: service)
  • 搜索到服务之后执行该方法,将特征下的服务peripheral.services对应搜索特征

设置通知

peripheral.setNotifyValue(true, for: characteristic)
  • 当搜索到对应特征之后,可以根据条件判断是否设置及保存该特征,如特征的uuid
  • 通知的特征需要设置该项,以开启通知

2.2 代码

2.2.1 流程

实例化

将CBCenteralManager实例化:

centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey:false])

实例化完成后的回调:

func centralManagerDidUpdateState(_ central: CBCentralManager) {
    if central.state == CBManagerState.poweredOn {
            print("powered on")
            
        } else {
            if central.state == CBManagerState.poweredOff {
                print("BLE powered off")
            } else if central.state == CBManagerState.unauthorized {
                print("BLE unauthorized")
            } else if central.state == CBManagerState.unknown {
                print("BLE unknown")
            } else if central.state == CBManagerState.resetting {
                print("BLE ressetting")
            }
        }
}
  • CBCenteralManager实例化完毕,可以开始扫描外设CBPeripheral
  • 如无特殊要求,可以在此处直接进行扫描操作
扫描并发现设备

扫描设备:

centralManager.scanForPeripherals(withServices: serviceUUIDS, options: nil)

发现设备:

func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
    guard !aPeArray.contains(peripheral), let deviceName = peripheral.name, deviceName.count > 0 else {
        return
    }
    
}
  • 此处可以将搜索到的设备保存并显示出来,也可以将需要的设备直接连接
连接设备

连接设备:

centralManager.connect(peripheral, options: nil)

设备连接完成后的回调:

func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
    print("\(#function)连接外设成功。\ncentral:\(central),peripheral:\(peripheral)\n")
    // 设置代理
    peripheral.delegate = self
    // 开始发现服务
    peripheral.discoverServices(nil)
}
  • 连接完成后直接搜索对应的服务Service

设备连接失败的回调:

func centralManager(_ central: CBCentralManager, didFailToConnect peripheral:
CBPeripheral, error: Error?) {
    print("\(#function)连接外设失败\n\(String(describing:
peripheral.name))连接失败:\(String(describing: error))\n")
    // 这里可以发通知出去告诉设备连接界面连接失败
    
}

连接丢失的回调:

func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
    print("\(#function)连接丢失\n外设:\(String(describing:
peripheral.name))\n错误:\(String(describing: error))\n")
    // 这里可以发通知出去告诉设备连接界面连接丢失
    
}
搜索服务

搜索服务(设备连接成功的回调处执行):

peripheral.discoverServices(nil)

发现服务:

func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
    if let error = error  {
        print("\(#function)搜索到服务-出错\n设备(peripheral):\(String(describing:
peripheral.name)) 搜索服务(Services)失败:\(error)\n")
        return
    } else {
        print("\(#function)搜索到服务\n设备(peripheral):\(String(describing:
peripheral.name))\n")
    }
    for service in peripheral.services ?? [] {
        peripheral.discoverCharacteristics(nil, for: service)
    }
}
  • 搜索到服务之后直接搜索其对应的特征
搜索特征

搜索特征(当发现服务之后即执行搜索):

peripheral.discoverCharacteristics(nil, for: service)

发现特征

func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
    if let _ = error {
        print("\(#function)发现特征\n设备(peripheral):\(String(describing: peripheral.name))\n服务(service):\(String(describing: service))\n扫描特征(Characteristics)失败:\(String(describing: error))\n")
        return
    } else {
        print("\(#function)发现特征\n设备(peripheral):\(String(describing: peripheral.name))\n服务(service):\(String(describing: service))\n服务下的特征:\(service.characteristics ?? [])\n")
    }
    
    for characteristic in service.characteristics ?? [] {
        if characteristic.uuid.uuidString.lowercased().isEqual(BLE_WRITE_UUID) {
            pe = peripheral
            writeCh = characteristic
        } else if characteristic.uuid.uuidString.lowercased().isEqual(BLE_NOTIFY_UUID) {
            notifyCh = characteristic
            peripheral.setNotifyValue(true, for: characteristic)
        }
        //此处代表连接成功
    }
}
  • 此处可保存连接设备外设peripheral和特征characteristic,后续发送接收数据时使用
  • swift的UUID与oc的不一样,要区分大小写,故UUID最好保存为字符串
  • 连接成功可在此处设置回调
读取设备发送的数据

获取设备发送的数据:

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic:
CBCharacteristic, error: Error?) {
    if let _ = error {
        return
    }
    //拿到设备发送过来的值,可传出去并进行处理
    
}
  • 设备发送过来的数据会在此处回调,可设置闭包、协议等回调方式将值传出去
向设备发送数据

发送数据:

peripheral.writeValue(data, for: characteristic, type: .withResponse)
  • 类型根据需要填写
  • 特征characteristic需要是写入的特征,不能用其他的特征

检测发送数据是否成功:

func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
    if let error = error {
        print("\(#function)\n发送数据失败!错误信息:\(error)")
    }
}

2.2.2 完整代码下载

github代码:BleDemo

欢迎相互学习交流,也欢迎去github点个star。

Acer Swift 3笔记本怎么样 Acer Swift 3笔记本上手图赏

Acer Swift 3笔记本怎么样 Acer Swift 3笔记本上手图赏

Acer Swift 3是宏碁推出的笔记本电脑,具有轻薄时尚等元素,这里为大家带来 Acer Swift 3笔记本上手图赏 ,一起来看看。

14英寸1920*1080的显示屏幕、2.5GHz的英特尔酷睿酷睿i3、i5-7200u/i7处理器、图形128mb英特尔高清显卡620、8GB/256GB的SSD、Windows Hello、指纹识别器,处理速度快可媲美MacBook,售价仅为1398美元(约£1090/1760美元),性价比方面还是不错的。

以上就是 Acer Swift 3笔记本上手图赏 相关内容,希望对你有帮助。

android – 如何最好地实现共享一项服务的两项活动(w.蓝牙连接)?

android – 如何最好地实现共享一项服务的两项活动(w.蓝牙连接)?

现在,主要活动(Act.A)启动一个持有蓝牙连接的服务.
(它绑定服务)

该服务是BluetoothChatService的修改版(Android蓝牙示例)…
…根据MessengerService(Android Remote Messenger Sample)添加了Messenger和Handler进行了修改

活动A确保蓝牙连接到外部设备,然后启动另一项活动(行动B).

问题是让服务继续运行并顺利地为新活动提供蓝牙连接.我不知道如何:

>当从活动A切换到B时,确保服务未重新启动或重新安装
>确保消息传递功能按预期工作(从当前活动的活动)

我是否需要将服务重新绑定到新活动,以及如何确保BT连接不会丢失(由于其他服务实例)?
或者我是否需要将Messenger对象传递给新活动以与alread实例化的Service进行通信.如果是这样,我该如何做到最好?

非常感谢答案!

解决方法

使服务“粘滞”,以便它继续运行.并为您的2个活动创建一个基本Activity类.基本Activity类可以处理与服务绑定的所有常见功能,并提供正确的通信.我肯定会建议在活动暂停时取消绑定您的服务,并在活动恢复时重新绑定它们.但是这可以在公共基类活动中完成一次.

绑定到服务只应在它尚未运行时启动,如果在Resume / Pause中绑定/取消绑定,则在任何给定时间都应该只有一个活动连接到服务.

Android 蓝牙连接 ESC/POS 热敏打印机打印实例(ESC/POS指令篇)

Android 蓝牙连接 ESC/POS 热敏打印机打印实例(ESC/POS指令篇)

上一篇 主要介绍了如何通过蓝牙连接到打印机。这一篇,我们就介绍如何向打印机发送打印指令,来打印字符和图片。

1. 构造输出流

首先要明确一点,就是蓝牙连接打印机这种场景下,手机是 Client 端,打印机是 Server 端。

在上一篇的最后,我们从 BluetoothSocket 得到了一个OutputStream。这里我们做一层包装,得到一个OutputStreamWriter 对象:

OutputStreamWriter writer = new OutputStreamWriter(outputStream,"GBK");

这样做主要是为了后面可以直接输出字符串,不然只能输出 int 或 byte 数据;

2. 常用打印指令

手机通过蓝牙向打印机发送的都是纯字节流,那么打印机如何知道该打印的是一个文本,还是条形码,还是图片数据呢?

初始化打印机 :

在每次打印开始之前要调用该指令对打印机进行初始化。向打印机发送这条指令对应的代码就是:

 protected void initPrinter() throws IOException { 
    writer.write(0x1B); 
    writer.write(0x40); 
    writer.flush(); 
 }

打印文本:

没有对应指令,直接输出

protected void printText(String text) throws IOException { 
   writer.write(text);
   writer.flush();
 }

设置文本对齐方式:

对应的发送指令的代码:

  /* 设置文本对齐方式
   * @param align 打印位置 0:居左(默认) 1:居中 2:居右 
   * @throws IOException 
   */ 
  protected void setAlignPosition(int align) throws IOException { 
    writer.write(0x1B); 
    writer.write(0x61); 
    writer.write(align); 
    writer.flush(); 
  }

与初始化指令不同的是,这条指令带有一个参数n。

换行和制表符:

直接输出对应的字符:

 protected void nextLine() throws IOException { 
   writer.write("\n"); 
   writer.flush(); 
 }

 protected void printTab(int length) throws IOException { 
   for (int i = 0; i < length; i++) { 
     writer.write("\t"); 
   } 
   writer.flush(); 
 }

这两个指令在打印订单详情的时候使用最多。尤其是制表符,可以让每一列的文字对齐。

设置行间距:

n表示行间距为n个像素点,最大值256

protected void setLineGap(int gap) throws IOException { 
    writer.write(0x1B); 
    writer.write(0x33); 
    writer.write(gap); 
    writer.flush(); 
}

这个指令在后面打印图片的时候会用到。

3. 打印图片

很多小票上面都会附上一个二维码,用户扫描之后,可以获得更多的信息。因为热敏打印机只能打印黑白两色,所以首先把图片转成纯黑白的,再调用图片打印指令进行打印。

3.1 打印图片指令


这个指令的参数很多,一个一个来说:

  1. m:取值十进制 0、1、32、33。设置打印精度,0、1对应每行8个点,32、33对应每行24个点,对应最高的打印精度(其实这里也没太搞清楚取值0、1或者取值32、33的区别,只要记住取值33,对应每行24个点,后面还有用)
  2. n1,n2 : 表示图片的宽度,为什么有两个?其实只是分成了高位和低位两部分,因为每部分只有8bit,最大表示256。所以 n1 = 图片宽度 % 256,n2 = 图片宽度 / 256。假设图片宽300,那么n1=1,n2=44
  3. d1 d2 ... dk 这部分就是转换成字节流的图像数据了

3.2 图片分辨率调整

如果分辨率过大,超过了打印机可打印的最大宽度,那么超出的部分将无法打印。我试验的这台最大宽度是 384 个像素点,超过这个宽度的数据无法被打印出来。所以在开始打印之前,我们需要调整图片的分辨率。代码如下:

  /**
   * 对图片进行压缩(去除透明度)
   *
   * @param bitmapOrg
   */
  public static Bitmap compresspic(Bitmap bitmap) {
    // 获取这个图片的宽和高
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    // 指定调整后的宽度和高度
    int newWidth = 240;
    int newHeight = 240;
    Bitmap targetBmp = Bitmap.createBitmap(newWidth,newHeight,Bitmap.Config.ARGB_8888);
    Canvas targetCanvas = new Canvas(targetBmp);
    targetCanvas.drawColor(0xffffffff);
    targetCanvas.drawBitmap(bitmap,new Rect(0,width,height),newWidth,newHeight),null);
    return targetBmp;
  }

3.2 图片黑白化处理

因为能够打印的图像只有黑白两色,所以需要先做黑白化的处理。这一部分其实又细分为彩色图片->灰度图片,灰度图片->黑白图片两步。直接上代码:

  /**
   * 灰度图片黑白化,黑色是1,白色是0
   *
   * @param x  横坐标
   * @param y  纵坐标
   * @param bit 位图
   * @return
   */
  public static byte px2Byte(int x,int y,Bitmap bit) {
    if (x < bit.getWidth() && y < bit.getHeight()) {
      byte b;
      int pixel = bit.getPixel(x,y);
      int red = (pixel & 0x00ff0000) >> 16; // 取高两位
      int green = (pixel & 0x0000ff00) >> 8; // 取中两位
      int blue = pixel & 0x000000ff; // 取低两位
      int gray = RGB2Gray(red,green,blue);
      if (gray < 128) {
        b = 1;
      } else {
        b = 0;
      }
      return b;
    }
    return 0;
  }

  /**
   * 图片灰度的转化
   */
  private static int RGB2Gray(int r,int g,int b) {
    int gray = (int) (0.29900 * r + 0.58700 * g + 0.11400 * b); //灰度转化公式
    return gray;
  }

其中的灰度化转换公式是一个广为流传的公式,具体原理不明。我们直接看灰度转化为黑白的函数 px2Byte(int x,Bitmap bit)。对于一个 Bitmap 中的任意一个坐标点,取出其 RGB 三色信息后做灰度化处理,然后对于灰度小于128的,用黑色表示,灰度大于128的,用白色表示。

3.3 逐行打印图片

其实打印图片和打印文本是一样的,也是一行一行的打印。直接上代码吧,注释已经尽量详细了。

  /*************************************************************************
   * 假设一个240*240的图片,分辨率设为24,共分10行打印
   * 每一行,是一个 240*24 的点阵,每一列有24个点,存储在3个byte里面。
   * 每个byte存储8个像素点信息。因为只有黑白两色,所以对应为1的位是黑色,对应为0的位是白色
   **************************************************************************/
  /**
   * 把一张Bitmap图片转化为打印机可以打印的字节流
   *
   * @param bmp
   * @return
   */
  public static byte[] draw2PxPoint(Bitmap bmp) {
    //用来存储转换后的 bitmap 数据。为什么要再加1000,这是为了应对当图片高度无法   
    //整除24时的情况。比如bitmap 分辨率为 240 * 250,占用 7500 byte,
    //但是实际上要存储11行数据,每一行需要 24 * 240 / 8 =720byte 的空间。再加上一些指令存储的开销,
    //所以多申请 1000byte 的空间是稳妥的,不然运行时会抛出数组访问越界的异常。
    int size = bmp.getWidth() * bmp.getHeight() / 8 + 1000;
    byte[] data = new byte[size];
    int k = 0;
    //设置行距为0的指令
    data[k++] = 0x1B;
    data[k++] = 0x33;
    data[k++] = 0x00;
    // 逐行打印
    for (int j = 0; j < bmp.getHeight() / 24f; j++) {
      //打印图片的指令
      data[k++] = 0x1B;
      data[k++] = 0x2A;
      data[k++] = 33; 
      data[k++] = (byte) (bmp.getWidth() % 256); //nL
      data[k++] = (byte) (bmp.getWidth() / 256); //nH
      //对于每一行,逐列打印
      for (int i = 0; i < bmp.getWidth(); i++) {
        //每一列24个像素点,分为3个字节存储
        for (int m = 0; m < 3; m++) {
          //每个字节表示8个像素点,0表示白色,1表示黑色
          for (int n = 0; n < 8; n++) {
            byte b = px2Byte(i,j * 24 + m * 8 + n,bmp);
            data[k] += data[k] + b;
          }
          k++;
        }
      }
      data[k++] = 10;//换行
    }
    return data;
  }

4. 总结

用两篇介绍了一个比较冷门的应用,纯粹是因为自己花了很多时间去搞懂原理,所以希望记录下来。尤其是图片打印部分,废了好多纸啊哈哈哈,一个字节操作错误,打印出来就是一堆乱码。感觉和 java 的 .class 文件很像,每一个指令占用多少位,每一位表示什么都是严格规定好的,不能超出也不能缺少。

最后希望能帮到需要的人吧,感觉网上这部分资料还是比较少的。也希望大家多多支持编程小技巧。

Android 蓝牙连接 ESC/POS 热敏打印机打印实例(蓝牙连接篇)

Android 蓝牙连接 ESC/POS 热敏打印机打印实例(蓝牙连接篇)

公司的一个手机端的 CRM 项目最近要增加小票打印的功能,就是我们点外卖的时候经常会见到的那种小票。这里主要涉及到两大块的知识:

  1. 蓝牙连接及数据传输
  2. ESC/POS 打印指令

蓝牙连接不用说了,太常见了,这篇主要介绍这部分的内容。但ESC/POS 打印指令是个什么鬼?简单说,我们常见的热敏小票打印机都支持这样一种指令,只要按照指令的格式向打印机发送指令,哪怕是不同型号品牌的打印机也会执行相同的动作。比如打印一行文本,换行,加粗等都有对应的指令,这部分内容放在下一篇介绍。

本篇主要基于官方文档,相比官方文档,省去了大段的说明,更加便于快速上手。

1. 蓝牙权限

想要使用蓝牙功能,首先要在 AndroidManifest 配置文件中声明蓝牙权限:

<manifest> 
 <uses-permission android:name="android.permission.BLUetoOTH" />
 <uses-permission android:name="android.permission.BLUetoOTH_ADMIN" />
 ...
</manifest>

BLUetoOTH 权限只允许建立蓝牙连接以及传输数据,但是如果要进行蓝牙设备发现等操作的话,还需要申请 BLUetoOTH_ADMIN 权限。

2. 初始配置

这里主要用到一个类BluetoothAdapter。用法很简单,直接看代码:

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
 // Device does not support Bluetooth
}

单例模式,全局只有一个实例,只要为 null,就代表设备不支持蓝牙,那么需要有相应的处理。

如果设备支持蓝牙,那么接着检查蓝牙是否打开:

if (!mBluetoothAdapter.isEnabled()) {
 Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
 startActivityForResult(intent,REQUEST_ENABLE_BT);
}

如果蓝牙未打开,那么执行 startActivityForResult() 后,会弹出一个对话框询问是否要打开蓝牙,点击`是`之后就会自动打开蓝牙。成功打开蓝牙后就会回调到 onActivityResult()。

除了主动的打开蓝牙,还可以监听 BluetoothAdapter.ACTION_STATE_CHANGED
广播,包含EXTRA_STATEEXTRA_PREVIoUS_STATE两个 extra 字段,可能的取值包括 STATE_TURNING_ON,STATE_ON,STATE_TURNING_OFF,and STATE_OFF。含义很清楚了,不解释。

3. 发现设备

初始化完成之后,蓝牙打开了,接下来就是扫描附近的设备,只需要一句话:

mBluetoothAdapter.startdiscovery();

不过这样只是开始执行设备发现,这肯定是一个异步的过程,我们需要注册一个广播,监听发现设备的广播,直接上代码:

private final broadcastReceiver mReceiver = new broadcastReceiver() {
 public void onReceive(Context context,Intent intent) {
  String action = intent.getAction();

  // 当有设备被发现的时候会收到 action == BluetoothDevice.ACTION_FOUND 的广播
  if (BluetoothDevice.ACTION_FOUND.equals(action)) {

   //广播的 intent 里包含了一个 BluetoothDevice 对象
   BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

   //假设我们用一个 ListView 展示发现的设备,那么每收到一个广播,就添加一个设备到 adapter 里
   mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
  }
 }
};
// 注册广播监听
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver,filter); // Don't forget to unregister during onDestroy

注释已经写的很清楚了,除了 BluetoothDevice.EXTRA_DEVICE 之外,还有一个 extra 字段 BluetoothDevice.EXTRA_CLASS,可以得到一个 BluetoothClass 对象,主要用来保存设备的一些额外的描述信息,比如可以知道这是否是一个音频设备。

关于设备发现,有两点需要注意:

startdiscovery() 只能扫描到那些状态被设为 可发现 的设备。安卓设备默认是不可发现的,要改变设备为可发现的状态,需要如下操作:

Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_disCOVERABLE);
//设置可被发现的时间,00s
intent.putExtra(BluetoothAdapter.EXTRA_disCOVERABLE_DURATION,300);
startActivity(intent);

执行之后会弹出对话窗询问是否允许设备被设为可发现的状态,点击`是`之后设备即被设为可发现的状态。

startdiscovery()是一个十分耗费资源的操作,所以需要及时的调用canceldiscovery()来释放资源。比如在进行设备连接之前,一定要先调用canceldiscovery()

4. 设备配对与连接

4.1 配对

当与一个设备第一次进行连接操作的时候,屏幕会弹出提示框询问是否允许配对,只有配对成功之后,才能建立连接。

系统会保存所有的曾经成功配对过的设备信息。所以在执行startdiscovery()之前,可以先尝试查找已配对设备,因为这是一个本地信息读取的过程,所以比startdiscovery()要快得多,也避免占用过多资源。如果设备在蓝牙信号的覆盖范围内,就可以直接发起连接了。

查找配对设备的代码如下:

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
 for (BluetoothDevice device : pairedDevices) {
  mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
 }
}

代码很简单,不解释了,就是调用BluetoothAdapter.getBondedDevices()得到一个 Set<BluetoothDevice> 并遍历取得已配对的设备信息。

4.2 连接

蓝牙设备的连接和网络连接的模型十分相似,都是Client-Server 模式,都通过一个 socket 来进行数据传输。那么作为一个 Android 设备,就存在三种情况:

  1. 只作为 Client 端发起连接
  2. 只作为 Server 端等待别人发起建立连接的请求
  3. 同时作为 Client 和 Server

因为是为了下一篇介绍连接热敏打印机打印做铺垫,所以这里先讲 Android 设备作为 Client 建立连接的情况。因为打印机是不可能主动跟 Android 设备建立连接的,所以打印机必然是作为 Server 被连接。

4.2.1 作为 Client 连接

  1. 首先需要获取一个 BluetoothDevice 对象。获取的方法前面其实已经介绍过了,可以通过调用 startdiscovery()并监听广播获得,也可以通过查询已配对设备获得。
  2. 通过 BluetoothDevice.createRfcommSocketToServiceRecord(UUID) 得到BluetoothSocket 对象
  3. 通过BluetoothSocket.connect()建立连接
  4. 异常处理以及连接关闭

废话不多说,上代码:

private class ConnectThread extends Thread {
 private final BluetoothSocket mmSocket;
 private final BluetoothDevice mmDevice;

 public ConnectThread(BluetoothDevice device) {

  BluetoothSocket tmp = null;
  mmDevice = device;
  try {
   // 通过 BluetoothDevice 获得 BluetoothSocket 对象
   tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
  } catch (IOException e) { }
  mmSocket = tmp;
 }

 @Override
 public void run() {
  // 建立连接前记得取消设备发现
  mBluetoothAdapter.canceldiscovery();
  try {
   // 耗时操作,所以必须在主线程之外进行
   mmSocket.connect();
  } catch (IOException connectException) {
   //处理连接建立失败的异常
   try {
    mmSocket.close();
   } catch (IOException closeException) { }
   return;
  }
  doSomething(mmSocket);
 }

 //关闭一个正在进行的连接
 public void cancel() {
  try {
   mmSocket.close();
  } catch (IOException e) { }
 }
}

device.createRfcommSocketToServiceRecord(MY_UUID) 这里需要传入一个 UUID,这个UUID 需要格外注意一下。简单的理解,它是一串约定格式的字符串,用来唯一的标识一种蓝牙服务。

Client 发起连接时传入的 UUID 必须要和 Server 端设置的一样!否则就会报错!

如果是连接热敏打印机这种情况,不知道 Server 端设置的 UUID 是什么怎么办?
不用担心,因为一些常见的蓝牙服务协议已经有约定的 UUID。比如我们连接热敏打印机是基于 SPP 串口通信协议,其对应的 UUID 是 "00001101-0000-1000-8000-00805F9B34FB",所以实际的调用是这样:

复制代码 代码如下:

device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"))

其他常见的蓝牙服务的UUID大家可以自行搜索。如果只是用于自己的应用之间的通信的话,那么理论上可以随便定义一个 UUID,只要 server 和 client 两边使用的 UUID 一致即可。

4.2.2 作为 Server 连接

  1. 通过BluetoothAdapter.listenUsingRfcommWithServiceRecord(String,UUID)获取一个 BluetoothServerSocket 对象。这里传入的第一个参数用来设置服务的名称,当其他设备扫描的时候就会显示这个名称。UUID 前面已经介绍过了。
  2. 调用BluetoothServerSocket.accept()开始监听连接请求。这是一个阻塞操作,所以当然也要放在主线程之外进行。当该操作成功执行,即有连接建立的时候,会返回一个BluetoothSocket 对象。
  3. 调用 BluetoothServerSocket.close() 会关闭监听连接的服务,但是当前已经建立的链接并不会受影响。

还是看代码吧:

private class AcceptThread extends Thread {

 private final BluetoothServerSocket mmServerSocket;

 public AcceptThread() {

  BluetoothServerSocket tmp = null;
  try {
   // client 必须使用一样的 UUID !!!
   tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME,MY_UUID);
  } catch (IOException e) { }
  mmServerSocket = tmp;
 }

 @Override
 public void run() {
  BluetoothSocket socket = null;
  //阻塞操作
  while (true) {
   try {
    socket = mmServerSocket.accept();
   } catch (IOException e) {
    break;
   }
   //直到有有连接建立,才跳出死循环
   if (socket != null) {
    //要在新开的线程执行,因为连接建立后,当前线程可能会关闭
    doSomething(socket);
    mmServerSocket.close();
    break;
   }
  }
 }

 public void cancel() {
  try {
   mmServerSocket.close();
  } catch (IOException e) { }
 }
}

5. 数据传输

终于经过了前面的4步,万事俱备只欠东风。而最后这一部分其实是最简单的,因为就只是简单的利用 InputStream OutputStream进行数据的收发。

示例代码:

private class ConnectedThread extends Thread {
 private final BluetoothSocket mmSocket;
 private final InputStream mmInStream;
 private final OutputStream mmOutStream;

 public ConnectedThread(BluetoothSocket socket) {
  mmSocket = socket;
  InputStream tmpIn = null;
  OutputStream tmpOut = null;
  //通过 socket 得到 InputStream 和 OutputStream
  try {
   tmpIn = socket.getInputStream();
   tmpOut = socket.getoutputStream();
  } catch (IOException e) { }

  mmInStream = tmpIn;
  mmOutStream = tmpOut;
 }

 public void run() {
  byte[] buffer = new byte[1024]; // buffer store for the stream
  int bytes; // bytes returned from read()

  //不断的从 InputStream 取数据
  while (true) {
   try {
    bytes = mmInStream.read(buffer);
    mHandler.obtainMessage(MESSAGE_READ,bytes,-1,buffer)
      .sendToTarget();
   } catch (IOException e) {
    break;
   }
  }
 }

 //向 Server 写入数据
 public void write(byte[] bytes) {
  try {
   mmOutStream.write(bytes);
  } catch (IOException e) { }
 }

 public void cancel() {
  try {
   mmSocket.close();
  } catch (IOException e) { }
 }
}

下一篇介绍通过手机操作热敏打印机打印的时候,还会用到这部分内容,所以这里就先不多讲了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

关于[iOS/swift]蓝牙连接swift 蓝牙连接的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于Acer Swift 3笔记本怎么样 Acer Swift 3笔记本上手图赏、android – 如何最好地实现共享一项服务的两项活动(w.蓝牙连接)?、Android 蓝牙连接 ESC/POS 热敏打印机打印实例(ESC/POS指令篇)、Android 蓝牙连接 ESC/POS 热敏打印机打印实例(蓝牙连接篇)的相关知识,请在本站寻找。

本文标签:

上一篇iOS 模糊效果如何添加 UIBlurEffect UIBlurEffectView Swift(苹果自带模糊效果)

下一篇iOS swift 关闭包(swift包管理)