前言
最近在做Android蓝牙这部分内容,所以查阅了很多相关资料,在此总结一下。
基本概念
Bluetooth是一种短距离(10米)的无线通信技术标准,蓝牙协议分为4层,即核心协议层、电缆替代协议层、电话控制协议层和采纳的其它协议层。这4种协议中最重要的是核心协议。蓝牙的核心协议包括基带、链路管理、逻辑链路控制和适应协议四部分。其中链路管理(LMP)负责蓝牙组件间连接的建立。逻辑链路控制与适应协议(L2CAP)位于基带协议层上,属于数据链路层,是一个为高层传输和应用层协议屏蔽基带协议的适配协议。
安卓平台提供对蓝牙的通讯栈的支持,允许设别和其他的设备进行无线传输数据。应用程序层通过安卓API来调用蓝牙的相关功能,这些API使程序无线连接到蓝牙设备,并拥有P2P或者多端无线连接的特性。
- 蓝牙的功能:
- 扫描其他蓝牙设备
- 为可配对的蓝牙设备查询蓝牙适配器
- 建立RFCOMM通道(其实就是尼玛的认证)
- 通过服务搜索来链接其他的设备
- 与其他的设备进行数据传输
- 管理多个连接
- 蓝牙建立连接必须要求:
- 打开蓝牙
- 查找附近已配对或可用设备
- 连接设备
- 设备间数据交互
蓝牙API
代码分布
packages/apps/Bluetooth/
- 蓝牙应用,主要是关于蓝牙应用协议的表现代码,包括opp、hfp、hdp、a2dp、pan等等
frameworks/base/core/Java/android/server/
- 4.2以后这个目录虽然还有,但里面代码已经转移到应用层了,就是前面那个目录,所以4.2.2上的蓝牙这里可以忽略。
framework/base/core/java/android/bluetooth
- 这个目录里的代码更像一个桥梁,里面有供java层使用一些类,也有对应的aidl文件联系C、C++部分的代码,还是挺重要的。
kernel\drivers\bluetoothBluetooth
- 具体协议实现。包括hci,hid,rfcomm,sco,SDP等协议
kernel\net\bluetooth Linux kernel
- 对各种接口的Bluetoothdevice的驱动。例如:USB接口,串口等,上面kernel这两个目录有可能看不到的,但一定会有的。
external\bluetooth\bluedroid
- 官方蓝牙协议栈
system\bluetoothBluetooth
- 适配层代码,和framework那个作用类似,是串联framework与协议栈的工具。
关键类
/frameworks/base/core/java/android/bluetooth/
-
BluetoothAdapter
代表本地蓝牙适配器(蓝牙发射器),是所有蓝牙交互的入口。通过它可以搜索其它蓝牙设备,查询已经配对的设备列表,通过已知的MAC地址创建BluetoothDevice,创建BluetoothServerSocket监听来自其它设备的通信。 -
BluetoothDevice
代表了一个远端的蓝牙设备, 使用它请求远端蓝牙设备连接或者获取 远端蓝牙设备的名称、地址、种类和绑定状态。 (其信息是封装在 bluetoothsocket 中) 。 -
BluetoothSocket
代表了一个蓝牙套接字的接口(类似于 tcp 中的套接字) ,他是应用程 序通过输入、输出流与其他蓝牙设备通信的连接点。 -
BluetoothServerSocket
代表打开服务连接来监听可能到来的连接请求 (属于 server 端) , 为了连接两个蓝牙设备必须有一个设备作为服务器打开一个服务套接字。 当远端设备发起连 接连接请求的时候,并且已经连接到了的时候,Blueboothserversocket 类将会返回一个 bluetoothsocket。 -
BluetoothClass
描述了一个设备的特性(profile)或该设备上的蓝牙大致可以提供哪些服务(service),但不可信。比如,设备是一个电话、计算机或手持设备;Blueboothserversocket 设备可以提供audio/telephony服务等。可以用它来进行一些UI上的提示。 -
BluetoothProfile
蓝牙协议 -
BluetoothHeadset
提供手机使用蓝牙耳机的支持。这既包括蓝牙耳机和免提(V1.5)模式。 -
BluetoothA2dp
定义高品质的音频,可以从一个设备传输到另一个蓝牙连接。 “A2DP的”代表高级音频分配模式。 -
BluetoothHealth
代表了医疗设备配置代理控制的蓝牙服务 -
BluetoothHealthCallback
一个抽象类,使用实现BluetoothHealth回调。你必须扩展这个类并实现回调方法接收更新应用程序的注册状态和蓝牙通道状态的变化。 -
BluetoothHealthAppConfiguration
代表一个应用程序的配置,蓝牙医疗第三方应用注册与远程蓝牙医疗设备交流。 -
BluetoothProfile.ServiceListener
当他们已经连接到或从服务断开时通知BluetoothProfile IPX的客户时一个接口(即运行一个特定的配置文件,内部服务)。
\packages\apps\Settings\src\com\android\settings\bluetooth
-
BluetoothEnabler
界面上蓝牙开启、关闭的开关就是它了, -
BluetoothSettings
主界面,用于管理配对和连接设备 -
LocalBluetoothManager
提供了蓝牙API上的简单调用接口,这里只是开始。 -
CachedBluetoothDevice
描述蓝牙设备的类,对BluetoothDevice的再封装 -
BluetoothPairingDialog
那个配对提示的对话框
/packages/apps/Phone/src/com/android/phone/
-
BluetoothPhoneService
在phone的目录肯定和电话相关了,蓝牙接听挂断电话会用到这个
/packages/apps/Bluetooth/src/com/android/bluetooth/
说到这里不能不说4.2蓝牙的目录变了,在4.1及以前的代码中packages层的代码只有opp协议相关应用的代码,也就是文件传输那部分,而4.2的代码应用层的代码则丰富了许多,按具体的蓝牙应用协议来区别,分为以下文件夹(这里一并对蓝牙一些名词作个简单解释)
-
btservice
这个前面AdapterService.java的描述大家应该能猜到一些,关于蓝牙基本操作的目录,一切由此开始。- AdapterService (4.2后才有的代码)蓝牙打开、关闭、扫描、配对都会走到这里,其实更准确的说它替代了4.1之前的BluetoothService.java,原来的工作就由这个类来完成了。
a2dp
(Advanced Audio Distribution Profile)高级音频传输模式,蓝牙立体声,和蓝牙耳机听歌有关那些。avrcp
音频/视频远程控制配置文件,是用来听歌时暂停,上下歌曲选择的。hdp
(Health Device Profile)蓝牙医疗设备模式,可以创建支持蓝牙的医疗设备,使用蓝牙通信的应用,例如心率监视器,血液,温度计和秤。hfp
(Hands-free Profile)让蓝牙设备可以控制电话,如接听、挂断、拒接、语音拨号等,拒接、语音拨号要视蓝牙耳机及电话是否支持。pbap
(Phonebook Access Profile)电话号码簿访问协议hid
(The Human Interface Device)人机交互接口,蓝牙鼠标键盘什么的就是这个了。该协议改编自USB HID Protocol。opp
(Object Push Profile)对象存储规范,最为常见的,文件的传输都是使用此协议。pan
(Personal Area Network)描述了两个或更多个蓝牙设备如何构成一个即时网络,和网络有关还有串行端口功能(SPP),拨号网络功能(DUN)
android 4.2的蓝牙应用层部分代码更丰富了,虽然有些目录还没具体代码,不过说不准哪个版本更新就有了,就像4.0添加了hdp医疗那部分一样。另外原本在framework的JNI代码也被移到packages/apps/bluetooth当中。
主要方法
-
BluetoothAdapter
(蓝牙本地适配器)-
getDefaultAdapter()
得到本地蓝牙适配器 -
setName(String name)
设置蓝牙名称 -
disable()
关闭蓝牙 -
enable()
打开蓝牙 -
isEnabled()
判断蓝牙是否打开 -
getName()
得到本地蓝牙的名称 -
getAddress()
得到本地蓝牙适配器的地址 -
getBondedDevices()
得到已经绑定的蓝牙的设备 -
getRemoteDevice(byte[] address)
得到远程蓝牙设备 -
getRemoteDevice(String address)
得到远程蓝牙设备 -
startDiscovery()
开始搜多附近蓝牙 -
cancelDiscovery()
停止当前搜索蓝牙的 Task -
listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid)
创建 BluetoothServerSocket
-
-
BluetoothDevice
(蓝牙设备)-
createBond()
蓝牙配对 (低版本不支持,>=api19) -
createRfcommSocketToServiceRecord(UUID uuid)
创建 BluetoothSocket -
getBondState()
得到配对的状态 -
getAddress()
得到远程蓝牙适配器的地址 -
getName()
得到远程蓝牙的名称
-
-
BluetoothServerSocket
(数据传输服务端)
这个类一共只有三个方法两个重载的。两个重载的区别在于后面的方法指定了过时时间,需要注意的是,执行这两个方法的时候,直到接收到了客户端的请求(或是过期之后),都会阻塞线程,应该放在新线程里运行!-
close()
关闭 -
connect()
连接 -
isConnected()
判断当前的连接状态 -
accept()
接收请求 -
accept(int timeout)
接收请求
-
-
BluetoothSocket
(数据传输客户端)-
close()
关闭 -
connect()
连接 -
getInptuStream()
获取输入流 -
getOutputStream()
获取输出流 -
getRemoteDevice()
获取远程设备,这里指的是获取bluetoothSocket指定连接的那个远程蓝牙设备
-
蓝牙操作
打开和关闭蓝牙
开启蓝牙有两种方法:
一、直接调用系统对话框启动蓝牙:
在AndroidManifest.xml
文件中添加需要的权限,高版本也不需要动态授权:
然后,在代码中执行:
startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 1);
如果不想让用户看到对话框,那么我们还可以选择第二种方法,进行静默开启蓝牙。
二、静默开启,不会有方法一的对话框:
照样在AndroidManifest.xml
文件中添加需要的权限:
由于蓝牙所需要的权限包含Dangerous Permissions,所以我们需要在Java代码中进行动态授权处理:
private static final int REQUEST_BLUETOOTH_PERMISSION=10;
private void requestBluetoothPermission(){
//判断系统版本
if (Build.VERSION.SDK_INT >= 23) {
//检测当前app是否拥有某个权限
int checkCallPhonePermission = ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION);
//判断这个权限是否已经授权过
if(checkCallPhonePermission != PackageManager.PERMISSION_GRANTED){
//判断是否需要 向用户解释,为什么要申请该权限
if(ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_COARSE_LOCATION))
Toast.makeText(this,"Need bluetooth permission.",
Toast.LENGTH_SHORT).show();
ActivityCompat.requestPermissions(this ,new String[]
{Manifest.permission.ACCESS_COARSE_LOCATION},REQUEST_BLUETOOTH_PERMISSION);
return;
}else{
}
} else {
}
}
接下来我们就可以静默开启蓝牙了:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter.enable(); //开启
关闭蓝牙
if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
mBluetoothAdapter.disable();
}
搜索蓝牙设备
搜索分为主动搜索和被动搜索:
一、被动搜索
if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)
{
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
// 设置被发现时间,最大值是3600秒,0表示设备总是可以被发现的(小于0或者大于3600则会被自动设置为120秒)
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120);
activity.startActivity(discoverableIntent);
}
二、主动搜索
创建BluetoothAdapter
对象
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
我们先获取并显示一下已经配对的蓝牙设备列表
/*
* 已配对设备列表
*/
private ListView mBoundDevicesLv;
/**
* 显示已配对的设备列表
*/
private void showBoundDevices() {
List
开始搜索
if (mBluetoothAdapter == null) {
LogUtil.e(TAG, "设备不支持蓝牙");
}
// 打开蓝牙
if (!mBluetoothAdapter.isEnabled()) {
BluetoothAdapter.enable();
mBluetoothAdapter.cancelDiscovery();
}
// 寻找蓝牙设备,android会将查找到的设备以广播形式发出去
while (!mBluetoothAdapter.startDiscovery()) {
LogUtil.e(TAG, "尝试失败");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
定义搜索结果的广播接收器
// 设置广播信息过滤
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);//每搜索到一个设备就会发送一个该广播
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//当全部搜索完后发送该广播
filter.setPriority(Integer.MAX_VALUE);//设置优先级
registerReceiver(receiver, filter);// 注册蓝牙搜索广播接收者,接收并处理搜索结果
搜索蓝牙设备的广播接收器如下:
/**
* 搜索出的设备集合
*/
private List
蓝牙配对
当我们搜索到了蓝牙的之后,就需要配对,因为只有在配对之后才能连接。
在上面的搜索到的设备列表的点击事件中,进行配对。
BluetoothDevice device = (BluetoothDevice) adapter.getItem(i);
if (device.getBondState() == BluetoothDevice.BOND_BONDED) {//是否已配对
connect(device);
} else {
try {
Method boned=device.getClass().getMethod("createBond");
boolean isok= (boolean) boned.invoke(device);
if(isOk) {
connect(device);
}
} catch (Exception e) {
e.printStackTrace();
}
}
这里需要说明的是,这个配对Android在API19
之后对外提供了createBond()
这个方法。但是在API19
以前并没有这个方法,所以用反射兼容性比较好。
蓝牙的UUID
在进行蓝牙连接之前,先介绍一下一个关键的东西:两个蓝牙设备进行连接时需要使用同一个UUID。但很多读者可能发现,有很多型号的手机(可能是非Android系统的手机)之间使用了不同的程序也可以使用蓝牙进行通讯。从表面上看,它们之间几乎不可能使用同一个UUID。
UUID的格式如下:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
UUID的格式被分成5段,其中中间3段的字符数相同,都是4,第1段是8个字符,最后一段是12个字符。所以UUID实际上是一个8-4-4-4-12的字符串。
实际上,UUID和TCP的端口一样,也有一些默认的值。例如,将蓝牙模拟成串口的服务就使用了一个标准的UUID:
00001101-0000-1000-8000-00805F9B34FB
除此之外,还有很多标准的UUID,如下面就是两个标准的UUID:
信息同步服务:00001104-0000-1000-8000-00805F9B34FB
文件传输服务:00001106-0000-1000-8000-00805F9B34FB
蓝牙设备间的数据传输
蓝牙传输数据与Socket类似。在网络中使用Socket和ServerSocket控制客户端和服务端的数据读写。而蓝牙通讯也由客户端和服务端Socket来完成。蓝牙客户端Socket是BluetoothSocket
,蓝牙服务端Socket是BluetoothServerSocket
。这两个类都在android.bluetooth包中。
无论是BluetoothSocket
,还是BluetoothServerSocket
,都需要一个UUID(全局唯一标识符,Universally Unique Identifier),UUID相当于Socket的端口,而蓝牙地址相当于Socket的IP。
下面,我们开始进行模拟一个蓝牙数据的传输:
一、首先来看客户端:
定义全局常量变量:
private ListView mDevicesLv;
private BluetoothAdapter mBluetoothAdapter;
private List
接下来我们设置设备列表的点击事件
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
Map s = devices.get(i);
String address = s.get("address");//把地址解析出来
//主动连接蓝牙服务端
try {
// 如果当前正在搜索,则取消搜索。
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
try {
if (device == null) {
//获得远程设备
device = mBluetoothAdapter.getRemoteDevice(address);
}
if (clientSocket == null) {
//创建客户端蓝牙Socket
clientSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
//开始连接蓝牙,如果没有配对则弹出对话框提示我们进行配对
clientSocket.connect();
//获得输出流(客户端指向服务端输出文本)
os = clientSocket.getOutputStream();
}
} catch (Exception e) {
}
if (os != null) {
//往服务端写信息
os.write("蓝牙信息来了".getBytes("utf-8"));
}
} catch (Exception e) {
}
}
二、接下来看服务端:
服务端使用的是另一部手机,接受上面手机通过蓝牙发送过来的信息并显示。
定义全局常量变量:
private BluetoothAdapter mBluetoothAdapter;
private AcceptThread acceptThread;
// 和客户端相同的UUID
private final UUID MY_UUID = UUID.fromString("abcd1234-ab12-ab12-ab12-abcdef123456");
private final String NAME = "Bluetooth_Socket";
private BluetoothServerSocket serverSocket;
private BluetoothSocket socket;
private InputStream is;//输入流
定义服务端线程类:
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
Toast.makeText(getApplicationContext(), String.valueOf(msg.obj),
Toast.LENGTH_LONG).show();
super.handleMessage(msg);
}
};
// 服务端监听客户端的线程类
private class AcceptThread extends Thread {
public AcceptThread() {
try {
serverSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (Exception e) {
}
}
public void run() {
try {
socket = serverSocket.accept();
is = socket.getInputStream();
while(true) {
byte[] buffer =new byte[1024];
int count = is.read(buffer);
Message msg = new Message();
msg.obj = new String(buffer, 0, count, "utf-8");
handler.sendMessage(msg);
}
}
catch (Exception e) {
}
}
}
在onCreate方法中初始化线程类并开启:
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
acceptThread = new AcceptThread();
acceptThread.start();
注意,使用socket.getInputStream接收到的数据是字节流,这样的数据是没法分析的,所以很多情况需要一个byte转十六进制String的函数:
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
蓝牙协议
蓝牙协议简介
从Android 3.0开始,Bluetooth API就包含了对Bluetooth profiles的支持。
Bluetooth profile是基于蓝牙的设备之间通信的无线接口规范。
你在你的类里可以完成BluetoothProfile接口来支持某一Bluetooth profile。
Android Bluetooth API完成了下面的Bluetooth profile:
-
Headset profile
提供了移动电话上的Bluetooth耳机支持。Android提供了BluetoothHeadset类,它是一个协议,用来通过IPC(interprocess communication)控制Bluetooth Headset Service。BluetoothHeadset既包含Bluetooth Headset profile
也包含Hands-Free profile
,还包括对AT命令
的支持。 -
HFP (Hands-free Profile)
,免提模式,让蓝牙设备可以控制电话,如接听、挂断、拒接、语音拨号等,拒接、语音拨号要视蓝牙耳机及电话是否支持。 -
HDP(Health Device Profile.)
,蓝牙医疗设备模式,可以创建支持蓝牙的医疗设备,使用蓝牙通信的应用,例如心率监视器,血液,温度计和秤。 -
AVRCP
,音频/视频远程控制配置文件,是用来听歌时暂停,上下歌曲选择的。 -
A2DP(Advanced Audio Distribution Profile)
,高级音频传输模式。Android提供了BluetoothA2dp类,这是一个通过IPC来控制Bluetooth A2DP的协议。 -
HID (The Human Interface Device)
,人机交互接口,蓝牙鼠标键盘什么的就是这个了。该协议改编自USB HID Protocol。 -
OPP (Object Push Profile)
,对象存储规范,最为常见的,文件的传输都是使用此协议。 -
PAN (Personal Area Network)
,描述了两个或更多个蓝牙设备如何构成一个即时网络,和网络有关还有串行端口功能(SPP),拨号网络功能(DUN)。 -
PBAP (Phonebook Access Profile)
,电话号码簿访问协议。
蓝牙协议的使用
下面是使用profile的基本步骤:
- 获取默认的Bluetooth适配器。
- 使用getProfileProxy()来建立一个与profile相关的profile协议对象的连接。在下面的例子中,profile协议对象是BluetoothHeadset的一个实例。
- 设置BluetoothProfile.ServiceListener。该listener通知BluetoothProfile IPC客户端,当客户端连接或断连服务器的时候
- 在BluetoothProfile.ServiceListener的onServiceConnected()内,得到一个profile协议对象的句柄。
- 一旦拥有了profile协议对象,就可以用它来监控连接的状态,完成于该profile相关的其他操作。
例如,下面的代码片段显示如何连接到一个BluetoothHeadset协议对象,用来控制Headset profile:
BluetoothHeadset mBluetoothHeadset;
// 获取默认的Bluetooth适配器
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 连接Headset profile
mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener(){
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = (BluetoothHeadset) proxy;
}
}
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = null;
}
}
};
// ... 使用 mBluetoothHeadset
// 使用之后,关闭Proxy
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset)
以上,就先分析到这儿吧。