精华内容
下载资源
问答
  • 2021-05-10 16:37:18

    Android开发 蓝牙通讯 (一)基础篇

    Android为蓝牙技术提供了4个工具类,分别是蓝牙适配器BluetoothAdapter、蓝牙设备BluetoothDevice、蓝牙服务端套接字BluetoothServerSocket和蓝牙客户端套接字BluetoothSocket。

    1. BluetoothAdapter蓝牙适配器

    方法:
    getDefaultAdapter:静态方法,获取默认的蓝牙适配器对象;
    enable:打开蓝牙功能;
    disable:关闭蓝牙功能;
    isEnable:判断蓝牙功能是否打开;
    startDiscovery:开始搜索周围的蓝牙设备;
    cancelDiscovery:取消搜索操作;
    isDiscovering:判断当前是否正在搜索设备;
    getBondedDevices:获取已绑定的设备列表;
    setName:设置本机的蓝牙名称;
    getName:获取本机的蓝牙名称;
    getAddress:获取本机的蓝牙地址;
    getRemoteDevice:根据蓝牙地址获取远程的蓝牙设备;
    getState:获取本地蓝牙适配器的状态;
    listenUsingRfcommWithServiceRecord:根据名称和UUID创建并返回BluetoothServiceSocket;
    listenUsingRfcommOn:根据渠道编号创建并返回BluetoothServiceSocket。

    常量

    状态值
    int STATE_OFF 蓝牙已经关闭
    int STATE_ON 蓝牙已经打开
    int STATE_TURNING_OFF 蓝牙处于关闭过程中 ,关闭ing
    int STATE_TURNING_ON 蓝牙处于打开过程中 ,打开ing
    int SCAN_MODE_CONNECTABLE 表明该蓝牙可以扫描其他蓝牙设备
    int SCAN_MODE_CONNECTABLE_DISCOVERABLE表 明该蓝牙设备同时可以扫码其他蓝牙设备,并且可以被其他蓝牙设备扫描到。
    int SCAN_MODE_NONE : 该蓝牙不能扫描以及被扫描。

    广播的Action
    ACTION_STATE_CHANGED 蓝牙状态值发生改变
    ACTION_SCAN_MODE_CHANGED 蓝牙扫描状态(SCAN_MODE)发生改变
    ACTION_DISCOVERY_STARTED 蓝牙扫描过程开始
    ACTION_DISCOVERY_FINISHED 蓝牙扫描过程结束
    ACTION_LOCAL_NAME_CHANGED 蓝牙设备Name发生改变
    ACTION_REQUEST_DISCOVERABLE 请求用户选择是否使该蓝牙能被扫描
    ACTION_REQUEST_ENABLE 请求用户选择是否打开蓝牙
    ACTION_FOUND 搜索到蓝牙设备

    2. 蓝牙设备BluetoothDevice

    一个BluetoothDevice对象代某个一个蓝牙设备。BluetoothDevice提供一下几个常用的方法:
    getName:获得该设备的名称;
    getAddress:获得该设备的地址;
    getBondState:获得该设备的绑定状态;
    createBond:创建匹配对象;
    createRfcommSocketToServiceRecord:根据UUID创建并返回一个BluetoothSocket。

    3. 蓝牙服务端套接字BluetoothServerSocket

    BluetoothServiceSocket是服务端的Socket,用来接收客户端的Socket连接请求。提供的常用方法:
    accept:监听外部的蓝牙连接请求;
    close:关闭服务端的蓝牙监听。

    4.蓝牙客户端套接字BluetoothSocket

    BluetoothSocket是客户端的Socket,用于与对方设备进行数据通信。常用方法有:
    connect:建立蓝牙的socket连接;
    close:关闭蓝牙的socket连接;
    getInputStream:获取socket连接的输入流对象;
    getOutputStream:获取socket连接的输出流对象;
    getRemoteDevice:获取远程设备信息。

    开发思路

    有了以上四个工具类以及提供的方法,我们就可以通过蓝牙建立连接,实现两个安卓设备之间的数据通讯。由上面的四个工具类我们不难看出使用蓝牙连接其实也是socket连接,只要是socket都会涉及到服务端和客户端,所以我们的开发流程(思路如下)

    服务端

    1. 打开蓝牙
    2. 使蓝牙可被搜索
    3. 等待连接
    4. 接收连接请求
    5. 建立连接

    客户端

    1. 打开蓝牙
    2. 扫描蓝牙
    3. 发送配对和连接请求
    4. 建立连接

    基本思路就在这里,下一篇我们实战看看如何建立连接,并且实现双向通讯

    更多相关内容
  • 我的博客“Android 蓝牙开发,蓝牙连打印机”有环境可运行截图。希望对大家有帮助,放心用吧。
  • 本文以实例的方式讲解Android蓝牙开发的知识。  1、使用蓝牙的响应权限 XML/HTML代码 <uses android:name=android.permission.BLUETOOTH/> <uses android:name=android.permission.BLUETOOTH_ADMIN/>  2、配置本...
  • 先回顾一下上一篇文章《Android蓝牙开发系列文章-蓝牙音箱连接》讲到的蓝牙音箱的完成配对、连接的流程:扫描设备–监听DEVICE_FOUND广播–>直到找到目标设备–>对目标设备发起配对–>监听到设备配对成功–>发起设备...
  • Android蓝牙开发

    2017-03-26 20:39:11
    Android蓝牙开发
  • android 蓝牙开发

    2022-06-02 15:10:48
    实现android 蓝牙模块的 扫描,数据交互,通讯 等功能
  • 我们在《Android蓝牙开发系列文章-策划篇》中计划讲解一下蓝牙BLE,现在开始第一篇:Android蓝牙开发系列文章-玩转BLE开发(一)。计划要写的BLE文章至少分四篇,其他三篇分别讲解:BLE Server端编码(用手机模拟...
  • android蓝牙开发步骤.pdf
  • android蓝牙开发

    2018-03-31 16:28:06
    Android蓝牙数据传输实现Android蓝牙数据传输实现Android蓝牙数据传输实现
  • 最近,随着智能穿戴式设备、智能医疗以及智能家居的普及,蓝牙开发在移动开中显得非常的重要。由于公司需要,研究了一下,蓝牙4.0在Android中的应用。 以下是我的一些总结。 1.先介绍一下关于蓝牙4.0中的一些名词吧...
  • 最近公司开发需要用到蓝牙,花了大约一天的时间总结整理了一下。主要是为了以后自己方便看。有需要的朋友可以看下。欢迎一起讨论。后面会带上博客。里面是实现了蓝牙搜索,配对,连接,数据互传。
  • 这个demo实现了Android蓝牙开发,创建客户端,服务端。并实现互发消息,接收消息
  • 由于近期正在开发一个通过蓝牙进行数据传递的模块,在参考了有关资料,并详细阅读了Android的官方文档后,总结了Android蓝牙模块的使用
  • Android蓝牙开发 — 经典蓝牙&BLE蓝牙

    千次阅读 2021-04-30 20:02:10
    一,前期基础知识储备 1)蓝牙是一种支持设备之间短距离通信的无线电技术(其他还包括红外,WIFI); 支持移动电话、笔记本电脑、...2)蓝牙开发技术一共分为两种: 经典蓝牙,3.0版本以下的蓝牙,功耗高,传输

    一,前期基础知识储备

    1)蓝牙是一种支持设备之间短距离通信的无线电技术(其他还包括红外,WIFI);

    支持移动电话、笔记本电脑、无线耳机等设备之间进行信息的交换;

    Android支持的蓝牙协议栈:Bluz,BlueDroid,BLE

    • Bluz是Linux推出的,目前使用最广泛;
    • BlueDroid是Android4.0之后推出来的,简化了Bluz的操作;
    • BLE是最新的低功耗协议,传输效率和传输速率都是很高的;

    2)蓝牙开发技术一共分为两种:

    • 经典蓝牙,3.0版本以下的蓝牙,功耗高,传输数据量大,有效距离10米;
    • 低功耗蓝牙BLE,4.0及以上版本,低功耗,数据量小,有效距离40米;

    经典蓝牙的开发包括蓝牙的互相连接、读取蓝牙列表、文件传输、蓝牙耳机等等;

    特点:基于Socket连接,传输速率快;缺点:耗电,距离短;
    通信的流程,发现设备 -> 配对/绑定设备  ->  建立连接  ->  数据通信  ->  断开连接

    BLE蓝牙开发主要是低功耗设备(临近设备间传输少量数据),比如血糖仪、蓝牙手环、蓝牙手表、蓝牙温度枪等等;Android 4.3(API 级别 18)为发挥核心作用的蓝牙低功耗 (BLE) 引入内置平台支持,并提供相应 API,方便应用发现设备、查询服务和传输信息。与传统蓝牙不同,蓝牙低功耗 (BLE) 旨在提供显著降低的功耗这使 Android 应用可与功率要求更严格的 BLE 设备(例如近程传感器、心率监测仪和健身设备)通信

    注意:当用户使用 BLE 将其设备与其他设备配对时,用户设备上的所有应用都可以访问在这两个设备间传输的数据。

    因此,如果您的应用捕获敏感数据,您应实现应用层安全以保护此类数据的私密性。

    3)蓝牙通信底层原理:

    Android 平台包含蓝牙网络堆栈支持,此支持能让设备以无线方式与其他蓝牙设备交换数据。应用框架提供通过 Android Bluetooth API 访问蓝牙功能的权限。这些 API 允许应用以无线方式连接到其他蓝牙设备,从而实现点到点和多点无线功能。

    为了让支持蓝牙的设备能够在彼此之间传输数据,它们必须先通过配对过程形成通信通道。其中一台设备(可检测到的设备)需将自身设置为可接收传入的连接请求。另一台设备会使用服务发现过程找到此可检测到的设备。在可检测到的设备接受配对请求后,这两台设备会完成绑定过程,并在此期间交换安全密钥。二者会缓存这些密钥,以供日后使用。完成配对和绑定过程后,两台设备会交换信息。当会话完成时,发起配对请求的设备会发布已将其链接到可检测设备的通道。但是,这两台设备仍保持绑定状态,因此在未来的会话期间,只要二者在彼此的范围内且均未移除绑定,便可自动重新连接。

    4)关键类和接口

    android.bluetooth 包中提供所有 Bluetooth API。以下概要列出了创建蓝牙连接所需的类和接口:

     BluetoothAdapter
    表示本地蓝牙适配器(蓝牙无线装置)。BluetoothAdapter 是所有蓝牙交互的入口点。借助该类,您可以发现其他蓝牙设备、查询已绑定(已配对)设备的列表、使用已知的 MAC 地址实例化 BluetoothDevice,以及通过创建 BluetoothServerSocket 侦听来自其他设备的通信。

    <!--蓝牙连接权限-->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <!--蓝牙通讯权限-->
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    
    // 启动蓝牙
    public void turnOnBlueTooth(Activity activity, int requestCode) {
    	Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    	activity.startActivityForResult(intent, requestCode);
    	// mAdapter.enable(); // 谷歌不推荐这种方式
    }
    
    // 关闭蓝牙
    public void turnOffBluetooth() {
    	mAdapter.disable();
    }
    
    // 打开蓝牙可见性
    public void enableVisibily(Context context) {
    	Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
    	intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
    	context.startActivity(intent);
    }

    BluetoothDevice
    表示远程蓝牙设备。借助该类,您可以通过 BluetoothSocket 请求与某个远程设备建立连接,或查询有关该设备的信息,例如设备的名称、地址、类和绑定状态等。

    // 查找设备
    public void findDevice() {
    	assert (mAdapter != null);
    	mAdapter.startDiscovery();
    }
    
    // 绑定设备
    public boolean createBond(BluetoothDevice device) {
    	boolean result = device.createBond();
    	return result;
    }
    
    // 绑定状态
    BluetoothDevice.BOND_BONDED
    BluetoothDevice.BOND_BONDING
    BluetoothDevice.BOND_NONE
    
    // 获取已绑定的蓝牙设备
    public List<BluetoothDevice> getBondedDeviceList() {
    	return new ArrayList<>(mAdapter.getBondedDevices());
    }
    
    // 解除绑定
    public boolean removeBond(Class btClass, BluetoothDevice btDevice)
    throws Exception {
    	Method removeBondMethod = btClass.getMethod("removeBond");
    	Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice);
    	return returnValue.booleanValue();
    }
    
    
    // 蓝牙操作中发出的广播
    private void registerBluetoothReceiver() {
    	IntentFilter filter = new IntentFilter();
    	//开始查找
    	filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
    	//结束查找
    	filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    	//查找设备
    	filter.addAction(BluetoothDevice.ACTION_FOUND);
    	//设备扫描可见改变 当我可以被看见时就会发送一个广播过来
    	filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
    	//绑定状态
    	filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
    
    	registerReceiver(receiver, filter);
    }

    BluetoothSocket
    表示蓝牙套接字接口(类似于 TCP Socket)。这是允许应用使用 InputStream 和 OutputStream 与其他蓝牙设备交换数据的连接点。

    close(),关闭
    connect()连接
    getInptuStream()获取输入流
    getOutputStream()获取输出流
    getRemoteDevice()获取远程设备,这里指的是获取bluetoothSocket指定连接的那个远程蓝牙设备

    BluetoothServerSocket
    表示用于侦听传入请求的开放服务器套接字(类似于 TCP ServerSocket)。如要连接两台 Android 设备,其中一台设备必须使用此类开放一个服务器套接字。当远程蓝牙设备向此设备发出连接请求时,该设备接受连接,然后返回已连接的 BluetoothSocket。

    void    close()
        closes the object and release any system resources it holds.
    void    connect()
        attempt to connect to a remote device.
    InputStream getInputStream()
        get the input stream associated with this socket.
    OutputStream    getOutputStream()
        get the output stream associated with this socket.
    BluetoothDevice getRemoteDevice()
        get the remote device this socket is connecting, or connected, to.
    获取远程设备,该套接字连接,或连接到---。
    boolean isConnected()
        get the connection status of this socket, ie, whether there is an active connection with remote device.
    判断当前的连接状态

    BluetoothSocket 和 BluetoothServerSocket 
    类似于Java中的套接字的 Socket 和 ServerSocket;
    在服务器端和客户端进行数据传输的时候都要使用这个类;

    服务器端 : 使用BluetoothServerSocket对象可以创建一个BluetoothSocket对象, 调用BluetoothServerSocket的accept()方法就可以获取该对象;
    客户端 : 调用BluetoothDevice的createRfcommSocketToServiceRecord()可以获取该对象; 

    在服务器端BluetoothServerSocket进行accept()阻塞, 在客户端BluetoothSocket调用connect()连接服务器, 如果连接成功, 
    服务器端的accept()方法就会返回BluetoothSocket对象, 同时客户端的BluetoothSocket也成功连接服务器, 
    此时服务器端和客户端的BluetoothSocket对象就可以获取输入输出流, 对数据进行操作;


    基于蓝牙这套通信流程,可以实现一个简单的聊天程序。

    BluetoothHeadset
    提供蓝牙耳机支持,以便与手机配合使用。这包括蓝牙耳机配置文件和免提 (v1.5) 配置文件。
    BluetoothA2dp
    定义如何使用蓝牙立体声音频传输配置文件 (A2DP),通过蓝牙连接将高质量音频从一个设备流式传输至另一个设备

    • A2DP蓝牙立体声音频传输配置文件 (A2DP) 定义如何通过蓝牙连接和流式传输,将高质量音频从一个设备传输至另一个设备。Android 提供 BluetoothA2dp 类,该类是用于控制蓝牙 A2DP 服务的代理。

    官方文档:《蓝牙概览》《蓝牙低功耗概览》《BluetoothA2dp

    5)电话音频协议(HSP,HFP)和媒体音频协议(A2DP,AVRCP)

    • HSP(手机规格)– 提供手机(移动电话)与耳机之间通信所需的基本功能。
    • HFP(免提规格)– 在 HSP 的基础上增加了某些扩展功能,原来只用于从固定车载免提装置来控制移动电话。
    • A2DP(高级音频传送规格)– 允许传输立体声音频信号。 (相比用于 HSP 和 HFP 的单声道加密,质量要好得多)。
    • AVRCP(音频/视频遥控规格)–用于从控制器(如立体声耳机)向目标设备(如装有 Media Player 的电脑)发送命令(如前跳、暂停和播放)。

    关于A2DP,安卓手机都是支持A2DP的,只需要通过广播就可以获取状态了。

    关于AVRCP,这部分嵌入式工程师的逻辑多,硬件上的按键可以控制手机app的,比如按键加减时,可以与app交互。

    A2DP全名是Advanced Audio Distribution Profile,高质量音频数据传输的协议,其定义里了传送单声道或立体声等高质量音频(区别于蓝牙SCO链路上传输的普通语音)信息的协议和过程。A2DP的典型应用是将音乐播放器的音频数据发送到耳机或音箱。 

    参考文章:《Android 蓝牙开发之A2DP基本功能

    参考项目:sample-bluetooth-audio(Bluetooth A2DP sample using Android Things)

    二,上代码,具体实现

    经典蓝牙开发文章:《Android蓝牙开发—经典蓝牙详细开发流程》《android 经典蓝牙开发

    BLE蓝牙开发文章:《Android之低功耗蓝牙的基本使用》《Android Kotlin&BLE(低功耗蓝牙) 笔记

                                    《Android BLE蓝牙详细解读》《Android 蓝牙开发(三) -- 低功耗蓝牙开发

    总结:

    1)写过经典蓝牙的就知道,如果说两者的搜索操作还差不多的话,连接操作和写入操作就是完全不同的东西了。

    经典蓝牙可以获取到一个类似 TCP 中 Socket 的对象,然后获取 InputStream 和OutputStream,二者分别通过套接字以及 getInputStream()和 getOutputStream()来处理数据传输。

    而 BLE 中需要通过不同的 UUID 获取对应的服务、特征才可以写入数据。

    2)UUID:每个服务和特征都会有唯一的 UUID ,由硬件决定。
    服务(Service):蓝牙设备中可以定义多个服务,相当于功能的集合。
    特征(Characteristic):一个服务可以包含多个特征,可以通过 UUID 获取到对应的特征的实例,通过这个实例就可以向蓝牙设备发送 / 读取数据。

    蓝牙开源框架:《一款适用经典蓝牙的快速开发框架》《开源蓝牙框架 Android-BLE

    三,蓝牙5.0

    06wj jsdt ly 01

    古老无线再升级 深入了解蓝牙5.0技术

    查看源图像

    蓝牙5.0是由蓝牙技术联盟在2016年提出的蓝牙技术标准,蓝牙5.0针对低功耗设备速度有相应提升和优化,蓝牙5.0结合wifi对室内位置进行辅助定位,提高传输速度,增加有效工作距离。上一次蓝牙4.2是公布于2014年12月。

    补充一点内容

    划时代的蓝牙 4.0 

    蓝牙技术联盟(Bluetooth SIG)在2010年发布了跨时代的蓝牙4.0,它并不是蓝牙3.0的简单升级版本,
    而是全新的技术架构,蓝牙4.0版本分两种模式:单模蓝牙和双模蓝牙。常见的蓝牙音箱,是典型的双模蓝牙,它需要传输大量的音频数据。
    而小米手环,蓝牙温度计则属于单模蓝牙。行业里一般不讲单模蓝牙,而是统一称为低功耗蓝牙。

    相对于经典蓝牙,低功耗蓝牙芯片有传输远、功耗低、延迟低等优势。传输距离方面,经典蓝牙只有10-100米,而BLE最远能传输300米;
    连接方式上,经典蓝牙只能通过点对点的方式传输,而BLE设备能够能通过点对点、广播、Mesh组网与其他设备相连;
    在功耗上两者的差别巨大,低功耗蓝牙运行和待机功耗极低,使用一颗纽扣电池便能连续工作数月甚至数年之久。

    经典蓝牙主要用于大量音频传输的情景,而低功耗蓝牙主要用在非音频数据传输上。
    基于这个差距,经典蓝牙和低功耗蓝牙应用场景有所不同。经典蓝牙主要应用在音频传输设备上, 
    而低功耗蓝牙主要用在数据传输领域,尤其是以物联网为主的数据传输。
    如血糖仪、蓝牙手环、蓝牙手表、蓝牙温度枪、近程传感器、心率监测仪和健身设备等等。

     蓝牙音频传输 - 手机端的音乐如何通过转码的方式传输到蓝牙耳机进行播放。

    参考文章:

    经典蓝牙与低功耗蓝牙芯片功能性能对比

    BLE技术揭秘

    说说蓝牙音频常用的编解码格式

    Audio/Video Remote Control Profile (AVRCP)

    展开全文
  • Bluetooth Android 蓝牙开发 Android 打开、搜索、配对、连接、通信 发送文字、传输默认文件 <----- Android经典蓝牙 -------->
  • Android5.0蓝牙开发

    2019-03-12 13:39:35
    这是一个用于帮助开发者更好的了解封装包的API,以便于新手更快,更准的上手
  • Android平台支持蓝牙网络协议栈,实现蓝牙设备之间数据的无线传输。   本文档描述了怎样利用android平台提供的蓝牙API去实现蓝牙设备之间的通信,蓝牙设备之间的通信主要包括了四个步骤:   设置...
  • android蓝牙开发工具包

    2022-05-07 18:56:05
    这个是配合文章android蓝牙开发,通过Sbbluetooth.aar文件连接蓝牙设备并上传与解析数据使用的框架包,如果有问题私聊我,
  • android蓝牙开发小程序, 基于android系统,详细的源程序,注释 android,蓝牙开发浅谈,蓝牙连接,socket连接-android, development of Bluetooth, Bluetooth connectivity, socket connection
  • Android蓝牙开发—经典蓝牙详细开发流程

    万次阅读 多人点赞 2019-06-12 12:06:04
    配对的过程,其实就是Socket通信的一个过程,两个蓝牙设备仅仅匹配是还不能够传递数据的,只有当二者建立了Socket通道之后,才能进行数据的传递 权限 核心API Android蓝牙开发有涉及到不同的蓝牙种类,例如低功耗...

    开发流程

    • 开启蓝牙
    • 扫描蓝牙,并将这些设备加入到devices列表
    • 配对蓝牙,将次设备加入到已配对设备列表
    • 连接蓝牙
    • 通信

    一些解释

    设备列表:是指附近的可见蓝牙设备,但是还没匹配
    匹配设备列表:是指已经完成匹配的列表

    设备类型分为:【未匹配状态】、【匹配状态】、【配对状态】

    未匹配,就是指从来没有匹配过的设备
    匹配状态,就是指设备间已经相互完成了身份验证,处于待配对(待建立Socket)状态

    以前蓝牙配对的时候,手机会弹出一个对话框,提示输入pin码,其实也就是提前约定的一个配对码,到后来,手机与与手机之间的连接就不需要配对码了(实际上是程序内部完成了配对的过程)

    当然,手机与一些蓝牙硬件(例如单片机+蓝牙模块的组合)配对时,还是需要输入pin码(不过也能通过程序自动完成验证)

    配对的过程,其实就是Socket通信的一个过程,两个蓝牙设备仅仅匹配是还不能够传递数据的,只有当二者建立了Socket通道之后,才能进行数据的传递

    权限

        <!-- 使用蓝牙的权限 -->
        <uses-permission android:name="android.permission.BLUETOOTH" />
        <!-- 扫描蓝牙设备或者操作蓝牙设置 -->
        <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
        <!--模糊定位权限,仅作用于6.0+-->
        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
        <!--精准定位权限,仅作用于6.0+-->
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    

    后边的模糊定位和精准定位是必须的,没有这两个权限,注册BroadcastReceiver 监听扫描结果时,不会成功的,不走扫描开始之类的方法

    核心API

    Android的蓝牙开发有涉及到不同的蓝牙种类,例如低功耗蓝牙(BluetoothGatt)、蓝牙健康(BlueToothHealth)等,这里介绍的依然是常规的蓝牙开发API

    BlueToothAdapter

    这个类代表着本地的蓝牙适配器,让你可以从事各种与蓝牙相关的操作,例如开始和停止设备的查找;查询已匹配的设备并以集合的形式返回;通过已知的设备地址,实例化一个BlueToothDevice;创建一个BluetoothServerSocket来监听来自其他设备的链接请求等等,总之要想使用本机的蓝牙,这个类是极其重要的

    getDefaultAdapter():获取BluetoothAdapter对象
    //获取本地蓝牙适配器
    bluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
    
    判断设备是否支持蓝牙
    /**
     * 设备是否支持蓝牙  true为支持
     * @return
     */
    public boolean isSupportBlue(){
        return bluetoothAdapter!= null;
    }
    
    判断蓝牙是否开启
    		//获取BluetoothAdapter对象
            mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
            //判断设备是否支持蓝牙,如果mBluetoothAdapter为空则不支持,否则支持
            if (mBluetoothAdapter == null) {
                Toast.makeText(this, "这台设备不支持蓝牙", Toast.LENGTH_SHORT).show();
            } else {
                // If BT is not on, request that it be enabled.
                // setupChat() will then be called during onActivityResult
                //判断蓝牙是否开启,如果蓝牙没有打开则打开蓝牙
                if (!mBluetoothAdapter.isEnabled()) {
                	//请求用户开启
                    Intent enableIntent = new Intent(
                            BluetoothAdapter.ACTION_REQUEST_ENABLE);
                    startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
                } else {
                    getDeviceList();
                }
            }
    

    判断请求后是否开启

    @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (requestCode == REQUEST_ENABLE_BT) {
                if (resultCode == Activity.RESULT_OK) {
                    // bluetooth is opened
                    //可以获取列表操作等
                } else {
                    // bluetooth is not open
                    Toast.makeText(this, "蓝牙没有开启", Toast.LENGTH_SHORT).show();
                }
            }
        }
    
    
    getBondedDevices()

    获取已经匹配的设备列表,并以set集合的方式返回

    	/*
         *获取已经配对的设备
         */
        private void setPairingDevice() {
            Set<BluetoothDevice> devices = bluetoothAdapter.getBondedDevices();
            if (devices.size() > 0) { //存在已配对过的设备
                //利用for循环读取每一个设备的信息
                for (Iterator<BluetoothDevice> it = devices.iterator(); it.hasNext(); ) {
                    BluetoothDevice btd = it.next();
                    ```````
                }
            }else{   
                //不存在已经配对的蓝牙设备
            }
        }
    
    isDiscovering()

    判断当前是否正在查找设备,是返回true

    cancelDiscovery()

    取消查找设备

    startDiscovery()

    这里需要注意一下,由于搜素是一个耗时操作,所以这个方法应该在线程中去调用,同时需要配合着广播去使用
    开始查找设备,一般的调用代码如下:

    private void doDiscovery() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if(bluetoothAdapter.isDiscovering()){
                        bluetoothAdapter.cancelDiscovery();
                    }
                    bluetoothAdapter.startDiscovery();
                }
            }).start();
        }
    

    这里再注意一个细节,那就是如果当前的adapter正在查找,那么必须停止当前查找,然后再重新查找,这是因为查找操作占用很多的系统资源,我们需要避免重复的查找

    getAddress() | getName() | getState()

    getAddress()获取本地蓝牙地址
    getName()获取本地蓝牙名称
    getState()获取本地蓝牙适配器当前状态

    BluetoothDevice

    这个类代表一个远程设备,它能够让我们与一个其他相关设备创建连接,或者查询与此设备相关的信息,例如名称(name)、地址(address)、连接状态(boud state)

    getAddress() | getName() | getState()

    getAddress()获取Address设备的蓝牙地址
    getName()获取Address设备的地蓝牙名称
    getState()获取Address设备的蓝牙适配器当前状态,是匹配状态,不是连接状态

    功能实现

    扫描蓝牙

    开始扫描

    这部分代码之前已经写过

    		if (bluetoothAdapter.isDiscovering()) {
                bluetoothAdapter.cancelDiscovery();
            }
            // Request discover from BluetoothAdapter
            bluetoothAdapter.startDiscovery();
    
    通过广播的方式接收扫描结果
    注册广播
    //搜索开始的过滤器
    IntentFilter filter1 = new IntentFilter(android.bluetooth.BluetoothAdapter.ACTION_DISCOVERY_STARTED);
    //搜索结束的过滤器
    IntentFilter filter2 = new IntentFilter(android.bluetooth.BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    //寻找到设备的过滤器
    IntentFilter filter3 = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    //绑定状态改变
    IntentFilter filer4 = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
    //配对请求
    IntentFilter filter5 = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST)
    
    registerReceiver(mFindBlueToothReceiver ,filter1);
    registerReceiver(mFindBlueToothReceiver ,filter2);
    registerReceiver(mFindBlueToothReceiver ,filter3);
    registerReceiver(mFindBlueToothReceiver ,filter4);
    registerReceiver(mFindBlueToothReceiver ,filter5);
    
    接收广播
    //广播接收器,当远程蓝牙设备被发现时,回调函数onReceiver()会被执行
        private final BroadcastReceiver mFindBlueToothReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                switch (action){
                    case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
                        Log.d(TAG, "开始扫描...");
                        callBack.onScanStarted();
                        break;
                    case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
                        Log.d(TAG, "结束扫描...");
                        callBack.onScanFinished();
                        break;
                    case BluetoothDevice.ACTION_FOUND:
                        Log.d(TAG, "发现设备...");
                        callBack.onScanning(device);
                        break;
                     case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
                        Log.d(TAG, "设备绑定状态改变...");
                        callBack.onStateChanged(device);
                        break;
                }
            }
        };
    

    配对蓝牙

    配对指定设备

    其中布尔值returnValue为true,表示配对成功,配对成功后,可能会输入PIN码
    当输入完成正确PIN码后,才会配对完成。就可以通过广播的方式接收配对结果

     /**
         * 配对蓝牙设备
         */
        private void pinTargetDevice(int position) {
            //在配对之前,停止搜索
            cancelDiscovery();
    		//获取要匹配的BluetoothDevice对象,后边的deviceList是你本地存的所有对象
            BluetoothDevice device = deviceList.get(position);
            if (device.getBondState() != BluetoothDevice.BOND_BONDED) {//没配对才配对
                try {
                    Log.d(TAG, "开始配对...");
    
                    Method createBondMethod = BluetoothDevice.class.getMethod("createBond");
                    Boolean returnValue = (Boolean) createBondMethod.invoke(device);
    
                    if (returnValue){
                        Log.d(TAG, "配对成功...");
                        showToast("配对成功");
                    }
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    
    取消配对
    /**
     * 取消配对(取消配对成功与失败通过广播返回 也就是配对失败)
     * @param device
     */
    public void cancelPinBule(BluetoothDevice device){
            Log.d(TAG, "attemp to cancel bond:" + device.getName());
            try {
                Method removeBondMethod = device.getClass().getMethod("removeBond");
                Boolean returnValue = (Boolean) removeBondMethod.invoke(device);
                returnValue.booleanValue();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                Log.e(TAG, "attemp to cancel bond fail!");
            }
    }
    

    经测试,不管用,貌似只有在系统设置里才能取消配对?所以我的做法是跳转了蓝牙设置

      startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
    
    通过广播的方式接收配对结果
      /**
         * 广播接收器
         * 当远程蓝牙设备被发现时,回调函数onReceiver()会被执行
         */
        private final BroadcastReceiver mFindBlueToothReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                // Get the BluetoothDevice object from the Intent
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                switch (action){
                     ......
                    case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
                        Log.d(TAG, "设备绑定状态改变...");
    
                        switch (device.getBondState()) {
                            case BluetoothDevice.BOND_BONDING:
                                Log.w(TAG, "正在配对......");
                                break;
                            case BluetoothDevice.BOND_BONDED:
                                Log.w( TAG, "配对完成");
                                break;
                            case BluetoothDevice.BOND_NONE:
                                Log.w(TAG, "取消配对");
                            default:
                                break;
                        }
                        break;
                }
            }
        };
    

    蓝牙连接

    蓝牙配对和连接是两个不同的东西
    经典蓝牙连接相当于socket连接,是个非常耗时的操作,所以应该放到子线程中去完成

    蓝牙连接线程
     /**
         * 蓝牙连接线程
         */
        public class ConnectBlueTask extends AsyncTask<BluetoothDevice, Integer, BluetoothSocket> {
            private BluetoothDevice bluetoothDevice;
            private ConnectBlueCallBack callBack;
    
            public ConnectBlueTask(ConnectBlueCallBack callBack){
                this.callBack = callBack;
            }
    
            @Override
            protected BluetoothSocket doInBackground(BluetoothDevice... bluetoothDevices) {
                bluetoothDevice = bluetoothDevices[0];
                BluetoothSocket socket = null;
                try{
                    Log.d(TAG,"开始连接socket,uuid:00001101-0000-1000-8000-00805F9B34FB");
                    socket = bluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
                    if (socket != null && !socket.isConnected()){
                        socket.connect();
                    }
                }catch (IOException e){
                    Log.e(TAG,"socket连接失败");
                    try {
                        socket.close();
                    } catch (IOException e1) {
                        e1.printStackTrace();
                        Log.e(TAG,"socket关闭失败");
                    }
                }
                return socket;
            }
    
            @Override
            protected void onPreExecute() {
                Log.d(TAG,"开始连接");
                if (callBack != null) callBack.onStartConnect();
            }
    
            @Override
            protected void onPostExecute(BluetoothSocket bluetoothSocket) {
                if (bluetoothSocket != null && bluetoothSocket.isConnected()){
                    Log.d(TAG,"连接成功");
                    if (callBack != null) callBack.onConnectSuccess(bluetoothDevice, bluetoothSocket);
                }else {
                    Log.d(TAG,"连接失败");
                    if (callBack != null) callBack.onConnectFail(bluetoothDevice, "连接失败");
                }
            }
        }
    
    启动连接线程
    
    /**
     * 连接 (在配对之后调用)
     * @param device
     */
    public void connect(BluetoothDevice device, ConnectBlueCallBack callBack){
        //连接之前把扫描关闭
        if (mBluetoothAdapter.isDiscovering()){
            mBluetoothAdapter.cancelDiscovery();
        }
        new ConnectBlueTask(callBack).execute(device);
    }
    

    其中ConnectBlueCallBack是个接口,用于连接之后回调

    public interface ConnectBlueCallBack{
            void onStartConnect();
            void onConnectSuccess(BluetoothDevice device,BluetoothSocket bluetoothSocket);
            void onConnectFail(BluetoothDevice device,String string);
        }
    

    所以在真正调用以上conn方法时是这样写的,传入一个连接的device,并且new一个刚才定义的接口

    connect(device, new ConnectBlueCallBack() {
                                    @Override
                                    public void onStartConnect() {
                                        Log.w( TAG, "开始连接");
                                    }
    
                                    @Override
                                    public void onConnectSuccess(BluetoothDevice device, BluetoothSocket bluetoothSocket) {
                                        Log.w( TAG, "连接成功");
                                    }
    
                                    @Override
                                    public void onConnectFail(BluetoothDevice device, String string) {
                                        Log.w( TAG, "连接失败");
                                    }
                                });
    

    值得注意的是,如果你不知道UUID也不用担心,一些常见的蓝牙服务协议已经有约定的 UUID。比如我们连接热敏打印机是基于 SPP 串口通信协议,其对应的 UUID 是 “00001101-0000-1000-8000-00805F9B34FB”,所以上边的一串数字被我写死了

    判断是否连接成功
    
    /**
     * 蓝牙是否连接
     * @return
     */
    public boolean isConnectBlue(){
        return mBluetoothSocket != null && mBluetoothSocket.isConnected();
    }
    
    断开连接
    
    /**
     * 断开连接
     * @return
     */
    public boolean cancelConnect(){
        if (mBluetoothSocket != null && mBluetoothSocket.isConnected()){
            try {
                mBluetoothSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
        }
        mBluetoothSocket = null;
        return true;
    }
    

    参考文章

    android 蓝牙锁应用开发实例(三)蓝牙相关功能实现【第一部分】
    Android蓝牙开发—经典蓝牙详细开发流程

    展开全文
  • Android 蓝牙开发(扫描设备、绑定、解绑)

    千次阅读 多人点赞 2020-07-03 11:01:15
    公司最近给我丢了一个蓝牙开发的项目,不了解怎么办呢,那当然是从最基础的开始了,所以这里相当于做笔记了。 正文 话不多说,创建一个项目才是首要的任务,创建一个名为MyBluetooth的Android项目 ...

    Android 蓝牙开发(扫描设备、绑定、解绑)

    前言

    公司最近给我丢了一个蓝牙开发的项目,不了解怎么办呢,那当然是从最基础的开始了,所以这里相当于做笔记了。

    效果图

    打开蓝牙
    在这里插入图片描述

    扫描蓝牙设备
    在这里插入图片描述
    看了效果图,你想不想自己试一下呢?扫描这个二维码下载进行测试
    在这里插入图片描述

    话不多说,创建一个项目才是首要的任务,创建一个名为MyBluetooth的Android项目。

    一、配置项目

    在工程的build.gradle中,添加

    maven { url "https://jitpack.io" }
    

    如下图所示

    在这里插入图片描述
    然后是在app下的build.gradle中添加依赖库

    	compileOptions {//指定使用的JDK1.8
            sourceCompatibility = 1.8
            targetCompatibility = 1.8
        }
    
    	//Google Material控件,以及迁移到AndroidX下一些控件的依赖
        implementation 'com.google.android.material:material:1.0.0'
        //RecyclerView最好的适配器,让你的适配器一目了然,告别代码冗余
        implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30'
        //权限请求框架
        implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'
        implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
        implementation "io.reactivex.rxjava2:rxjava:2.0.0"
    

    在这里插入图片描述
    改动之后记得Sync一下,否则不生效的。
    配置AndroidManifest.xml文件

    	<!--蓝牙连接权限-->
        <uses-permission android:name="android.permission.BLUETOOTH" />
        <!--蓝牙通讯权限-->
        <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    
        <!--位置信息  获取精准位置-->
        <!--Android 6.0及后续版本,使用蓝牙扫描,还需要添加如下的权限,且该权限还需要在使用时动态申请-->
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    

    在这里插入图片描述
    然后改动colors.xml中系统默认的颜色
    在这里插入图片描述

    然后是styles.xml文件
    在这里插入图片描述

    二、布局和样式

    图片资源
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    在drawable下创建一个名为progressbar.xml的样式文件,代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item>
            <rotate
                android:drawable="@drawable/icon_loading"
                android:fromDegrees="0.0"
                android:pivotX="50.0%"
                android:pivotY="50.0%"
                android:toDegrees="360.0" />
            <!-- 其中360.0值越大,转的圈圈越快 -->
            <span style="white-space:pre" />
        </item>
    
    </layer-list>
    

    修改activity_main.xml布局文件,代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:orientation="vertical"
        tools:context=".MainActivity">
    
        <!--标题-->
        <androidx.appcompat.widget.Toolbar
            android:elevation="3dp"
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/colorPrimary"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="我的蓝牙"
                android:textColor="#000"
                android:textSize="18sp" />
    
        </androidx.appcompat.widget.Toolbar>
    
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#EEEEEE" />
    
        <!--加载布局-->
        <LinearLayout
            android:id="@+id/loading_lay"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:visibility="gone">
    
            <ProgressBar
                android:layout_width="@dimen/dp_40"
                android:layout_height="@dimen/dp_40"
                android:indeterminate="true"
                android:indeterminateDrawable="@drawable/progressbar" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="扫描中..." />
        </LinearLayout>
    
        <!--设备展示列表-->
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv"
            android:background="#FFF"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />
    
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#EEEEEE" />
        <!--扫描蓝牙-->
        <TextView
            android:id="@+id/scan_devices"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="?android:attr/selectableItemBackground"
            android:gravity="center"
            android:text="扫描蓝牙" />
    </LinearLayout>
    

    在layout下创建列表展示的item的布局文件,名为item_device_list.xml
    在这里插入图片描述
    代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:id="@+id/item_device"
        android:background="?android:attr/selectableItemBackground"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <LinearLayout
            android:gravity="center_vertical"
            android:padding="12dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <ImageView
                android:id="@+id/iv_device_type"
                android:src="@mipmap/icon_bluetooth"
                android:layout_width="30dp"
                android:layout_height="30dp"/>
            <TextView
                android:id="@+id/tv_name"
                android:paddingLeft="12dp"
                android:textSize="16sp"
                android:text="设备名称"
                android:textColor="#000"
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="wrap_content"/>
    
    
            <TextView
                android:gravity="right"
                android:id="@+id/tv_bond_state"
                android:text="绑定状态"
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="wrap_content"/>
        </LinearLayout>
    
        <View
            android:background="#EBEBEB"
            android:layout_marginLeft="54dp"
            android:layout_width="match_parent"
            android:layout_height="1dp"/>
    </LinearLayout>
    

    三、编码

    在此之前呢,记得放一个工具类,用于改变状态栏的文字和背景颜色的。创建一个util包,包下创建一个StatusBarUtil.java文件
    在这里插入图片描述
    工具类代码如下:

    package com.llw.mybluetooth.util;
    
    import android.annotation.TargetApi;
    import android.app.Activity;
    import android.graphics.Color;
    import android.os.Build;
    import android.view.View;
    import android.view.Window;
    import android.view.WindowManager;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    /**
     * 状态栏工具类
     */
    public class StatusBarUtil {
        /**
         * 修改状态栏为全透明
         *
         * @param activity
         */
        @TargetApi(19)
        public static void transparencyBar(Activity activity) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                Window window = activity.getWindow();
                window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
                window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
                window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
                window.setStatusBarColor(Color.TRANSPARENT);
    
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                Window window = activity.getWindow();
                window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
                        WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            }
        }
    
    
        /**
         * 状态栏亮色模式,设置状态栏黑色文字、图标,
         * 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
         *
         * @param activity
         * @return 1:MIUUI 2:Flyme 3:android6.0
         */
        public static int StatusBarLightMode(Activity activity) {
            int result = 0;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                if (MIUISetStatusBarLightMode(activity, true)) {
                    result = 1;
                } else if (FlymeSetStatusBarLightMode(activity.getWindow(), true)) {
                    result = 2;
                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
                    result = 3;
                }
            }
            return result;
        }
    
        /**
         * 已知系统类型时,设置状态栏黑色文字、图标。
         * 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
         *
         * @param activity
         * @param type     1:MIUUI 2:Flyme 3:android6.0
         */
        public static void StatusBarLightMode(Activity activity, int type) {
            if (type == 1) {
                MIUISetStatusBarLightMode(activity, true);
            } else if (type == 2) {
                FlymeSetStatusBarLightMode(activity.getWindow(), true);
            } else if (type == 3) {
                activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
            }
    
        }
    
        /**
         * 状态栏暗色模式,清除MIUI、flyme或6.0以上版本状态栏黑色文字、图标
         */
        public static void StatusBarDarkMode(Activity activity, int type) {
            if (type == 1) {
                MIUISetStatusBarLightMode(activity, false);
            } else if (type == 2) {
                FlymeSetStatusBarLightMode(activity.getWindow(), false);
            } else if (type == 3) {
                activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
            }
    
        }
    
    
        /**
         * 设置状态栏图标为深色和魅族特定的文字风格
         * 可以用来判断是否为Flyme用户
         *
         * @param window 需要设置的窗口
         * @param dark   是否把状态栏文字及图标颜色设置为深色
         * @return boolean 成功执行返回true
         */
        public static boolean FlymeSetStatusBarLightMode(Window window, boolean dark) {
            boolean result = false;
            if (window != null) {
                try {
                    WindowManager.LayoutParams lp = window.getAttributes();
                    Field darkFlag = WindowManager.LayoutParams.class
                            .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
                    Field meizuFlags = WindowManager.LayoutParams.class
                            .getDeclaredField("meizuFlags");
                    darkFlag.setAccessible(true);
                    meizuFlags.setAccessible(true);
                    int bit = darkFlag.getInt(null);
                    int value = meizuFlags.getInt(lp);
                    if (dark) {
                        value |= bit;
                    } else {
                        value &= ~bit;
                    }
                    meizuFlags.setInt(lp, value);
                    window.setAttributes(lp);
                    result = true;
                } catch (Exception e) {
    
                }
            }
            return result;
        }
    
        /**
         * 需要MIUIV6以上
         *
         * @param activity
         * @param dark     是否把状态栏文字及图标颜色设置为深色
         * @return boolean 成功执行返回true
         */
        public static boolean MIUISetStatusBarLightMode(Activity activity, boolean dark) {
            boolean result = false;
            Window window = activity.getWindow();
            if (window != null) {
                Class clazz = window.getClass();
                try {
                    int darkModeFlag = 0;
                    Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
                    Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
                    darkModeFlag = field.getInt(layoutParams);
                    Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
                    if (dark) {
                        extraFlagField.invoke(window, darkModeFlag, darkModeFlag);//状态栏透明且黑色字体
                    } else {
                        extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字体
                    }
                    result = true;
    
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        //开发版 7.7.13 及以后版本采用了系统API,旧方法无效但不会报错,所以两个方式都要加上
                        if (dark) {
                            activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
                        } else {
                            activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
                        }
                    }
                } catch (Exception e) {
    
                }
            }
            return result;
        }
    
    }
    
    

    然后是创建设备列表展示数据的适配器了,创建一个adapter包,包下创建一个DeviceAdapter.java文件
    在这里插入图片描述
    适配器代码如下:

    package com.llw.mybluetooth.adapter;
    
    import android.bluetooth.BluetoothClass;
    import android.bluetooth.BluetoothDevice;
    import android.widget.ImageView;
    
    import androidx.annotation.Nullable;
    
    import com.chad.library.adapter.base.BaseQuickAdapter;
    import com.chad.library.adapter.base.BaseViewHolder;
    import com.llw.mybluetooth.R;
    
    import java.util.List;
    
    public class DeviceAdapter extends BaseQuickAdapter<BluetoothDevice, BaseViewHolder> {
        
        public DeviceAdapter(int layoutResId, @Nullable List<BluetoothDevice> data) {
            super(layoutResId, data);
        }
    
    
        @Override
        protected void convert(BaseViewHolder helper, BluetoothDevice item) {
    
            if (item.getName() == null) {
                helper.setText(R.id.tv_name, "无名");
            } else {
                helper.setText(R.id.tv_name, item.getName());
            }
    
            ImageView imageView = helper.getView(R.id.iv_device_type);
            getDeviceType(item.getBluetoothClass().getMajorDeviceClass(), imageView);
    
            //蓝牙设备绑定状态判断
            switch (item.getBondState()) {
                case 12:
                    helper.setText(R.id.tv_bond_state, "已配对");
                    break;
                case 11:
                    helper.setText(R.id.tv_bond_state, "正在配对...");
                    break;
                case 10:
                    helper.setText(R.id.tv_bond_state, "未配对");
                    break;
            }
    
            //添加item点击事件
            helper.addOnClickListener(R.id.item_device);
    
        }
    
        /**
         * 刷新适配器
         */
        public void changeBondDevice(){
            notifyDataSetChanged();
        }
    
        /**
         * 根据类型设置图标
         * @param type 类型码
         * @param imageView 图标
         */
        private void getDeviceType(int type, ImageView imageView) {
            switch (type) {
                case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES://耳机
                case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET://穿戴式耳机
                case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE://蓝牙耳机
                case BluetoothClass.Device.Major.AUDIO_VIDEO://音频设备
                    imageView.setImageResource(R.mipmap.icon_headset);
                    break;
                case BluetoothClass.Device.Major.COMPUTER://电脑
                    imageView.setImageResource(R.mipmap.icon_computer);
                    break;
                case BluetoothClass.Device.Major.PHONE://手机
                    imageView.setImageResource(R.mipmap.icon_phone);
                    break;
                case BluetoothClass.Device.Major.HEALTH://健康类设备
                    imageView.setImageResource(R.mipmap.icon_health);
                    break;
                case BluetoothClass.Device.AUDIO_VIDEO_CAMCORDER://照相机录像机
                case BluetoothClass.Device.AUDIO_VIDEO_VCR://录像机
                    imageView.setImageResource(R.mipmap.icon_vcr);
                    break;
                case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO://车载设备
                    imageView.setImageResource(R.mipmap.icon_car);
                    break;
                case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER://扬声器
                    imageView.setImageResource(R.mipmap.icon_loudspeaker);
                    break;
                case BluetoothClass.Device.AUDIO_VIDEO_MICROPHONE://麦克风
                    imageView.setImageResource(R.mipmap.icon_microphone);
                    break;
                case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO://打印机
                    imageView.setImageResource(R.mipmap.icon_printer);
                    break;
                case BluetoothClass.Device.AUDIO_VIDEO_SET_TOP_BOX://音频视频机顶盒
                    imageView.setImageResource(R.mipmap.icon_top_box);
                    break;
                case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CONFERENCING://音频视频视频会议
                    imageView.setImageResource(R.mipmap.icon_meeting);
                    break;
                case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER://显示器和扬声器
                    imageView.setImageResource(R.mipmap.icon_tv);
                    break;
                case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_GAMING_TOY://游戏
                    imageView.setImageResource(R.mipmap.icon_game);
                    break;
                case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_MONITOR://可穿戴设备
                    imageView.setImageResource(R.mipmap.icon_wearable_devices);
                    break;
                default://其它
                    imageView.setImageResource(R.mipmap.icon_bluetooth);
                    break;
            }
        }
    }
    
    

    万事俱备,现在可以进入到MainActivity.java了,进行功能的实现了。
    首先实现底部TextView的点击事件
    在这里插入图片描述
    然后会实现一个onClick方法

    	/**
         * 控件点击事件
         * @param v 视图
         */
        @Override
        public void onClick(View v) {
            if (v.getId() == R.id.scan_devices) {
                //此处进行点击后的操作
            }
        }
    

    为了使这个点击生效,所以要初始化控件。

    	private static int REQUEST_ENABLE_BLUETOOTH = 1;//请求码
    
        BluetoothAdapter bluetoothAdapter;//蓝牙适配器
    
        private TextView scanDevices;//扫描设备
        private LinearLayout loadingLay;//加载布局
        private RecyclerView rv;//蓝牙设备展示列表
        private BluetoothReceiver bluetoothReceiver;//蓝牙广播接收器
    
        private RxPermissions rxPermissions;//权限请求
    
        DeviceAdapter mAdapter;//蓝牙设备适配器
        List<BluetoothDevice> list = new ArrayList<>();//数据来源
    

    其中BluetoothReceiver这个会报红,不用慌张,这是一个内部的广播接收器,等下会创建的。

    	/**
         * 初始化控件
         */
        private void initView() {
            loadingLay = findViewById(R.id.loading_lay);
            scanDevices = findViewById(R.id.scan_devices);
            rv = findViewById(R.id.rv);
            scanDevices.setOnClickListener(this);
        }
    

    完成这个之后你的点击事件才会生效哦~
    现在基本的控件都已经初始化了,这个时候我们需要对Android的版本进行判断,看是否需要动态申请权限。我的手机是Android10.0,所以铁定是要动态申请了,不过代码上最好还是判断一下。下面检查版本

    	/**
         * 检查Android版本
         */
        private void checkVersion() {
            if (Build.VERSION.SDK_INT >= 23) {//6.0或6.0以上
                permissionsRequest();//动态权限申请
            } else {//6.0以下
                initBlueTooth();//初始化蓝牙配置
            }
        }
    

    这里面有两个方法,一个是动态权限申请,一个是初始化蓝牙配置,先来写这个初始化蓝牙配置吧。方法如下:

     	/**
         * 初始化蓝牙配置
         */
        private void initBlueTooth() {
            IntentFilter intentFilter = new IntentFilter();//创建一个IntentFilter对象
            intentFilter.addAction(BluetoothDevice.ACTION_FOUND);//获得扫描结果
            intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);//绑定状态变化
            intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//开始扫描
            intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//扫描结束
            bluetoothReceiver = new BluetoothReceiver();//实例化广播接收器
            registerReceiver(bluetoothReceiver, intentFilter);//注册广播接收器
            bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();//获取蓝牙适配器
        }
    

    这个里面的BluetoothReceiver依然会报红,不过管它,等会再说,心急吃不了热豆腐。
    然后是动态权限申请的代码

    	/**
         * 动态权限申请
         */
        private void permissionsRequest() {//使用这个框架使用了Lambda表达式,设置JDK版本为 1.8或者更高
            rxPermissions = new RxPermissions(this);//实例化这个权限请求框架,否则会报错
            rxPermissions.request(Manifest.permission.ACCESS_FINE_LOCATION)
                    .subscribe(granted -> {
                        if (granted) {//申请成功
                            initBlueTooth();//初始化蓝牙配置
                        } else {//申请失败
                            showMsg("权限未开启");
                        }
                    });
        }
    

    这里可以看到,我在权限申请成功之后进行蓝牙初始化,失败则给一个提示,这个地方是一个静态的方法,其实就是弹出一个Toast,但是Android原生的代码太长了,所以这里我写个方法来调用显示,看起来会简洁很多。方法如下:

    	/**
         * 消息提示
         *
         * @param msg 消息内容
         */
        private void showMsg(String msg) {
            Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
        }
    

    OK,现在关于权限的问题就已经是解决了,接下来就该扫描了吧。让我们回到onClick方法那里,在这里首先要获取蓝牙适配器,这一步我们再初始化蓝牙配置的里面就已经做好了,所以这里只要判断是否为空就可以了。如果不为空我再判断蓝牙是否打开,如果没有打开,就要去打开,如果已经打开了就开始扫描,于是下面的代码就这样写。

    /**
         * 控件点击事件
         * @param v 视图
         */
        @Override
        public void onClick(View v) {
            if (v.getId() == R.id.scan_devices) {
                if (bluetoothAdapter != null) {//是否支持蓝牙
                    if (bluetoothAdapter.isEnabled()) {//打开
                        //开始扫描周围的蓝牙设备,如果扫描到蓝牙设备,通过广播接收器发送广播
                        bluetoothAdapter.startDiscovery();
                    } else {//未打开
                        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                        startActivityForResult(intent, REQUEST_ENABLE_BLUETOOTH);
                    }
                } else {
                    showMsg("你的设备不支持蓝牙");
                }
            }
        }
    
    

    这个应该一目了然吧,不过打开蓝牙是会有一个返回的,因为我们用的是startActivityForResult,所以要在返回里做确认。

    	/**
         * 结果返回
         *
         * @param requestCode 请求码
         * @param resultCode  结果码
         * @param data
         */
        @Override
        protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (requestCode == REQUEST_ENABLE_BLUETOOTH) {
                if (resultCode == RESULT_OK) {
                    showMsg("蓝牙打开成功");
                } else {
                    showMsg("蓝牙打开失败");
                }
            }
        }
    

    写代码是讲究这个逻辑的,所以很多东西不是只看表面,细节也是很重要的。
    通过上面的代码,我们已经实现了点击扫描时,如果蓝牙已打开则扫描周边蓝牙设备,但是扫描的结果呢?这时你有没有想到我们之前一直报红的BluetoothReceiver呢?该它出马了。

    	/**
         * 广播接收器
         */
        private class BluetoothReceiver extends BroadcastReceiver {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                switch (action) {
                    case BluetoothDevice.ACTION_FOUND://扫描到设备
                        showDevicesData(context, intent);//数据展示
                        break;
                    case BluetoothDevice.ACTION_BOND_STATE_CHANGED://设备绑定状态发生改变
                        mAdapter.changeBondDevice();//刷新适配器
                        break;
                    case BluetoothAdapter.ACTION_DISCOVERY_STARTED://开始扫描
                        loadingLay.setVisibility(View.VISIBLE);//显示加载布局
                        break;
                    case BluetoothAdapter.ACTION_DISCOVERY_FINISHED://扫描结束
                        loadingLay.setVisibility(View.GONE);//隐藏加载布局
                        break;
                }
            }
    
        }
    

    这里还是要做一下简单的说明,我之前在初始化蓝牙的时候加了四个过滤器,所以这里就可以在接收的时候做处理了,从而实现相应的操作,还有一个就是这个广播接收器是和onCreate方法平级的,所以只要是在MainActivity这个{}里面,你想放哪就放哪。代码里面的注释已经说明了一切,我们现在应该最关心的是这个数据展示的方法了吧!OK,下面看这个方法。

    	/**
         * 显示蓝牙设备信息
         *
         * @param context 上下文参数 
         * @param intent  意图
         */
        private void showDevicesData(Context context, Intent intent) {
            getBondedDevice();//获取已绑定的设备
            //获取周围蓝牙设备
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    
            if (list.indexOf(device) == -1) {//防止重复添加
    
                if (device.getName() != null) {//过滤掉设备名称为null的设备
                    list.add(device);
                }
            }
            mAdapter = new DeviceAdapter(R.layout.item_device_list, list);
            rv.setLayoutManager(new LinearLayoutManager(context));
            rv.setAdapter(mAdapter);
    
            mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
                @Override
                public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
                    //点击时获取状态,如果已经配对过了就不需要在配对
                    if (list.get(position).getBondState() == BluetoothDevice.BOND_NONE) {
                        createOrRemoveBond(1, list.get(position));//开始匹配
                    } else {
                        showDialog("确定要取消配对吗?", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                //取消配对
                                createOrRemoveBond(2, list.get(position));//取消匹配
                            }
                        });
                    }
                }
            });
        }
    

    这个时候你要是首先这一段代码的话,你肯定会发现很多报红,因为你还没有创建相应的方法的。首先来看getBondedDevice() 这个方法,用户获取已绑定的设备。

    	/**
         * 获取已绑定设备
         */
        private void getBondedDevice() {
            Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
            if (pairedDevices.size() > 0) {//如果获取的结果大于0,则开始逐个解析
                for (BluetoothDevice device : pairedDevices) {
                    if (list.indexOf(device) == -1) {//防止重复添加
                        if (device.getName() != null) {//过滤掉设备名称为null的设备
                            list.add(device);
                        }
                    }
                }
            }
        }
    

    这个方法也比较简单,相信我不解释你也明白的。
    然后是createOrRemoveBond 这个方法用于绑定或者解绑设备,里面传入两个参数一个是类型,另一个是设备。方法代码如下:

     	/**
         * 创建或者取消匹配
         *
         * @param type 处理类型 1 匹配  2  取消匹配
         * @param device 设备
         */
        private void createOrRemoveBond(int type, BluetoothDevice device) {
            Method method = null;
            try {
                switch (type) {
                    case 1://开始匹配
                        method = BluetoothDevice.class.getMethod("createBond");
                        method.invoke(device);
                        break;
                    case 2://取消匹配
                        method = BluetoothDevice.class.getMethod("removeBond");
                        method.invoke(device);
                        list.remove(device);//清除列表中已经取消了配对的设备
                        break;
                }
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
    
        }
    

    这样写的好处就是看起来数据一些,虽然页面上方法比较多,但是逻辑上是一环扣一环的,也没有什么解释的必要了,内容一目了然。
    最后来看showDialog 这个方法就是显示一个弹窗,使用户的操作没有那么突兀,方法如下。

    	/**
         * 弹窗
         * @param dialogTitle 标题
         * @param onClickListener  按钮的点击事件
         */
        private void showDialog(String dialogTitle, @NonNull DialogInterface.OnClickListener onClickListener) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage(dialogTitle);
            builder.setPositiveButton("确定", onClickListener);
            builder.setNegativeButton("取消", null);
            builder.create().show();
        }
    

    现在你再回头看showDevicesData,这个方法里面就不会再有报红了。
    然后再优化一下onClick
    在这里插入图片描述
    在onClick方法中加入:

    					if (mAdapter != null) {//当适配器不为空时,这时就说明已经有数据了,所以清除列表数据,再进行扫描
                            list.clear();
                            mAdapter.notifyDataSetChanged();
                        }
    

    然后在onCreate方法中调用

     	@Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            StatusBarUtil.StatusBarLightMode(this);//状态栏黑色字体
    
            initView();//初始化控件
    
            checkVersion();//检查版本
        }
    

    最后在onDestroy

    	/**
         * 销毁
         */
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //卸载广播接收器
            unregisterReceiver(bluetoothReceiver);
        }
    

    至此,这个功能就写完了,效果如下图所示:

    在这里插入图片描述

    四、源码

    GitHub: MyBluetooth-Java

    GitHub: MyBluetooth-Kotlin

    有什么问题欢迎提出,当然你也可以给我发邮件
    lonelyholiday@qq.com
    我是初学者-Study,山高水长,后会有期~

    展开全文
  • Android蓝牙开发与串口蓝牙通讯

    千次阅读 2021-11-30 11:41:21
    Android开发平台示例:BluetoothLeGatt https://github.com/android/connectivity-samples/tree/main/BluetoothLeGatt/ 1、设备是否支持蓝牙 //获取蓝牙适配器 BluetoothAdapter mBluetoothAdapter = ...
  • B站Android蓝牙开发学习(Rex老师)视频讲座的的最终源码,BlueToothClass6,已适配最新AS。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 31,556
精华内容 12,622
关键字:

android蓝牙开发