通信硬件NFC的文章,虽然可以在Android系统中通过非直接接触的形式与支持NFC硬件的设备通信,但是也只能交互一些简短的标签内容,对大量的持续性数据,却并不能很好的支持。因此针对这个弊端,可以考虑使用支持Bluetooth技术的硬件。

Android系统支持传统的Bluetooth技术,其实现功能不仅可以传输数据,还可以传输并执行远程控制指令。在Android4.3 即API 18 及以后的版本中,低功耗的Bluetooth技术(简称为BLE)取自传统Bluetooth的核心功能,可以更省功耗并支持数据传输功能。在传统蓝牙技术中,应用程序所持有的蓝牙设备可以作为蓝牙服务端,开启蓝牙等待处理其他蓝牙设备的接入请求;也可以作为蓝牙客户端,开启蓝牙查找附近的蓝牙设备并请求接入。而在BLE技术中,应用程序所持有的蓝牙设备只能作为GATT客户端,连接其他类似蓝牙耳机等BLE设备。下面应用程序将会以这三种身份分别说明。

权限声明

使用Bluetooth技术,需要在清单文件中声明蓝牙相关权限。申请权限使用<uses-permission />标签,并设置其android:name属性为对应的权限名。蓝牙相关权限中主要有三种权限,申请后可分别对应执行不同功能。首先是可以执行蓝牙通信的基础权限,其值为Manifest.permission.BLUETOOTH="android.permission.BLUETOOTH";在需要通过蓝牙获取位置信息的应用程序中,还需要获取位置权限,其值为Manifest.permission.ACCESS_FINE_LOCATION="android.permission.ACCESS_FINE_LOCATION";另外如果需要使用蓝牙的远程控制功能,需要蓝牙管理员权限,其值为Manifest.permission.BLUETOOTH_ADMIN="android.permission.BLUETOOTH_ADMIN"

使用流程

由于一个Android系统的设备上只能使用唯一的一个蓝牙硬件,因此应用程序中可以通过android.bluetooth.BluetoothAdapter蓝牙适配器操作底层的蓝牙硬件。

设置蓝牙

调用BluetoothAdapter中的静态方法getDefaultAdapter()可以获取到BluetoothAdapter蓝牙适配器对象。但是,从Android 12即API 31开始就废弃了上述方法,取而代之的是借助android.bluetooth.BluetoothManager蓝牙管理类。在能获取到Context上下文环境类对象的位置,调用其getSystemService(String name)方法,传入参数 name 值为Context.BLUETOOTH_SERVICE="bluetooth",可以获取到BluetoothManager蓝牙管理类的实例化对象,进而调用该对象的getAdapter()方法获取已经实例化的BluetoothAdapter蓝牙适配器对象。

对于不支持蓝牙硬件的设备,得到的蓝牙适配器对象为null,因此记得在应用程序中做非空判断的相关操作!

在支持蓝牙的设备上得到BluetoothAdapter对象后,还要确保设备已经开启了蓝牙功能。调用该对象的isEnabled()方法,根据返回的boolean类型结果,判断该设备是否已经正常开启了蓝牙功能;如果蓝牙处于未启用状态,还要通过调用Context上下文环境对象的startActivityForResult(Intent intent, int requestCode)方法跳转到蓝牙功能开启界面,而这里传入的参数 intent 则是标记为蓝牙界面的Intent意图对象,在该对象中设置其 action 值为BluetoothAdapter.ACTION_REQUEST_ENABLE ="android.bluetooth.adapter.action.REQUEST_ENABLE"。在跳转到蓝牙功能界面后,需要由用户手动选择开启蓝牙,并返回当前界面后,才能继续执行后续操作。

蓝牙客户端查找其他蓝牙设备

在设置蓝牙等操作之后,就是查找该设备附近范围内其他开启蓝牙功能的设备了。可以直接调用BluetoothAdapter对象的startDiscovery()方法来开启查找附近设备的功能,但是在发现设备后,系统会通过广播的形式发送已发现的设备信息给应用程序。所以还需要在某个自定义界面Activity或者服务Service中,注册BroadcastReceiver以接收该广播。

注册广播的方式可以回顾BroadcastReceiver动态注册部分内容,这里只是需要创建的IntentFilter意图过滤对象中的参数 action 值为BluetoothDevice.ACTION_FOUND="android.bluetooth.device.action.FOUND"。同时在自定义的BroadcastReceiver中处理收到的onReceive(Context context, Intent intent)方法中,对参数 intentgetParcelableExtra(String name)方法,并设置 name 值为BluetoothDevice.EXTRA_DEVICE="android.bluetooth.device.extra.DEVICE",可以获得android.bluetooth.BluetoothDevice蓝牙硬件设备类对象,在该对象中可以获取查找到的蓝牙硬件相关信息。

在查找到设备后,要及时调用BluetoothAdapter对象的cancelDiscovery()方法关闭查找功能。否则设备的蓝牙硬件会一直占用cpu资源并消耗电量,而且在后续的建立连接及数据传输时,也会由于蓝牙发现功能的开启占用大量带宽而影响后续操作效率。

对于之前已经连接配对过的设备,就不需要通过上述查找蓝牙的方式发现设备了,可以通过调用BluetoothAdapter对象的getBondedDevices()方法,直接获取已配对过的蓝牙设备,返回BluetoothDevice对象组成的Set集合。

蓝牙服务端等待其他设备连接请求

在设置蓝牙操作后,如果想将该设备作为被发现的设备等待连接,需要启动蓝牙的可检测功能。与开启蓝牙功能界面的方法类似,调用Context上下文环境对象的startActivityForResult(Intent intent, int requestCode)方法跳转到启动蓝牙可检测性功能界面,传入的参数 intent 意图对象中,设置的 action 值为BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE="android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";另外可以通过putExtra(String name, int value)设置可检测时间,其参数 name 值为BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION="android.bluetooth.adapter.extra.DISCOVERABLE_DURATION",而参数 value 值为int类型的数值,单位为秒。

同样在跳转到蓝牙可检测性功能界面后,需要由用户手动确认开启,才能等待被其他蓝牙设备查找发现。同时不管用户确认开启或取消开启功能,都会将结果回调到Context上下文环境所代表的原启动界面Activity中的onActivityResuslt(int requestCode, int resultCode, Intent data)方法中。

蓝牙Gatt客户端查找其他BLE设备

在设置蓝牙操作后,如果只是想查找附近的BLE设备并建立连接,则需要先拿到android.bluetooth.le.BluetoothLeScannerBLE扫描类对象。通过BluetoothAdapter对象的getBluetoothLeScanner()方法,可以得到BluetoothLeScannerBLE扫描类对象。之后借助该对象的startScan(ScanCallback callback)等系列方法,开始扫描查找附近的BLE设备,在查找到符合条件的设备后,会回调android.bluetooth.le.ScanCallback类型的参数 callback 对象中的onScanResult(int callbackType, ScanResult result)方法,并将结果保存在android.bluetooth.le.ScanResult扫描结果类型的参数 result 对象中。在ScanResult对象中,可以获得连接的BLE设备BlutoothDevice对象及连接所使用的协议功能等信息。

扫描查找到BLE设备后,及时调用BluetoothLeScanner对象的stopScan(ScanCallback callback)方法,参数 callback 与上文开始查找附近BLE设备方法中的参数相同,以停止当前蓝牙扫描查找功能。

连接蓝牙

在上述查找蓝牙或蓝牙可检测功能开启后被其他设备连接后,都会得到对方的BlutetoothDevice蓝牙设备类对象。通常,应用程序所在设备查找到其他蓝牙设备后,可以主动向设备建立连接请求;而当应用程序所在设备开启蓝牙可检测功能等待其他设备查找到时,只能被动的开启连接服务,等待处理其他设备的连接请求。

蓝牙客户端发起连接请求

对于主动发起蓝牙连接请求的方式,由得到的BluetoothDevice对象调用createRfcommSocketToServiceRecord(UUID uuid)方法,可以创建远程socket连接。参数 uuid 是自定义的java.util.UUID唯一索引类对象,该值必须与后文提到的被动等待连接的蓝牙设备中的监听服务中的唯一索引值一致。该方法会返回android.bluetooth.BluetoothSocket类用以保存创建的远程蓝牙socket连接。

在得到BluetoothSocket对象后,直接调用该对象的connect()方法发起连接请求。该方法调用后会一直等待连接的建立,只有建立连接后才会正常返回并执行后续操作,否则,如果请求超时或连接失败,该方法会抛出java.io.IOException异常。

蓝牙服务端等待处理连接

对于被动等待建立连接的方式,由得到的BluetoothDevice对象调用listenUsingRfcommWithServiceRecord(String name, UUID uuid)方法,建立持续监听服务。参数 name 是为该服务定义的名字;参数 uuid 是监听的唯一索引值,其值与上文中主动发起蓝牙连接请求是创建远程socket连接所传入的参数一致。该方法返回android.bluetooth.BluetoothServerSocket蓝牙连接的服务端socket类,用以保存在等待建立连接时的socket对象。

在得到BluetoothServerSocket对象后,直接调用该对象的accept()方法监听连接请求。该方法调用后也会一致等待监听状态,只有监听到匹配的连接请求后,才会返回BluetoothSocket对象,否则,如果连接失败,该方法同样会抛出java.io.IOException异常。

对于上述两种建立连接方式执行之后,便可以通过BluetoothSocket对象在蓝牙客户端和服务端之间传输数据。该对象的getInputStream()getOutputStream()方法可以分别获取socket连接的InputStream数据输入流对象和OutputStream数据输出流对象。可以借助InputStream数据输入流的read()系列方法,从socket连接中读取数据到应用程序中,反之借助OutputStream数据输出流的write()系列方法,将应用程序中的数据写入到socket连接中。同样地,这里的数据输入流的读取系列方法会一直处于等待读取状态,直到读到指定长度数据后才会返回结果,而数据输出流的写入系列方法,会在对方数据输入流读取慢导致写入缓存满时处于一直等待写入状态,直到指定数据完全写入才会返回结果。

BluetoothServerSocketBluetoothSocket对象传输数据结束后,需要分别调用他们的close()方法关闭连接,防止远程socket连接一直处于占用资源状态。

Gatt客户端连接BLE设备

对于Gatt客户端连接方式,由得到的BluetoothDevice对象调用connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)系列方法连接BLE设备的Gatt服务端,参数 context 是当前应用程序所在的上下文环境对象,参数 autoConnect 标识是否在连接该设备后自动建立Gatt服务连接,参数 callback 是建立Gatt服务连接后的回调android.bluetooth.BluetoothGattCallback抽象类。

在初次连接其他BLE设备的Gatt服务端时,会回调参数 callback 中的onServicesDiscovered(BluetoothGatt gatt, int status) 方法。而当已建立的Gatt连接状态发生改变时,会回调参数 callback 中的onConnectionStateChange(BluetoothGatt gatt, int status, int newState)方法。其他连接状态也会对应BluetoothGattCallback类中的相关方法。

BluetoothGattCallback抽象类的回调方法中,Gatt服务连接信息是通过android.bluetooth.BluetoothGatt类的参数 gatt 来传递的。通过BluetoothGatt对象的相关方法,可以执行Gatt客户端的连接及协议控制等操作,具体可需根据应用程序需求实现。只是记得在执行完相关操作后,调用参数 gattclose()方法关闭连接。

————————————————————————————————————————————————————————

Android系统编程入门系列之硬件交互——通信硬件Bluetooth的更多相关文章

  1. Android系统编程入门系列之加载界面Activity

    上回说到应用初始化加载及其生命周期,在Android系统调用Applicaiton.onCreate()之后,继续创建并加载清单文件中注册的首个界面即主Activity,也可称之为入口界面.主Acti ...

  2. Android系统编程入门系列之硬件交互——多媒体摄像头

    多媒体系列硬件 多媒体包括图片.动画.音频.视频,这些多媒体素材的采集(输入)主要依靠摄像头和麦克风等硬件设备转化为基础数据,而他们的播放渲染(输出),则需要依靠具有相关功能的编解码软件.当然随着硬件 ...

  3. Android系统编程入门系列之硬件交互——传感器

    到目前为止,关于应用程序与用户之间的相关内容便比较肤浅的大致介绍完毕.而在整个系统架构中,应用程序与用户之间的交互,犹如参天大树上的枝干和树叶,交互起来五彩缤纷,但使整个生态系统保持生命力的核心,在于 ...

  4. Android系统编程入门系列之硬件交互——通信硬件USB

    在硬件交互的首篇对设备硬件的分类中,互联通信系列硬件主要用来与其他设备进行数据交互.从本文开始,将重点介绍该系列相关硬件. 互联通信系列硬件 根据硬件的可通信距离,由近及远分为USB.NFC.蓝牙.W ...

  5. Android系统编程入门系列之硬件交互——多媒体展示

    前两篇文章通过麦克风硬件和摄像头硬件分别采集音频和视频的多媒体数据,在得到的多媒体数据通常是以编码文件的格式存储,在用户需要展示时,可通过设备的内置扩音器或蓝牙耳机等硬件播放音频,通过设备的显示屏或外 ...

  6. Android系统编程入门系列之硬件交互——通信硬件NFC

    在上篇文章介绍了接入式USB硬件的简单使用,接下来将介绍不依赖物理连接的硬件通信了.本文的重点是近距离通信的硬件NFC. NFC硬件 应用程序中可以通过NFC硬件读取或发送指定协议的技术实现,在And ...

  7. Android系统编程入门系列之硬件交互——无线通信WLAN

    Android系统的移动设备大多支持无线WLAN技术.利用该技术,不仅能实现互联网通信,还能实现无线定位,热点共享等远程通信功能.针对使用WLAN的不同功能,可能需要分别申请不同的权限声明,同时调用不 ...

  8. Android系统编程入门系列之硬件交互——通信硬件电信SIM卡

    现在的SIM卡通常具备基站定位.语音通话.短信消息.网络流量这四大功能,而在移动端是无法对SIM卡使用基站定位功能的,所以这里只介绍移动端如何使用SIM卡实现语音通话.短信消息.数据流量三个功能. 语 ...

  9. Android系统编程入门系列之界面Activity交互响应

    在上篇文章中已经了解到界面Activity的绘制完全依赖其加载的视图组件View,不仅如此,用户的每次触摸操作都可以在界面Activity内接收并响应,也可以直接传递给其中的某个视图View响应.本文 ...

随机推荐

  1. bootstrap datetimepick 时分秒选择,坑我15个小时,整理记录

    官网的datetimepick 下载链接 官网下载 <input type="text" readonly name="feedDay" id=" ...

  2. select......for update会锁表还是锁行

    select查询语句是不会加锁的,但是select .......for update除了有查询的作用外,还会加锁呢,而且它是悲观锁. 那么它加的是行锁还是表锁,这就要看是不是用了索引/主键. 没用索 ...

  3. 【安卓】AndroidStudio使用本地gradle进行build的配置

    1.修改setting使用local gradle2.将下载的gradle-6.7.1-all.zip放入E:/AndroidProject文件夹 修改gradle-wapper.propertie使 ...

  4. 一个老菜鸟的年度回忆 & 智能工厂奋斗的第三年,可能有你值得借鉴的

    岁月蹉跎,寒冬的夜晚仍伏案疾书,见论坛中有诸多大神已经开始了一年的总结,突然安奈不住心中的躁动,也想为这今年的奋斗留下只言片语,没有年初的目标总结,没有未来的展望,就想作为一篇日记记录今年项目精力,为 ...

  5. Java基础周测题,输入一个整数,输出所有能整除该整数的结果:

    需求说明: 输入一个整数,输出所有能整除该整数的结果: 实现代码: package demo; import java.util.Scanner; public class test1 { publi ...

  6. TypeScript中文教程基础部分上----翻译自TS官方

    为什么使用TS? js中每一个值在不同的操作运行中表现出一系列不同的行为,比如说下面这个例子: message.toLowerCase();message(); 逐行看下,第一行调用了message的 ...

  7. 怎样查看Jenkins的版本

    where to check jenkins version To identify your current version of Jenkins, you can do one of two th ...

  8. Git 如何放弃所有本地修改

    git checkout . #本地所有的修改,没有提交的,都返回到原来的状态 git stash #把所有没有提交的修改暂存到stash里面.可用git stash pop恢复. git reset ...

  9. 新增访客数量MR统计之Reduce和Runner相关准备

    关注公众号:分享电脑学习回复"百度云盘" 可以免费获取所有学习文档的代码(不定期更新)云盘目录说明:tools目录是安装包res 目录是每一个课件对应的代码和资源等doc 目录是一 ...

  10. Spark本地环境实现wordCount单词计数

    注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6814778610788860424/ 编写类似MapReduce的案例-单词统计WordCount 要统计的文件为 ...