Android经典蓝牙开发全流程

Android经典蓝牙开发全流程一 基本介绍 所谓蓝牙 Bluetooth 技术 实际上是一种短距离无线电技术 最初是由爱立信公司公司发明的 技术始于爱立信公司 1994 方案 它是研究在移动电话和其他配件间进行低功耗 低成本无线通信连接的方法 发明者希望为设备间的通讯创造一组统一规则 标准化协议 用来解决用户间相互不兼容的移动电子设备 1998 年 5 月 20 日 索尼以立信 国际商业机器 英特尔 诺基亚及东芝公司等业界龙头创立 特别兴趣小组 SpecialInter 即蓝牙技术联盟的前身 目标是开发一

一、基本介绍

在这里插入图片描述

  蓝牙发展至今经历了多个版本的更新,1.1、1.2、2.0、2.1、3.0、4.0、4.1、4.2、5.0等。其中,将1.x~3.0之间的版本称之为经典蓝牙,4.x开始的蓝牙称之为低功耗蓝牙,也就是蓝牙ble。根据应用、协议类型等,可以对蓝牙进行以下分类:

在这里插入图片描述

二、经典蓝牙API介绍

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

  • 扫描其他蓝牙设备
  • 查询本地蓝牙适配器的配对蓝牙设备
  • 建立 RFCOMM 通道
  • 通过服务发现连接到其他设备
  • 与其他设备进行双向数据传输
  • 管理多个连接

以下对经典蓝牙开发相关的API进行介绍:

1、BluetoothAdapter类

  BluetoothAdapter代表了移动设备的本地的蓝牙适配器, 通过该蓝牙适配器可以对蓝牙进行基本操作, 例如 : 启动设备发现,获取已配对设备,通过mac蓝牙地址获取蓝牙设备等。

(1)获取本地蓝牙适配器实例

方法定义:

/ * 作用: * 获取本地蓝牙适配器实例 * 参数: * 无 * 返回: * 如果设备具备蓝牙功能,返回BluetoothAdapter 实例;否则,返回null对象。 */ public static synchronized BluetoothAdapter getDefaultAdapter(); 

使用说明:

1、获取默认本地蓝牙适配器的句柄。目前Android仅支持一个蓝牙适配器,但该API可以扩展为支持更多。

(2)打开蓝牙

方法定义:

/ * 作用: * 打开蓝牙 * 参数: * 无 * 返回: * 如果蓝牙开始打开,则返回true;如果蓝牙打开发生问题,则返回false。 */ public boolean enable(); 

使用说明:

(3)关闭蓝牙

方法定义:

/ * 作用: * 关闭蓝牙 * 参数: * 无 * 返回: * 如果蓝牙开始关闭,则返回true;如果蓝牙关闭发生问题,则返回false。 */ public boolean disable(); 

使用说明:

(4)验证蓝牙MAC地址

方法定义:

/ * 作用: * 验证蓝牙设备MAC地址是否有效。 * 参数: * address:蓝牙MAC地址,字母必须大写,例如:"00:43:A8:23:10:F1"。 * 返回: * 如果蓝牙MAC地址有效,则返回true;否则返回false。 */ public static boolean checkBluetoothAddress(String address); 

使用说明:

(5)获取本地蓝牙MAC地址

方法定义:

/ * 作用: * 获取本地蓝牙适配器的硬件地址(MAC地址) * 参数: * 无 * 返回: * 本地的硬件地址,例如:"00:11:22:AA:BB:CC"。 */ public String getAddress(); 

使用说明:

1、需要 BLUETOOTH权限。

(6)获取蓝牙绑定列表

方法定义:

/ * 作用: * 获取与本机蓝牙所有绑定的远程蓝牙信息。 * 参数: * 无 * 返回: * 将本地蓝牙适配器绑定的一组BluetoothDevice对象返回。若出现错误返回null。 */ public Set 
   
     getBondedDevices(); 
   

使用说明:

(7)获取蓝牙名称

方法定义:

/ * 作用: * 获取本地蓝牙适配器的蓝牙名称。 * 参数: * 无 * 返回: * 本地蓝牙名称。若出现错误,返回null。 */ public String getName(); 

使用说明:

1、需要 BLUETOOTH权限。

(8)设置蓝牙名称

方法定义:

/ * 作用: * 设置本地蓝牙适配器的蓝牙名称。 * 参数: * name:蓝牙名称。 * 返回: * 设置成功返回true,否则返回false。 */ public boolean setName(String name); 

使用说明:

(9)获取蓝牙扫描模式

方法定义:

/ * 作用: * 获取本地蓝牙适配器的当前蓝牙扫描模式。 * 参数: * 无 * 返回: * 当前蓝牙适配器的蓝牙扫描模式。 */ public int getScanMode(); 

使用说明:

蓝牙扫描模式:

名称 值(int) 含义
SCAN_MODE_NONE 20 该设备不能扫描以及被扫描。
SCAN_MODE_CONNECTABLE 21 该设备可以扫描其他蓝牙设备。
SCAN_MODE_CONNECTABLE_DISCOVERABLE 23 该设备既可以扫描其他设备,也可以被其他设备扫描发现。

(10)获取蓝牙适配器状态

方法定义:

/ * 作用: * 获取本地蓝牙适配器的当前状态。 * 参数: * 无 * 返回: * 当前蓝牙适配器状态。 */ public int getState(); 

使用说明:

1、需要 BLUETOOTH权限。

蓝牙适配器状态:

名称 值(int) 含义
STATE_OFF 10 表示本地蓝牙适配器已关闭
STATE_TURNING_ON 11 表示本地蓝牙适配器正在打开
STATE_ON 12 表示本地蓝牙适配器已开启,并可供使用
STATE_TURNING_OFF 13 表示本地蓝牙适配器正在关闭

(11)蓝牙是否打开

方法定义:

/ * 作用: * 判断当前蓝牙适配器是否打开 * 参数: * 无 * 返回: * 若蓝牙为打开状态,则返回true,否则返回false。 */ public boolean isEnabled(); 

使用说明:

(12)蓝牙是否正在扫描

方法定义:

/ * 作用: * 判断蓝牙适配器是否正在处于扫描过程中。 * 参数: * 无 * 返回: * 若蓝牙处于扫描状态,则返回true;否则返回false。 */ public boolean isDiscovering(); 

使用说明:

(13)启动扫描

方法定义:

/ * 作用: * 开始扫描周边蓝牙设备。 * 参数: * 无 * 返回: * 若启动成功,返回true;否则返回false。 */ public boolean startDiscovery(); 

使用说明:

(14)取消扫描

方法定义:

/ * 作用: * 取消蓝牙搜索操作 * 参数: * 无 * 返回: * 如果取消成功, 则返回true; 如果取消失败, 返回false。 */ public boolean cancelDiscovery() 

(15)获取远程蓝牙设备

方法定义:

/ * 作用: * 获取给定蓝牙硬件地址的BluetoothDevice对象。 * 参数: * address:蓝牙MAC地址,字母必须大写,例如:"00:43:A8:23:10:F1"。 * 返回: * 指定的远程蓝牙设备。 */ public BluetoothDevice getRemoteDevice(String address); 

使用说明:

1、如果MAC无效无效,将抛出IllegalArgumentException异常。

(16)创建不安全的蓝牙服务套接字

方法定义:

/ * 作用: * 创建一个正在监听的不安全的带有服务记录的无线射频通信(RFCOMM)蓝牙端口。 * 参数: * name:SDP记录下的服务器名,可以是任意字符串。 * uuid:SDP记录下的UUID。 * 返回: * BluetoothServerSocket对象。 */ public BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid); 

使用说明:

(17)创建安全的蓝牙服务套接字

方法定义:

/ * 作用: * 创建一个正在监听的安全的带有服务记录的无线射频通信(RFCOMM)蓝牙端口。 * 参数: * name:SDP记录下的服务器名,可以是任意字符串。 * uuid:SDP记录下的UUID。 * 返回: * BluetoothServerSocket对象。 */ public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid); 

使用说明:

2、BluetoothDevice类

  BluetoothDevice对象代表了一个远程的蓝牙设备, 通过这个类可以查询远程设备的物理地址, 名称, 连接状态等信息。这个类实际上只是一个蓝牙硬件地址的简单包装,这个类的对象是不可变的。对这个类的操作, 会执行在远程蓝牙设备的硬件上。

(1)获取蓝牙名称

方法定义:

/ * 作用: * 获取远程蓝牙设备的蓝牙名称。 * 参数: * 无 * 返回: * 成功则返回蓝牙名称,若出现问题则返回null。 */ public String getName(); 

使用说明:

(2)获取蓝牙MAC

方法定义:

/ * 作用: * 获取远程蓝牙设备的硬件地址 * 参数: * 无 * 返回: * 蓝牙硬件地址 */ public String getAddress(); 

使用说明:

(3)获取蓝牙绑定状态

方法定义:

/ * 作用: * 获取远程蓝牙设备的绑定状态 * 参数: * 无 * 返回: * 蓝牙绑定状态 */ public int getBondState(); 

使用说明:

1、需要BLUETOOTH权限。

蓝牙绑定状态:

名称 值(int) 含义
BOND_NONE 10 远程设备未绑定。
BOND_BONDING 11 正在与远程设备进行绑定。
BOND_BONDED 12 远程设备已绑定。

(4)绑定远程设备

方法定义:

/ * 作用: * 开始与远程蓝牙设备的绑定过程。 * 参数: * 无 * 返回: * 若成功开始绑定则返回true,否则返回false。 */ public boolean createBond(); 

使用说明:

(5)创建不安全的蓝牙套接字

方法定义:

/ * 作用: * 创建不安全的蓝牙套接字。 * 参数: * uuid:用于查找RFCOMM通道的服务记录UUID * 返回: * 一个准备好外界连接的RFCOMM蓝牙服务端口 */ public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid); 

使用说明:

(6)创建安全的蓝牙套接字

方法定义:

/ * 作用: * 创建安全的蓝牙套接字。 * 参数: * uuid:用于查找RFCOMM通道的服务记录UUID * 返回: * 一个准备好外界连接的RFCOMM蓝牙服务端口 */ public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid); 

使用说明:

3、BluetoothServerSocket类

  BluetoothServerSocket是一个侦听蓝牙服务套接字。使用BluetoothServerSocket可以创建一个监听服务端口, 使用accept方法阻塞, 当该方法监测到连接的时候, 就会返回一个BluetoothSocket对象来管理这个连接。BluetoothServerSocket是线程安全的,close方法始终会立即中止正在进行的操作并关闭蓝牙服务套接字。需要BLUETOOTH权限。

(1)阻塞等待连接(无超时)

方法定义:

/ * 作用: * 阻塞直到建立连接。 * 参数: * 无 * 返回: * 成功连接时连接的BluetoothSocket对象。 */ public BluetoothSocket accept(); 

使用说明:

(2)阻塞等待连接(有超时)

方法定义:

/ * 作用: * 阻塞直到建立连接或超时。 * 参数: * timeout:阻塞等待的超时时间。 * 返回: * 成功连接时连接的BluetoothSocket对象。 */ public BluetoothSocket accept(int timeout); 

使用说明:

(3)关闭

方法定义:

/ * 作用: * 关闭该监听服务端口,并释放所有关联的资源。 * 参数: * 无 * 返回: * 无 */ public void close(); 

使用说明:

4、BluetoothSocket类

  BluetoothSocket是蓝牙套接口。在服务器端,使用BluetoothServerSocket创建侦听服务器套接字。当连接被BluetoothServerSocket接受时,它将返回一个新的BluetoothSocket来管理连接。 在客户端,使用单个BluetoothSocket来启动连接并管理连接。最常见的蓝牙套接字类型是RFCOMM,它是Android API支持的类型。 RFCOMM是一种通过蓝牙实现的面向连接的流媒体传输。它也被称为串口行为规范(SPP)。BluetoothSocket是线程安全的,close方法始终会立即中止正在进行的操作并关闭套接字。需要BLUETOOTH权限。

(1)连接

方法定义:

/ * 作用: * 尝试连接到远程蓝牙服务器。 * 参数: * 无 * 返回: * 无 */ public void connect(); 

使用说明:

(2)是否连接

方法定义:

/ * 作用: * 获取此套接字的连接状态,即是否与远程蓝牙服务连接。 * 参数: * 无 * 返回: * 若连接则返回true,否则返回false。 */ public boolean isConnected(); 

使用说明:

(3)获取远程蓝牙设备

方法定义:

/ * 作用: * 获取此套接字连接的远程蓝牙设备。 * 参数: * 无 * 返回: * 连接的远程蓝牙设备BluetoothDevice对象。 */ public BluetoothDevice getRemoteDevice(); 

使用说明:

(4)获取输入流

方法定义:

/ * 作用: * 获取与此套接字关联的输入流。 * 参数: * 无 * 返回: * 输入流对象。 */ public InputStream getInputStream(); 

使用说明:

(5)获取输出流

方法定义:

/ * 作用: * 获取与此套接字关联的输出流。 * 参数: * 无 * 返回: * 输出流对象。 */ public OutputStream getOutputStream(); 

使用说明:

(6)关闭

方法定义:

/ * 作用: * 关闭此流并释放与其关联的所有系统资源。如果流已经关闭,则调用此方法不起作用。 * 参数: * 无 * 返回: * 无 */ public void close(); 

使用说明:

1、该方法调用出现问题,将抛出IOException异常。

三、经典蓝牙开发流程

1、经典蓝牙开发流程分析

在这里插入图片描述

2、蓝牙服务端实现

(1)在工程清单文件AndroidManifest.xml中添加权限:
 
        
        
        
        
        
        
        
        
(2)获取本地蓝牙适配器:
BluetoothAdapter mAdapter= BluetoothAdapter.getDefaultAdapter(); 
(3)打开蓝牙:
//方式一:通过Intent来向用户弹框请求打开蓝牙,可以重写onActivityResult来监听打开蓝牙的请求结果 //打开蓝牙 public void openBluetooth(){ if(mBluetoothAdapter==null){ //自定义方法,用来往TextView上添加提示信息 showTip("当前设备不支持蓝牙功能!"); return; } if(mBluetoothAdapter.isEnabled()){ showTip("蓝牙已打开"); return; } Intent intent=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(intent,GlobalDef.REQ_CODE_OPEN_BT); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(requestCode==GlobalDef.REQ_CODE_OPEN_BT){ if(resultCode == Activity.RESULT_OK){ showTip("蓝牙打开成功"); } else{ showTip("蓝牙打开失败"); } } } //方式二:通过enable方法静默打开蓝牙,无需用户同意(部分Android系统使用该方法依然会弹框提示,向用户请求打开蓝牙) mBluetoothAdapter.enable(); 
(4)关闭蓝牙
//关闭蓝牙,无需用户同意(部分Android系统使用该方法依然会弹框提示) mBluetoothAdapter.disable(); 
(5)允许蓝牙可见:
//方式一:通过Intent方式向用户请求允许蓝牙被搜索 //注:如果蓝牙没有开启,用户点击确定后,会首先开启蓝牙,继而设置蓝牙能被扫描 Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); //设置蓝牙可见性的时间,默认持续时间为120秒,每个请求的最长持续时间上限为300秒 intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120); startActivity(intent); //方式二:通过反射的方式来设置蓝牙可见性,且不会出现弹框 //注:如果蓝牙没有开启,通过此方式并不会直接打开蓝牙 / * 设置蓝牙可见 * @param adapter * @param timeout 超时为0时,永久可见 */ public static void setDiscoverableTimeout(BluetoothAdapter adapter, int timeout) { //BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter(); try { Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class); setDiscoverableTimeout.setAccessible(true); Method setScanMode =BluetoothAdapter.class.getMethod("setScanMode", int.class,int.class); setScanMode.setAccessible(true); setDiscoverableTimeout.invoke(adapter, timeout); setScanMode.invoke(adapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,timeout); } catch (Exception e) { e.printStackTrace(); } } / * 关闭蓝牙可见 * @param adapter */ public static void closeDiscoverableTimeout(BluetoothAdapter adapter) { try { Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class); setDiscoverableTimeout.setAccessible(true); Method setScanMode =BluetoothAdapter.class.getMethod("setScanMode", int.class,int.class); setScanMode.setAccessible(true); setDiscoverableTimeout.invoke(adapter, 1); setScanMode.invoke(adapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE,1); } catch (Exception e) { e.printStackTrace(); } } 
(6)创建蓝牙服务套接字,等待其他蓝牙客户端连接:
try{ mSocketList=new LinkedList 
       
         ();//用来管理连接的蓝牙套接字 mExecutorService= Executors.newCachedThreadPool();//创建线程池 //创建蓝牙服务端 mServerSocket=mBluetoothAdapter.listenUsingRfcommWithServiceRecord("BluetoothTool", GlobalDef.BT_UUID); mServerRunningFlag=true; showTip("蓝牙服务端成功启动"); new Thread(){ @Override public void run(){ try{ BluetoothSocket socket=null; //循环等待蓝牙socket连接 while(mServerRunningFlag){ socket=mServerSocket.accept();//阻塞式 mSocketList.add(socket); //SocketThread为自定义的线程类,用于管理BluetoothSocket的读写操作 mExecutorService.execute(new SocketThread(socket)); } }catch (Exception e){ e.printStackTrace(); } } }.start(); }catch(IOException e){ e.printStackTrace(); showTip("服务端启动出现异常"); Log.e(TAG,"runServer IOException"); } 
       
(7)连接成功之后,就通过获取BluetoothSocket的输入输出流进行数据传输:
// 获取流 InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); // 写出、读入 byte[] temp=new byte[1024]; inputStream.read(temp);//当无数据时将阻塞等待 outputStream.write(temp); 
(8)以下为用于操作BluetoothSocket的SocketThread的简单实现,仅供参考:
class SocketThread extends Thread { private BluetoothSocket mSocket=null; private InputStream mIn; private OutputStream mOut; private boolean isOpen = false; public SocketThread(BluetoothSocket socket) { try { mSocket=socket; mIn = mSocket.getInputStream(); mOut = mSocket.getOutputStream(); isOpen = true; Log.d(TAG, "a socket thread create"); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "create SocketThread fail"); } } @Override public void run() { int readLen=0; byte[] buffer=new byte[1024]; try{ while(isOpen){ readLen=mIn.read(buffer); if(readLen>0){ Log.i(TAG,"read data length="+readLen); Log.i(TAG,"read data hex = "+StringUtil.bytesToHexString(buffer,0,readLen)); } } }catch (Exception e){ e.printStackTrace(); release(); } } / * 写入数据 * @param data * @param offset * @param len */ public void writeData(byte[] data,int offset,int len){ if (data == null || offset<0 || len<=0 || (len+offset)>data.length) { Log.e(TAG,"BT writeData params fail"); return; } try { mOut.write(data,offset,len); mOut.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void release(){ Log.d(TAG,"A socketThread release"); try{ isOpen=false; if(mOut!=null){ try{ mOut.close(); }catch (Exception e){ e.printStackTrace(); } mOut=null; } if(mIn!=null){ try{ mIn.close(); }catch (Exception e){ e.printStackTrace(); } mIn=null; } if(mSocket!=null){ try{ mSocket.close(); }catch (Exception e){ e.printStackTrace(); } mSocket=null; } }catch (Exception e){ e.printStackTrace(); } } } 

3、蓝牙客户端实现

(1)添加权限,同蓝牙服务端。
(2)获取本地蓝牙适配器,同蓝牙服务端。
(3)打开蓝牙,同蓝牙服务端。
(4)关闭蓝牙,同蓝牙服务端。
(5)允许蓝牙可见,同蓝牙服务端。
(6)定义蓝牙广播接收器,用于接收蓝牙搜索、连接状态改变等的广播:
class BluetoothBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent){ String action=intent.getAction(); Log.d(TAG,"Action received is "+action); //蓝牙搜索 if(BluetoothDevice.ACTION_FOUND.equals(action)){ BluetoothDevice scanDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if(scanDevice == null || scanDevice.getName() == null){ return; } int btType=scanDevice.getType(); if(btType==BluetoothDevice.DEVICE_TYPE_LE || btType==BluetoothDevice.DEVICE_TYPE_UNKNOWN){ return; } Log.d(TAG, "bt name="+scanDevice.getName()+" address="+scanDevice.getAddress()); //将搜索到的蓝牙设备加入列表 deviceList.add(scanDevice); short rssi=intent.getExtras().getShort(BluetoothDevice.EXTRA_RSSI); rssiList.add(rssi); listAdapter.notifyDataSetChanged();//通知ListView适配器更新 } //蓝牙配对 else if(BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)){ BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if(mCurDevice!=null && btDevice.getAddress().equals(mCurDevice.getAddress())){ int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); if(state==BluetoothDevice.BOND_NONE){ showTip("已取消与设备" + btDevice.getName() + "的配对"); mFlag=-1; } else if(state==BluetoothDevice.BOND_BONDED){ showTip("与设备" + btDevice.getName() + "配对成功"); mFlag=1; } } } else if(BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)){ int blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0); switch (blueState) { case BluetoothAdapter.STATE_TURNING_ON: Log.i(TAG,"onReceive---------STATE_TURNING_ON"); break; case BluetoothAdapter.STATE_ON: Log.i(TAG,"onReceive---------STATE_ON"); showTip("蓝牙当前状态:ON"); break; case BluetoothAdapter.STATE_TURNING_OFF: Log.i(TAG,"onReceive---------STATE_TURNING_OFF"); break; case BluetoothAdapter.STATE_OFF: Log.i(TAG,"onReceive---------STATE_OFF"); showTip("蓝牙当前状态:OFF"); break; } } } } 
(7)注册广播:
 mBluetoothBroadcastReceiver=new BluetoothBroadcastReceiver(); IntentFilter filter=new IntentFilter(); filter.addAction(BluetoothDevice.ACTION_FOUND); filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); mContext.registerReceiver(mBluetoothBroadcastReceiver,filter); 
(8)搜索周边蓝牙设备:
 if(mBluetoothAdapter.isDiscovering()){ mBluetoothAdapter.cancelDiscovery(); } //搜索到的蓝牙设备通过广播接收 mBluetoothAdapter.startDiscovery(); 
(9)建立与蓝牙服务器的连接:
/ * 蓝牙配对并连接 */ public void bondAndConnect(BluetoothDevice mCurDevice){ //取消搜索 if(mBluetoothAdapter.isDiscovering()){ mBluetoothAdapter.cancelDiscovery(); } if(mCurDevice==null){ showTip("远程蓝牙设备为空!"); return; } //当前蓝牙设备未配对,则先进行配对 if(mCurDevice.getBondState()==BluetoothDevice.BOND_NONE){ Log.d(TAG,"create bond to "+mCurDevice.getName()); boolean nRet= BluetoothUtil.createBond(mCurDevice); if(!nRet){ showTip("createBond fail!"); return; } showLoadingDialog("正在与【"+mCurDevice.getName()+"】进行配对..."); mFlag=0; while(mFlag==0){ SystemClock.sleep(250); } if(mFlag==-1){ showTip("与【"+mCurDevice.getName()+"】的蓝牙配对失败"); dismissLoadingDialog(); return; } } if(mCurDevice.getBondState()==BluetoothDevice.BOND_BONDED){ showLoadingDialog("正在与【"+mCurDevice.getName()+"】进行连接..."); try { //创建Socket BluetoothSocket socket = mCurDevice.createRfcommSocketToServiceRecord(GlobalDef.BT_UUID); //连接蓝牙服务套接字 socket.connect(); mThread=new SocketThread(socket); mThread.start(); showTip(("成功与【"+mCurDevice.getName()+"】建立连接")); } catch (IOException e) { Log.d(TAG,"socket connect fail"); showTip(("连接【"+mCurDevice.getName()+"】失败")); e.printStackTrace(); } } dismissLoadingDialog(); } 
(10)连接成功之后,就通过输入输出流进行数据传输,同蓝牙服务端。

四、注意事项与常见问题

1、Android6.0以后,搜索蓝牙设备需要位置权限(危险权限,需要动态申请)。
2、高版本Android系统上进行蓝牙搜索时,除了动态申请位置权限外,有的可能还需要手动打开设备的位置信息,否则无法搜索蓝牙。

在这里插入图片描述

3、搜索周边蓝牙设备时,本机并不需要处于蓝牙可见状态。但其他设备必须处于蓝牙可见状态,本机才可以搜索到。已知蓝牙设备(处于有效范围内)的MAC地址,则随时可以向其发起连接,而无需打开蓝牙可见性。
4、如果尚未在设备上启用蓝牙,则启用设备可检测性将会自动启用蓝牙。
5、搜索周边设备对于蓝牙适配器而言是一个非常繁重的操作过程,并且会消耗大量资源。在找到要连接的设备后,确保始终使用 cancelDiscovery() 停止发现,然后再尝试连接。 此外,如果已经保持与某台设备的连接,那么执行搜索操作可能会大幅减少可用于该连接的带宽,因此不应该在处于连接状态时执行搜索周边蓝牙的操作。
6、在调用 connect() 时,应始终确保设备未在进行搜索操作。 如果正在进行搜索操作,则会大幅降低连接尝试的速度,并增加连接失败的可能性。
7、搜索周边设备,通过广播能够获取到周边蓝牙设备的名称、mac地址、rssi信号强度等。
8、BluetoothSocket是线程安全的, close()方法会终止BluetoothSocket进行的一切操作, 并且同时会关闭连接。
9、经典蓝牙是通过流来进行数据收发的,对流进行数据读取操作时,由于接收方不知道消息之间的界限,不知道一次性提取多少字节的数据,因而容易产生数据粘包问题。对于这种问题,可以给每个完整的数据包添加一个起始符与结束符,那么接收方就可以确定需要读取并处理的数据范围。

具体的演示demo放在Github上,感兴趣的同学可以了解一下。如果有什么问题,欢迎来一起讨论,共同进步。

GitHub地址:https://github.com/JINSHENGCCC/Android_Common/tree/master/Android-BT/src

五、附录

  1. 深入了解Android蓝牙Bluetooth——《基础篇》
  2. Android 经典蓝牙开发
  3. Android蓝牙开发—经典蓝牙详细开发流程
  4. 蓝牙概览




















版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/210184.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 上午8:06
下一篇 2026年3月19日 上午8:07


相关推荐

  • linux dstat,使用Dstat来进行Linux综合性能诊断

    linux dstat,使用Dstat来进行Linux综合性能诊断性能测试、评估和优化一直是系统管理维护人员工作的重点。当我们针对一台生产应用进行分析的时候,获取如CPU、内存、IO、网络吞吐和进程负载的基础数据,对于后续的性能评测和优化是至关重要的。Linux作为目前应用最广泛的服务器操作系统,为了应对各种性能问题,已经发展出很多原生的性能检测工具。从top、vmstat、iostat到mpstat,已经可以对操作系统主要性能方面进行详细的分析。面对越来越复杂…

    2022年6月24日
    23
  • 用户测试用例模板「建议收藏」

    用户测试用例模板「建议收藏」用户测试用例模板

    2022年7月17日
    16
  • Vim中如何全选复制粘贴、批量注释

    Vim中如何全选复制粘贴、批量注释Vim中如何全选并复制?(区分大小写!!!)全部删除:按esc键后,先按gg(到达顶部),然后dG全部复制:按esc键后,先按gg,然后ggyG全选高亮显示:按esc键后,先按gg,然后ggvG或者ggVG单行复制:按esc键后,然后yy单行删除:按esc键后,然后dd粘贴:按esc键后,然后pvim只能粘贴50行的问题:在当前用户主目录(~)编辑~/.v

    2025年11月26日
    7
  • PS修补工具使用方法[通俗易懂]

    PS修补工具使用方法[通俗易懂]使用场景:当我们在选取图片时,发现有多余的部分想要去除时可以通过PS中的修补工具来帮助我们实现这一操作,如果有对此功能不是很了解的小伙伴请看以下教程,小编将为大家带来PS修补工具的使用方法。方法步骤:一:把一张需要抹除的照片导入到Photoshop中二:然后找到Photoshop工具栏中的这个图标,点击选择”修复画笔工具”三:按住鼠标左键,把要抹去的部分选取出来.选取完成后会有一条虚线.四:保持鼠标在虚线内,按住鼠标左键,向相似的地方移动,这个时候你就会发现鼠标移动到的地方就是填.

    2022年6月23日
    39
  • linux时间戳命名表达式,Linux时间戳

    linux时间戳命名表达式,Linux时间戳在 Linux 系统中每一个文件都有 3 种时间定义 称之为时间戳 timestamps 这三种时间分别为 Accesstime atime Modifytime mtime Changetime ctime 对于这 3 种时间很容易被大家混淆 这里简要说明下 Accesstime atime 是指取用文件的时间 所谓取用 常见的操作有 使用编辑器查看文件内容 使用 cat 命令显示文件内容 使用

    2026年3月19日
    2
  • 云之遥全攻略 上「建议收藏」

    云之遥全攻略 上「建议收藏」转自dakkifox作者的和讯博客,很详尽很有用的一篇攻略。原贴地址:http://dakkifox.blog.hexun.com/45908818_d.html我是傻瓜分割线所谓傻瓜版,就是你照

    2022年8月2日
    7

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号