0

android 蓝牙

android平台包含了蓝牙网络协议栈的支持,允许android设备与其他蓝牙设备相互传输数据。应用层框架提供了API函数来访问蓝牙模块。使用这些API可以让应用程序连接其他蓝牙设备,实现点对点或多点无线传输。
运用蓝牙API,可以实现以下功能:
搜索其他蓝牙设备
查询本地蓝牙适配器中已经配对好的设备
建立RFCOMM协议通道
通过服务端搜索连接到其他设备
与其他设备互相传输数据
管理多个连接
快速阅读
Android蓝牙API可以让应用程序与其他设备传输无线数据。
关键类
BluetoothAdapter
BluetoothDevice
BluetoothSocket
BluetoothServerSocket
相关用例
蓝牙对讲 http://developer.android.com/resources/samples/BluetoothChat/index.html
蓝牙医疗设备(Health Device Profile),比如心率监视器,血压计,温度计等。http://developer.android.com/resources/samples/BluetoothHDP/index.html
目录 [隐藏]
1 基本原理
2 蓝牙权限
3 配置蓝牙
4 获取蓝牙设备
4.1 查询已配对设备
4.2 搜索设备
4.3 开启蓝牙可检测性
5 设备连接
5.1 作为服务端连接
5.2 作为客户端连接
6 连接管理
7 在蓝牙规范协议下工作
7.1 Vendor-specific AT commands
7.2 Health Device Profile
基本原理

本文档描述了如何使用蓝牙API来完成蓝牙通讯的四项必要任务:配置蓝牙、搜索附件未配对或可用的蓝牙设备、连接设备、设备间传输数据。
所有蓝牙API都包含在android.bluetooth包中。以下是建立蓝牙连接需要用到的类和接口的概要:
BluetoothAdapter (蓝牙适配器)
表示本地蓝牙适配器(蓝牙收发器). BluetoothAdapter是所有蓝牙活动的起始类. 可用于搜索其他蓝牙设备, 查询已配对设备的列表, 使用MAC地址实例化一个BluetoothDevice对象, 创建BluetoothServerSocket侦听其他设备的连接.
BluetoothDevice (蓝牙设备)
表示远程蓝牙设备。可以通过一个BluetoothSocket向它描述的远程设备发起连接,或者该设备的名称、地址、类、连接状态等信息。
BluetoothSocket (蓝牙套接字)
表示一个蓝牙套接字(与TCP Socket类似). 它是设备间的连接点,允许应用程序通过InputStream和OutputStream与其他设备进行数据传输。
BluetoothServerSocket (蓝牙服务端套接字)
表示一个开放的蓝牙服务器, 用于侦听其他设备发过来的连接请求(与TCP ServerSocket类似). 要将两台设备连接起来, 其中一台必须使用这个类开启一个server socket. 当远程蓝牙设备发起对server的连接请求, 如果连接被接受,BluetoothServerSocket将返回一个连接成功的BluetoothSocket对象.
BluetoothClass (蓝牙类型)
描述一个蓝牙设备的规格参数和功能。这是一个只读的属性集,定义了该设备的主要和次要设备种类和服务。它不能完全描述该设备的所有特性和服务,常用于判断设备的类型。
BluetoothProfile (蓝牙规范协议)
表示Bluetooth profile的接口. Bluetooth profile是设备间基于蓝牙通讯的接口规范协议。比如Hands-Free(非手持设备) profile。更多关于profiles的说明, 请查看Working with Profiles(在蓝牙规范协议下工作)。
BluetoothHeadset (蓝牙耳机)
提供手机使用蓝牙耳机的支持。同时包含了蓝牙耳机和Hands-Free(v1.5)的profiles.
BluetoothA2dp (蓝牙A2dp)
定义高质量音频流如何通过蓝牙连接传输到其他设备。”A2DP”是”Advanced Audio Distribution Profile”的缩写,表示高级音频分发规范协议。
BluetoothHealth (蓝牙医疗设备)
表示为医疗设备提供蓝牙服务的代理类。
BluetoothHealthCallback
这是一个抽象类,用于实现BluetoothHealth的callbacks方法。需要继承此类并实现callback方法才能接收应用程序状态和蓝牙频道状态的变化。
BluetoothHealthAppConfiguration
表示蓝牙医疗第三方应用与远程蓝牙医疗设备连接的配置参数。
BluetoothProfile.ServiceListener (蓝牙规范协议服务侦听)
一个接口类,当服务连接或断开的时候通知BluetoothProfile IPC 客户端。(这是内部服务运行的一个特殊模式)。
蓝牙权限

应用程序要使用手机的蓝牙功能,必须声明以下两种权限中的一种:BLUETOOTH 或 BLUETOOTH_ADMIN。
BLUETOOTH权限用于进行蓝牙通讯,比如请求连接、接受连接和传输数据。
BLUETOOTH_ADMIN权限用于初始化设备搜索和进行蓝牙设置。大部分应用只是需要它来搜索附近的蓝牙设备,BLUETOOTH_ADMIN权限赋予的其他能力不需要用到,除非该应用程序是需要修改本机蓝牙设置的电源管理类应用。注意:如果您使用了BLUETOOTH_ADMIN ,必须同时使用BLUETOOTH。
在你的程序中的manifest文件声明蓝牙权限,例如:
<manifest … >
<uses-permission android:name=”android.permission.BLUETOOTH” />

</manifest>
查看 用户权限 这一章节,获得更多关于声明应用程序权限的内容。
配置蓝牙

在使用蓝牙之前,首先要确认你的设备带有蓝牙模块,如果有,还要确认蓝牙功能是开启的。
如果设备不支持蓝牙,应用程序需要禁用所有蓝牙相关的功能。如果设备支持蓝牙,但蓝牙模块是关闭的,可以直接在程序中向用户请求开启蓝牙,不需要离开当前应用。这个过程需要使用BluetoothAdapter,分两步完成。
1.获取BluetoothAdapter对象
所有蓝牙活动都需要有一个BluetoothAdapter对象,使用静态方法getDefaultAdapter()可以获取一个BluetoothAdapter对象,表示本设备的蓝牙适配器(蓝牙收发器)。如果整个系统中存在这么一个蓝牙适配器,应用程序就可以通过BluetoothAdapter对象与其相互交流。如果getDefaultAdapter()方法返回null,说明本设备不支持蓝牙,故事结束了。例如:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// Device does not support Bluetooth
}
2.开启蓝牙
接下来,你需要确认蓝牙模块是否开启。调用isEnabled()检查蓝牙模块是否开启。如果该方法返回false,说明蓝牙模块关闭。需要用ACTION_REQUEST_ENABLE action Intent调用startActivityForResult()请求开启蓝牙。这将通过系统设置发出一个开启蓝牙的请求(无需停止当前应用程序)。例如:
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
这时会弹出一个对话框,询问用户是否开启蓝牙。如果用户选择“是”,系统将开启蓝牙,并且在完成(或失败)之后,返回到你的应用程序。
传递给startActivityForResult()的REQUEST_ENABLE_BT常量是一个局部定义的整形(必然大于0),系统会在你实现的onActivityResult()方法中,通过requestCode参数返回这一数值。
如果开启蓝牙成功,系统会调用你实现的onActivityResult()方法,并得到RESULT_OK的结果码。如果开启失败,或者用户选择“否”,结果码则是RESULT_CANCELED。

最后是一个可选项,你的程序可以关于ACTION_STATE_CHANGED intent的广播。每当系统蓝牙状态发生变化,系统就会发出该广播。广播中包含了额外的字段EXTRA_STATE 和EXTRA_PREVIOUS_STATE,分别表示新的和旧的蓝牙状态。这些字段可能的值是STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFF, 和STATE_OFF。侦听广播对程序在运行中检测到蓝牙状态变化非常有用。
提示:开启蓝牙可检测性会自动开启蓝牙模块,如果你在完成蓝牙活动之前需要一直开启蓝牙可检测性,则可以跳过上面的第二步,看后面的开启蓝牙可检测性。
获取蓝牙设备

使用BluetoothAdapter,你可以通过搜寻设备或者查询已配对设备列表来获取其他蓝牙设备。

搜寻设备是一个搜索附近开启蓝牙的设备并获取设备相关信息的检测过程。 附近的蓝牙设备,必须是开启了蓝牙可检测性,才会被你的设备所检测到。 如果附近一个设备开启了可检测性,它将发送一些信息来回应搜索的请求,比如设备名称、类型以及它的MAC地址。 你的设备可以通过这些信息,初始化一个和被发现设备的连接。

当与一个从未连接过的设备进行连接的时候,一个蓝牙配对请求会自动呈现给用户。 配对完成后,该设备的基本信息(比如设备名称、类型和MAC地址)就被保存下来并可以使用相关的API函数读取。 使用这个已知的MAC地址,连接就可以随时进行而不再需要进行搜索(前提是该设备在蓝牙范围内)。

已配对和已连接是不一样的概念,已配对表示两台设备间已经认识了彼此的存在,共享一个用于认证身份的link-key,并能够互相建立加密连接。 已连接表示两台设备当前正在共享一个RFCOMM信道,并能够互相传输数据。 目前Android Bluetooth API要求设备在建立RFCOMM连接之前必须先进行配对。 (当你用蓝牙API初始化一个加密连接时,配对会自动进行。)

下面的小节讲解如何获得已配对的设备和使用设备搜寻发现新设备。
注意:安卓蓝牙设备默认是不开启可检测性的。 用户可以通过系统设置将蓝牙可检测性临时打开一段时间, 应用程序也可以向用户请求打开可检测性,而不需要离开当前程序。 后面会说明如何开启可检测性。
查询已配对设备
在进行设备搜寻之前,最好先查询一下已配对设备的列表,看看想要连接的设备是否已经配对过了。 调用getBondedDevices()即可。这将返回一个BluetoothDevice对象的set,表示已配对设备的集合。 例如,你可以查询所有已配对设备并显示每个设备的名称,通过使用ArrayAdapter:
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
// Loop through paired devices
for (BluetoothDevice device : pairedDevices) {
// Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName() + “\n” + device.getAddress());
}
}
BluetoothDevice对象初始化一个连接所需要的唯一信息就是MAC地址。 在这个示例中,MAC地址作为ArrayAdapter的一部分显示出来。后续可用于初始化连接。 更多关于创建连接的信息请查看设备连接。
搜索设备
调用startDiscovery()即可开始设备搜寻。搜寻过程是异步的,该方法会立刻返回一个boolean值表示搜寻是否成功开始。 搜寻过程通常是一个十二秒的查询扫描,然后列出一页扫描到的设备的名称。

应用程序必须注册一个关于ACTION_FOUND Intent的BroadcastReceiver,才能接收每个搜寻到的设备的信息。每搜寻到一个设备,系统都会广播ACTION_FOUND Intent。 Intent里面包含额外的字段EXTRA_DEVICE和EXTRA_CLASS,分部包含一个BluetoothDevice和一个BluetoothClass。 以下代码说明如何注册接收和处理设备被搜寻到的信息:
// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName() + “\n” + device.getAddress());
}
}
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don’t forget to unregister during onDestroy
警告: 进行设备搜寻对于蓝牙适配器来说是一个繁重的过程并会消耗大量资源。 当你搜寻到一个可连接设备时,请确保在尝试连接之前,调用cancelDiscovery()来停止设备搜寻。 并且,当你已经与一个设备建立了连接时,进行设备搜寻会显著降低该连接的可用带宽。所以,在已有连接时,最好不要进行搜寻。
开启蓝牙可检测性
如果你想要本设备能被其他蓝牙设备搜寻到,调用startActivityForResult(Intent, int),Intent为ACTION_REQUEST_DISCOVERABLE action 。 这样会通过系统设置发出一个开启蓝牙可检测性的请求(不需要退出你的应用程序)。 默认情况下,本设备将变为可检测模式,持续120秒。 你可以通过额外添加EXTRA_DISCOVERABLE_DURATION到Intent来定义不同的持续时间。 应用程序能设置的最大持续时间为3600秒,设为0则让设备一直保持可检测状态(任何小于0或大于3600的值,会自动设为120秒)。 例如,以下代码设置持续时间为300秒:
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
在向用户请求开启可检测性时,一个对话框会弹出来。 如果用户选择“是”,设备将在指定的持续时间内开启可检测性。 你的应用程序将接收到onActivityResult()的回调,回调的结果码表示可检测性持续的时间。 如果用户选择“否”或者发生错误,结果码将是RESULT_CANCELED。
注意如果蓝牙功能没有开启,开启可检测性会自动开启蓝牙功能。

在预定的时间内,设备会静默地保持可检测性。 如果你想在可检测模式变化时收到相应的通知,可以注册关于ACTION_SCAN_MODE_CHANGED Intent的BroadcastReceiver。 里面包含EXTRA_SCAN_MODE 和 EXTRA_PREVIOUS_SCAN_MODE的额外字段,分别表示新的和旧的状态。 可能的值是SCAN_MODE_CONNECTABLE_DISCOVERABLE, SCAN_MODE_CONNECTABLE, SCAN_MODE_NONE,分别表示设备处于可检测可连接状态, 或者不可检测但可连接,或者不可检测不可连接。

如果你只想要连接附近的设备,不需要打开可检测性。 只有在你希望应用程序作为蓝牙socket服务端,接收来自其他设备的连接时,才需要打开可检测性。 因为其他设备在发起一个连接前,需要检测到你的设备。
设备连接

要在两个设备的应用之间建立连接,必须实现服务端和客户端的机制。 因为其中一个设备必须开启一个server socket,然后另一个设备才能接入进来(通过服务端设备的MAC地址建立连接)。 当服务端和客户端分别得到一个位于相同RFCOMM信道的已连接BluetoothSocket对象时,则说明成功建立连接。 此时,两者都可以获取输入输出流来开始数据传输,这将在连接管理这一节中讨论。 本节描述如何在两个设备间建立连接。

服务端和客户端获取BluetoothSocket的方式是不一样的, 服务端在接受连接的时候获得一个BluetoothSocket,而客户端是在创建一个连接服务端的RFCOMM信道时获得。
一个实现的方式是每台设备都自动初始化为服务端,这样每台设备都拥有一个开启的服务端socket用于侦听连接, 任意一个设备都可以作为客户端连接到其他设备。 还有一种方式是其中一台设备作为“主机”,开启一个服务端socket,其他设备只需要连接进来即可。
注意:如果两个设备之前没有配对过,Android应用框架将在连接过程中自动显示一个配对请求的通知或者一个对话框。 所以在尝试连接时,你的应用程序不需要考虑设备间是否已配对。 你的RFCOMM连接会被阻塞知道用户成功完成配对。如果用户拒绝配对、配对失败或者超时,连接将会失败。
作为服务端连接
要在两个设备的应用之间建立连接,其中一台需要作为服务器端,保持一个开启的BluetoothServerSocket。 服务器端socket的目的是侦听接入的连接请求,当连接成功时,生成一个已连接的BluetoothSocket对象。 当BluetoothSocket对象从BluetoothServerSocket获得时,该BluetoothServerSocket可以(也应该)废弃了, 除非你想接受更多的连接。
1.获得一个BluetoothServerSocket对象,通过调用listenUsingRfcommWithServiceRecord(String, UUID)
String是你的设备名称,可以自定义。系统会自动将该string写入Service Discovery Protocol (SDP)数据库(设备名称可以随便设定,可以直接使用你的应用名称)。
UUID也包含在SDP中,作为客户端连接协议的基础。就是客户端尝试连接时,连接信息会带上一个UUID,这个UUID是客户端想要连接的服务端的唯一鉴定标识。 UUID必须与服务端匹配,这样连接才会被接受。
关于UUID
Universally Unique Identifier (UUID)是一个可以标准化为128位格式的字符串,用于唯一标识信息。
UUID的好处在于它足够大,以至于你选择任意随机信息都不会重复。
在这里,UUID用于标识应用程序的蓝牙服务端。
你可以通过互联网上的随机UUID生成器来获得一个UUID字符串,然后用fromString(String)方法,初始化一个UUID用到应用程序中。
2.调用accept()开始侦听连接请求。
这是一个阻塞的调用。这个方法在连接成功建立或者错误发生时才会返回。 只有其他设备发送连接请求过来,并且请求中携带的UUID与服务端socket的UUID一致时,连接才会被接受。 当连接成功时,accept()将返回一个已连接的BluetoothSocket对象。
3.除非想接受更多的连接,否则调用close()
该方法将释放服务端socket以及它占用的所有资源,但不会关闭在accept()时返回的已连接BluetoothSocket对象。 跟TCP/IP不一样,RFCOMM只允许单个信道中存在一个连接。 所以大部分情况下,在连接建立之后,调用BluetoothServerSocket的close()方法就可以了。

accept()方法不应在activity的UI线程中调用,因为它是阻塞的,运行时会阻碍程序的其他活动。 通常的做法是应用程序创建一个新线程去完成所有关于BluetoothServerSocket和BluetoothSocket的工作。 如果想中止一个类似accept()这样的阻塞调用,在其他线程调用BluetoothServerSocket (或BluetoothSocket)的close方法,阻塞的调用就会立刻返回。 BluetoothServerSocket和BluetoothSocket的所有方法都是线程安全的。

示例:
private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;

public AcceptThread() {
// Use a temporary object that is later assigned to mmServerSocket,
// because mmServerSocket is final
BluetoothServerSocket tmp = null;
try {
// MY_UUID is the app’s UUID string, also used by the client code
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) { }
mmServerSocket = tmp;
}

public void run() {
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
while (true) {
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
break;
}
// If a connection was accepted
if (socket != null) {
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(socket);
mmServerSocket.close();
break;
}
}
}

/** Will cancel the listening socket, and cause the thread to finish */
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) { }
}
}
在这个示例中,只需要一个接入连接,所以当一个连接被接受并获取到BluetoothSocket对象时,应用程序将BluetoothSocket发送到另一个线程, 关闭了BluetoothServerSocket并停止了while循环。
需要注意的是,accept()返回的BluetoothSocket对象,内部的socket是已连接的,所以不需要像客户端那样调用connect().
manageConnectedSocket()是一个在应用程序中自定义的方法,作用是建立线程传输数据。这将在连接管理这一节中讨论。
当你完成接入连接的侦听时,应该立刻关闭BluetoothServerSocket。 在示例中,得到BluetoothSocket对象时,立刻调用了close(). 你可以在你的线程中,提供一个public方法,在停止服务端侦听的时候,可以同时关闭私有的BluetoothSocket对象。
作为客户端连接
要建立与服务端设备的连接,你必须先获取一个代表该设备的BluetoothDevice对象(获取方式在上面的获取蓝牙设备)。 使用BluetoothDevice来获得一个BluetoothSocket,并建立连接。

基本步骤如下:
1.使用BluetoothDevice,调用createRfcommSocketToServiceRecord(UUID)获得一个BluetoothSocket
这一步初始化一个BluetoothSocket对象用于连接BluetoothDevice。 这里传入的UUID必须与服务端BluetoothServerSocket开启时使用的UUID相匹配(服务端用listenUsingRfcommWithServiceRecord(String, UUID))。 只要同时在服务端和客户端应用程序用相同的UUID字符串硬编码,就可以得到相同的UUID。
2.调用connect()建立连接
在调用的过程中,服务端系统会执行一个SDP检查UUID是否匹配。 如果匹配则接受连接,分享RFCOMM信道,connect()就会返回。 该方法是一个阻塞调用。如果连接失败或者conntct()方法超时(大概12秒)或者其他任何原因,则会抛出一个异常。
因为connect()是阻塞调用,该连接步骤最好放在与主activity线程分离的新线程中执行。
注意:你必须确保在调用connect()时,设备没有在进行搜寻其他设备的活动。否则连接会明显变得很慢并且很容易失败。
示例
这是在一个线程中建立蓝牙连接的基本示例:
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;

public ConnectThread(BluetoothDevice device) {
// Use a temporary object that is later assigned to mmSocket,
// because mmSocket is final
BluetoothSocket tmp = null;
mmDevice = device;

// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
// MY_UUID is the app’s UUID string, also used by the server code
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) { }
mmSocket = tmp;
}

public void run() {
// Cancel discovery because it will slow down the connection
mBluetoothAdapter.cancelDiscovery();

try {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
mmSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
try {
mmSocket.close();
} catch (IOException closeException) { }
return;
}

// Do work to manage the connection (in a separate thread)
manageConnectedSocket(mmSocket);
}

/** Will cancel an in-progress connection, and close the socket */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
需要注意的是在连接开始前调用了cancelDiscovery(),你每次连接前都应该这样做, 调用cancelDiscovery()是安全的,不需要检查设备搜寻是否真的在运行(如果你想检查,可以调用isDiscovering())。
manageConnectedSocket()是一个在应用程序中自定义的方法,作用是建立线程传输数据。这将在连接管理这一节中讨论。
当你用BluetoothSocket完成相关工作之后,记得调用close()来释放。 调用该函数将会立刻关闭socket并清除所有内部资源。
连接管理

当你成功将两个设备连接起来时,每个设备都拥有一个已连接的BluetoothSocket。 乐趣就从此开始了,因为你可以在两台设备间分享数据。 通过使用BluetoothSocket,传输数据的过程非常简单:
1.通过socket获取InputStream和OutputStream来操纵传输,分别使用getInputStream()和getOutputStream()即可。
2.用read(byte[]) 和 write(byte[])在数据流中读写数据。
就这么简单。
当然,还有一些实现细节需要考虑。 首先,同时也是最重要的,是使用一个专门的线程来运行所有的数据流读写。 这非常重要,因为read(byte[]) 和 write(byte[])方法都是阻塞的调用。 read(byte[])方法会一直阻塞,直到从数据流上读取到数据。 write(byte[])一般不会阻塞,但是如果对方设备没有及时调用read(byte[])导致临时缓冲区满了的情况下,该方法会阻塞住便于流控制。 所以,你的线程的主循环可以专门用来InputStream读取,然后实现线程的另一个public方法用于OutputStream写入。
示例
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;

public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;

// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }

mmInStream = tmpIn;
mmOutStream = tmpOut;
}

public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()

// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI activity
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
break;
}
}
}

/* Call this from the main activity to send data to the remote device */
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}

/* Call this from the main activity to shutdown the connection */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
构造函数获取必须的数据流对象,而且一旦执行,线程就会通过InputStream等待数据进入。 当read(byte[])返回数据流上的数据,这些数据就会通过父类的成员Handler被发送到主activity。 然后循环回来继续等待更多数据。
向外发送数据很简单,只需要在主activity调用write()方法,并在参数中传递需要发送的数据。 该方法直接调用write(byte[])发送数据到对方设备。
线程的cancel()方法非常重要,这样才能在任何时候通过关闭BluetoothSocket来中断连接。 当你使用完蓝牙连接之后,记得调用cancel()。
更多关于使用蓝牙API的范例,可以看蓝牙聊天的示例应用. http://developer.android.com/resources/samples/BluetoothChat/index.html
在蓝牙规范协议下工作

从Android 3.0开始,蓝牙API就包含了Bluetooth profiles的支持。 Bluetooth profiles是设备间基于蓝牙通讯的无线接口协议描述。 比如免提协议(Hands-Free),手机如果要与无线耳机连接,两者都要支持免提协议。
你可以在自己的class中实现BluetoothProfile接口,用于支持特定的Bluetooth profile. Android蓝牙API提供了以下Bluetooth profiles的实现:
蓝牙耳机: 蓝牙耳机协议提供了蓝牙耳机在手机上使用的支持。Android提供了BluetoothHeadset类, 这是通过IPC(interprocess communication)控制蓝牙耳机服务的代理。 同时包含了蓝牙耳机(Bluetooth Headset)和免提(Hands-Free v1.5)协议。 BluetoothHeadset类还包含了AT命令的支持。更多信息请查看Vendor-specific AT commands。
A2DP: A2DP(Advanced Audio Distribution Profile),高质量音频分发协议定义了高质量音频的数据流如何通过蓝牙连接从一个设备传输到另一个。 Android提供了BluetoothA2dp类,这是一个通过IPC(interprocess communication)控制蓝牙A2DP服务的代理。
医疗设备: Android 4.0 (API level 14)引入了HDP(Bluetooth Health Device Profile)的支持。 可以让你的应用程序使用蓝牙连接那些支持蓝牙的医疗设备。 例如心率监视器、血压计、体温计等。 网站www.bluetooth.org的Bluetooth Assigned Numbers记录了关于已支持设备的列表和他们的相关设备数据代码。 这些数值也收录在ISO/IEEE 11073-20601 [7] specification as MDC_DEV_SPEC_PROFILE_* in the Nomenclature Codes Annex. 更多关于HDP的信息请查看Health Device Profile。
下面是在蓝牙规范协议下工作的基本步骤:
1.获取默认的适配器,在配置蓝牙这一节中描述过的。
2.使用getProfileProxy()方法建立到协议相关联的代理对象的连接。
在下面的示例中,协议代理对象就是一个BluetoothHeadset实例。
3.创建一个BluetoothProfile.ServiceListener
这个listener可以侦听BluetoothProfile IPC客户端的连接或断开服务。
4.在onServiceConnected()中,获得profile代理对象的句柄。
5.一旦你获得profile proxy对象,你可以用它监视连接状态,还可以执行其他相关的操作。
下面的代码片段介绍了如何连接一个BluetoothHeadset代理对象,并用来控制Headset协议。
BluetoothHeadset mBluetoothHeadset;

// Get the default adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

// Establish connection to the proxy.
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;
}
}
};

// … call functions on mBluetoothHeadset

// Close proxy connection after use.
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);
Vendor-specific AT commands
从Android 3.0开始,应用程序就可以注册接收耳机发送的pre-defined vendor-specific AT commands系统广播(类似Plantronics +XEVENT命令)。 例如,一个应用程序可以接收指示已连接设备电量等级的广播,然后通知用户或者采取其他必要的措施。 创建一个ACTION_VENDOR_SPECIFIC_HEADSET_EVENT intent的broadcast receiver,即可处理vendor-specific AT commands。
Health Device Profile

天边的星星