Android蓝牙系统
蓝牙是一种支持设备短距离通信(一般10m内)的无线电技术,可以在众多设备之间进行无线信息交换。
Android系统中的蓝牙模块
Android包含了对蓝牙网络协议栈的支持,使蓝牙设备能够无线连接其他蓝牙设备以便交换数据。
通过使用蓝牙API,一个Android应用程序能够实现如下功能:
- 扫描其他蓝牙设备
- 查询本地蓝牙适配器用于配对蓝牙设备
- 建立RFCOMM信道
- 通过服务发现连接其他设备
- 数据通信
- 管理多个连接
Android平台中蓝牙系统从上到下主要包括Java框架中的BlueTooth类、Android适配库、BlueZ库、驱动程序和协议。
蓝牙模块中的源码
- 初始化蓝牙芯片
通过BlueZ工具hciattach进行的,命令格式如下: hciattach [-n] [-p] [-b] [-t timeout] [-s initial_speed] <tty> <type | id> [speed] [flow|noflow] [bdaddr] type决定了要初始化的设备的型号,可以使用hciattach -1来列出所支持的设备型号。 Hciattach命令内部的工作步骤是:首先打开制定的tty设备,再做一些通用的设置,如flow等, 之后设置波特率为initial_speed,然后根据type调用各自的初始化代码,最后将波特率重新设置为speed。
- 蓝牙服务
一般不需要自己定义,只需要使用初始化脚本文件init.rc中的默认内容即可。
- 管理蓝牙电源
可以调用rfkill接口来控制电源管理,如果已经实现了rfkill接口,则无需再进行配置。
和蓝牙相关的类
BluetoothSocket类
- BluetoothSocket类基础
Android的蓝牙系统和Socket套接字密切相关,蓝牙端的监听接口和TCP端口类似,都是使用 Socket和ServerSocket类。在服务器端使用BluetoothServerSocket类来创建一个监听服务端口。当一个连接被BluetoothServerSocket所接受,它会返回一个新的BluetoothSocket来管理该连接。在客户端,使用一个单独的BluetoothSocket类去初始化一个外接连接和管理该连接。
最通常使用的蓝牙端口是RFCOMM,它是一个面向连接,通过蓝牙模块进行数据流传输方式,也被称为串行端口规范(SPP)。
创建一个BluetoothSocket去连接到一个已知设备,使用方法BluetoothDevice.createRfcommSocketToServiceRecord()。然后调用connect()方法去尝试一个面向远程设备的连接。这个调用将被阻塞指导一个连接已经建立或者该链接失效。
当端口连接成功后,通过getInputStream()和getOutputStream()来打开I/O流,从而获得各自的InputStream和OutputStream对象。
BluetoothSocket类的线程是安全的,因为close()方法会马上放弃外界操作并关闭服务器端口。
- BluetoothSocket类的公共方法
- public void close()
- public void connect()
- public InputStream getInputStream()
- public OutputStream getOutputStream()
- public BluetoothDevice getRemoteDevice()
BluetoothServerSocket类
- BluetoothServerSocket类的公共方法
- public BluetoothSocket accept(int timeout)
- public BluetoothSocket accept()
- public void close()
BluetoothAdapter类
- BluetoothAdapter类基础
代表本地的蓝牙适配器设备,通过此类可以让用户能执行基本的蓝牙任务,如初始化设备的搜索、查询可匹配的设备集、使用一个已知的MAC地址来初始化一个BluetoothDevice类、创建一个BluetoothServerSocket类以监听其他设备对本机的连接请求等。
调用静态方法getDefaultAdapter()获得,这是所有蓝牙动作使用的第一步。当拥有本地适配器以后,用户可以获得一系列的BluetoothDevice对象,这些对象代表所有拥有getBondedDevice()方法的已经匹配的设备;用startDiscovery()方法来开始设备的搜寻;或者创建一个BluetoothServerSocket类,通过listenUsingRfcommWithServiceRecord(String,UUID)方法来监听新来的连接请求。
大部分方法需要BLUETOOTH权限,一些方法同时需要BLUETOOTH_ADMIN权限。
- BluetoothAdapter类的常量
- String ACTION_DISCOVERY_FINISHED
广播事件:本地蓝牙适配器已经完成设备的搜索过程,需要BLUETOOTH权限接收。 常量值:android.bluetooth.adapter.action.DISCOVERY_FINISHED
- String ACTION_DISCOVERY_STARTED
广播事件:本地蓝牙适配器已经开始对远程设备的搜寻过程。用户可以通过cancelDiscovery()类来取消正在执行的查找过程,需要BLUETOOTH权限接收。 常量值:android.bluetooth.adapter.action.DISCOVERY_STARTED
- String ACTION_LOCAL_NAME_CHANGED
广播事件:本地蓝牙适配器已经更改了它的蓝牙名称,需要BLUETOOTH权限接收。 常量值:android.bluetooth.adapter.action.LOCAL_NAME_CHANGED
- String ACTION_REQUEST_DISCOVERABLE
Activity活动:显示一个请求被搜寻模式的系统活动。Android运用onActivityResult(int,int,Intent)回收方法来传递该活动结果的通知。需要BLUETOOTH权限。 常量值:android.bluetooth.adapter.action.REQUEST_DISCOVERYABLE
- String ACTION_REQUEST_ENABLE
Activity活动:显示一个允许用户打开蓝牙模块的系统活动。Android运用onActivityResult(int,int,Intent)回收方法来传递该活动结果的通知。需要BLUETOOTH权限。 常量值:android.bluetooth.adapte.action.REQUEST_ENABLE
- String ACTION_SCAN_MODE_CHANGED
广播活动:指明蓝牙扫描模块或者本地适配器已经发生变化,需要BLUETOOTH权限。 常量值:android.bluetooth.adapter.action.SCAN_MODE_CHANGED
- String ACTION_STATE_CHANGED
广播活动:本来的蓝牙适配器的状态已经改变,如蓝牙模块已经被打开或者关闭,需要BLUETOOTH权限接收。 常量值:android.bluetooth.adapter.action.STATE_CHANGED
- int ERROR
功能:标记该类的错误值,确保和该类中的任意其他整数常量不相等。它为需要一个标记错误值的函数提供了便利,例如: Intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,BluetoothAdapter.ERROR);
- String EXTRA_DISCOVERABLE_DURATION
功能:试图在ACTION_REQUEST_DISCOVERABLE常量中作为一个可选的整型附加域,来为短时间内的设备发现请求一个特定的持续时间,默认值为120秒,超过300秒的请求将被限制。 常量值:android.bluetooth.adapter.extra.DISCOVERABLE_DURATION
- String EXTRA_LOCAL_NAME
功能:试图在ACTION_LOCAL_NAME_CHANGED常量中作为一个字符串附加域,来请求本地蓝牙的名称。 常量值:android.bluetooth.adapter.extra.LOCAL_NAME
- String EXTRA_PREVIOUS_SCAN_MODE
功能:试图在ACTION_SCAN_MODE_CHANGED常量中作为一个整型附加域,来请求以前的扫描模式,可能值如下: SCAN_MODE_NONE SCAN_MODE_CONNECTABLE SCAN_MODE_CONNECTABLE_DISCOVERABLE 常量值:android.bluetooth.adapter.extra.PREVIOUS_SCAN_MODE
- String EXTRA_PREVIOUS_STATE
功能:试图在ACTION_STATE_CHANGED常量中作为一个整型附加域,来请求以前的供电状态。可能值如下: STATE_OFF STATE_TURNING_ON STATE_ON STATE_TURNING_OFF 常量:android.bluetooth.adapter.extra.PREVIOUS_STATE
- String EXTRA_SCAN_MODE
功能:试图在ACTION_SCAN_MODE_CHANGED常量作为一个整型附加域,来请求当前的扫描模式,可能值如下: SCAN_MODE_NONE SCAN_MODE_CONNECTABLE SCAN_MODE_CONNECTABLE_DISCOVERABLE 常量值:android.bluetooth.adapter.extra.SCAN_MODE
- String EXTRA_STATE
功能:试图在ACTION_STATE_CHANGED常量中作为一个整型附加域,来请求当前供电状态,可能值如下: STATE_OFF STATE_TURNING_ON STATE_ON STATE_TURNING_OFF 常量值:android.bluetooth.adapter.extra.STATE
- int SCAN_MODE_CONNECTABLE
功能:指明在本地蓝牙适配器中,查询扫描功能失效,但页面扫描功能有效。因此,该设备不能被远程蓝牙设备发现,但如果以前曾经发现过该设备,则远程设备可以对其进行连接。 常量值:21(0x00000015)
- int SCAN_MODE_CONNECTABLE_DISCOVERABLE
功能:指明在本地蓝牙适配器中, 查询扫描功能都有效,因此,该设备既可以被远程蓝牙设备发现,也可以被其连接。 常量值:23(0x00000017)
- int SCAN_MODE_NONE
功能:指明在本地蓝牙适配器中,查询扫描功能和页面扫描功能都失效,因此,该设备既不可以被远程蓝牙设备发现,也不可以被连接。 常量值:20(0x00000014)
- int STATE_OFF
功能:指明本地蓝牙适配器模式已经关闭 常量值:10(0x0000000a)
- int STATE_ON
功能:指明本地蓝牙适配器模块已经打开,并且准备被使用
- int STATE_TURNING_OFF
功能:指明本地蓝牙适配器模块正在关闭。本地客户端可以立刻尝试友好地断开任意外部连接。 常量值:13(0x0000000d)
- int STATE_TURNING_ON
功能:指明本地蓝牙适配器模块正在打开,然而本地客户在尝试使用这个适配器之前需要为STATE_ON状态而等待 常量值:11(0x000000b)
- String ACTION_DISCOVERY_FINISHED
- BluetoothAdapter类的公共方法
- public boolean cancelDiscovery()
- public static boolean checkBluetoothAddress(String address)
- public boolean disable()
- public boolean enable()
- public String getAddress()
- public Set getBondedDevices()
- public static synchronized BluetoothAdapter getDefaultAdapter()
- public String getName()
- public BluetoothDevice getRemoteDevice(String address)
- public int getScanMode()
- public int getState()
- public boolean isDiscovering()
- public boolean isEnabled()
- public BluetoothServerSocket ListenUsingRfcommWithServiceRecord(String name,UUID uuid)
- public boolean setName(String name)
- public boolean startDiscovery()
BluetoothClass.Service类
用于定义所有的服务类常量,任意BluetoothClass由0或多个服务类编码组成。在类BluetoothClass.Service中包含如下常量:
- int AUDIO
- int CAPTURE
- int INFORMATION
- int LIMITED_DISCOVERABILITY
- int NETWORKING
- int OBJECT_TRANSFER
- int POSITIONING
- int RENDER
- int TELEPHONY
BluetoothClass.Device类
用于定义所有的设备类的常量,每个BluetoothClass有一个带有主要和较小部分的设备类进行编码,里面的常量代表主要和较小的设备类部分(完整的设备类)的组合。
在Android平台开发蓝牙应用程序
- 1.设置权限
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
- 2.启动蓝牙
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if(mBluetoothAdapter == null){ //表示手机不支持蓝牙 return; } if(!mBluetoothAdapter.isEnabled()){ //蓝牙未开启,则开启蓝牙 Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableIntent,REQUEST_ENABLE_BT); } //... public void onActivityResult(int requestCode,int resultCode,Intent data){ if(requestCode == REQUEST_ENABLE_BT){ if(resultCode == RESULT_OK){ //蓝牙已经开启 } } }
- 3.发现蓝牙设备
//使本机蓝牙在300秒内可被搜索 private void ensureDiscoverable(){ if(mBluethAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE){ Intent discoverableIntent = new Intent(BluetoothAdapter.ATION_REQUEST_DISCOVERABLE); intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,300); startActivity(discoverableIntent); } } //查找已经配对的蓝牙设备,即以前已经配对过的设备 Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); if(pairedDevices.size() > 0){ findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE); for(BluetoothDevice device : pairedDevices){ //device.getName() + " " + device.getAddress(); } }else{ mPairedDevicesArrayAdapter.add("没有找到已配对的设备"); } //注册,当一个设备被发现时调用onReceive IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); this.registerReceiver(mReceiver,filter); //当搜索结束后调用onReceive filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); this.registerReceiver(mReceiver,filter); //... private BroadcastReceiver mReceiver = new BroadcastReceiver(){ @Override public void onReceiver(Context context,Intent intent){ String action = intent.getAction(); if(BluetoothDevice.ACTION_FOUND.equal(action)){ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); //已经配对的则跳过 if(device.getBondState() != BluetoothDevice.BOND_BONDED){ mNewDevicesArrayAdapter.add(device.getName()+"\n"+device.getAddress()); } }else if(BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equal(action)){ //搜索结束 if(mNewDevicesArrayAdapter.getCount() == 0){ mNewDevicesArrayAdapter.add("没有搜索到设备"); } } } }
- 4.建立连接
//UUID可以看做一个端口号 private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66"); //像一个服务器一样时刻监听是否有连接建立 private class AcceptThread extend Thread{ private BluetoothServerSocket serverSocket; public AcceptThread(boolean secure){ BluetoothServerSocket temp = null; try{ temp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME_INSECURE,MY_UUID); }catch(IOException e){ Log.e("TAG","listen() failed "+e); } serverSocket = temp; } public void run(){ BluetoothSocket socket = null; while(true){ try{ socket = serverSocket.accept(); }catch(IOException e){ Log.e("TAG","accept() failed "+e); break; } } if(socket != null){ //此时可以新建一个数据交换线程,把此socket传进去 } } //取消监听 public void cancel(){ try{ serverSocket.close(); }catch(){ Log.e("TAG","Socket Type "+socketType+" close() of server failed "+e); } } }
- 5.交换数据
//另一个设备去连接本机,相当于客户端 private class ConnectThread extends Thread{ private BluetoothSocket socket; private BluetoothDevice device; public ConnectThread(BluetoothDevice device,boolean secure){ this.device = device; BluetoothSocket tmp = null; try{ tmp = device.createRfcommSocketToServiceRecord(MY_UUID_SECURE); }catch(IOException e){ Log.e("TAG","create() failed "+e); } } public void run(){ mBluetoothAdapter.cancelDiscovery(); //取消设备查找 try{ socket.connect(); }catch(IOException e){ try{ socket.close(); }catch(IOException e1){ Log.e("TAG","unable to close "+"socket during connection failure "+e1); } connectionFailed(); //连接失败 return; } //此时可以新建一个数据交换线程,把此socket传进去 } public void cancel(){ try{ socket.close(); }catch(){ Log.e("TAG","close() of connect socket failed "+e); } } }
- 6.建立数据通信线程
//建立连接后,进行数据通信的线程 private class ConnectedThread extend Thread{ private BluetoothSocket socket; private InputStream inStream; private OutputStream outStream; public ConnectedThread(BluetoothSocket socket){ this.socket = socket; try{ //获取输入/输出流 inStream = socket.getInpuStream(); outStream = socket.getOutputStream(); }catch(IOException e){ Log.e("TAG","temp sockets not created "+e); } } public void run(){ byte[] buff = new byte[1024]; int len = 0; //读取数据需不断监听,写不需要 while(true){ try{ len = inStream.read(buff); //把读取到的数据发送给UI进行显示 Message msg = handler.obtainMessage(BluetoothChat.MESSAGE_READ,len,-1,buff); }catch(IOException e){ Log.e("TAG","disconnected "+e); connectionLost(); //失去连接 start(); //重新启动服务器 break; } } } public void write(byte[] buffer){ try{ outStream.write(buffer); handler.obtainMessage(BluetoothChat.MESSAGE_WRITE,-1,-1,buffer); msg.sendToTarget(); }catch(IOException e){ Log.e("TAG","Exception during write "+e); } } public void cancel(){ try{ socket.close(); }catch(IOException e){ Log.e("TAG","close() of connect socket failed "+e); } } }