to top

Bluetooth

安卓平台提供对蓝牙通讯栈的支持,它允许设备和其他的蓝牙设备进行无线传输数据。 应用程序层通过安卓蓝牙API来调用蓝牙相关功能。这些API使程序无线连接蓝牙设备,并拥有P2P或多端无线连接的特性。

使用蓝牙API,安卓程序能:

  • 扫描其他蓝牙设备
  • 为可配对蓝牙设备查询蓝牙适配器。
  • 建立RFCOMM通道
  • 通过服务搜索来连接其他设备。
  • 与其他设备进行数据传输。
  • 管理多个连接。

The Basics

本文介绍了怎样使用蓝牙API完成建立蓝牙连接的必要四步:1.打开蓝牙;2.查找附近已配对或可用的设备;3.连接设备;4.设备间数据交换。

所有蓝牙API都在android.bluetooth 包下.下面有一些类和接口的摘要,你可能需要它们来建立蓝牙连接:

BluetoothAdapter
代表本地蓝牙适配器(蓝牙无线电)。BluetoothAdapter是所有蓝牙交互的入口。使用这个你可以发现其他蓝牙设备,查询已配对的设备列表,使用一个已知的MAC地址来实例化一个BluetoothDevice,以及创建一个BluetoothServerSocket来为监听与其他设备的通信。
BluetoothDevice
代表一个远程蓝牙设备,使用这个来请求一个与远程设备的BluetoothSocket连接,或者查询关于设备名称、地址、类和连接状态等设备信息。
BluetoothSocket
代表一个蓝牙socket的接口(和TCP Socket类似)。这是一个连接点,它允许一个应用与其他蓝牙设备通过InputStream和OutputStream交换数据。
BluetoothServerSocket
代表一个开放的服务器socket,它监听接受的请求(与TCP ServerSocket类似)。为了连接两台Android设备,一个设备必须使用这个类开启一个服务器socket。当一个远程蓝牙设备开始一个和该设备的连接请求,BluetoothServerSocket将会返回一个已连接的BluetoothSocket,接受该连接。
BluetoothClass
描述一个蓝牙设备的基本特性和性能。这是一个只读的属性集合,它定义了设备的主要和次要的设备类以及它的服务。但是,它没有描述所有的蓝牙配置和设备支持的服务,它只是暗示了设备的类型。
BluetoothProfile
一个表示蓝牙配置文件的接口。一个Bluetooth profile是一个基于蓝牙的通信无线接口定义。一个例子是Hands-Free profile。更多的讨论请见Working with Profiles。
BluetoothHeadset
提供对移动手机使用的蓝牙耳机的支持。它包含了Headset and Hands-Free (v1.5)配置文件。
BluetoothA2dp
定义高品质的音频如何通过蓝牙连接从一个设备传输到另一个设备。”A2DP“是Advanced Audio Distribution Profile的缩写。
BluetoothHealth
表示一个Health Device Profile代理,它控制蓝牙服务。
BluetoothHealthCallback
一个抽象类,你可以使用它来实现BluetoothHealth的回调函数。你必须扩展这个类并实现回调函数方法来接收应用程序的注册状态改变以及蓝牙串口状态的更新。
BluetoothHealthAppConfiguration
表示一个应用程序配置,Bluetooth Health第三方应用程序注册和一个远程Bluetooth Health设备通信。
BluetoothProfile.ServiceListener
一个接口,当BluetoothProfile IPC客户端从服务器上建立连接或断开连接时,它负责通知它们(也就是,运行在特性配置的内部服务)。

Bluetooth Permissions

为了在你的应用中使用蓝牙特性,你需要至少声明一种蓝牙权限:BLUETOOTH 和BLUETOOTH_ADMIN。

为了执行任何蓝牙通信,例如请求一个连接、接受一个连接以及传输数据,你必须请求BLUETOOTH 权限。

为了初始化设备查找或控制蓝牙设置,你必须请求BLUETOOTH_ADMIN权限。大多数应用需要这个权限,仅仅是为了可以发现本地蓝牙设备。这个权限授权的其他功能不应该被使用,除非该应用是一个“强大的控制器”,来通过用户请求修改蓝牙设置。注意:如果你使用BLUETOOTH_ADMIN权限,那么必须拥有BLUETOOTH权限。

在你的应用程序清单文件中声明蓝牙权限。例如:

 
<manifest ... >
  <uses-permission android:name="android.permission.BLUETOOTH" />
  ...
</manifest>

更多关于声明应用程序权限的信息请见参考。

Setting Up Bluetooth

Figure 1: The enabling Bluetooth dialog.

在你的应用可以通过蓝牙进行通信之前,你需要验证该设备是否支持蓝牙,如果支持,确保它被打开了。

如果不支持蓝牙,那么你应该优雅地关掉所有蓝牙特性,如果支持蓝牙,但是没有开启,那么你可以请求用户在不离开你的应用前提下开启蓝牙。这个过程使用BluetoothAdapter用两步完成。

  1. Get the BluetoothAdapter

    有的蓝牙活动都需要请求BluetoothAdapter。为了得到BluetoothAdapter,调用静态的getDefaultAdapter()方法。它返回一个BluetoothAdapter,代表设备本身的蓝牙适配器(蓝牙无线电)。一个完整的系统只有一个蓝牙适配器,而且你的应用可以使用BluetoothAdapter与它进行交互。如果BluetoothAdapter返回为null,那么设备不支持蓝牙而你的故事将在此结束。例如:

     
    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
        // Device does not support Bluetooth
    }
    
  2. Enable Bluetooth

    接下来,你需要保证蓝牙是开启的。调用isEnabled()来检查蓝牙最近是否是开启的。如果这个方法返回false,那么蓝牙没有开启。为了请求开启蓝牙,调用startActivityForResult()并使用ACTION_REQUEST_ENABLE方法Intent。这将发出一个请求来利用系统设置开启蓝牙(不需要停止你的应用)。例如:

     
    if (!mBluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }
    

    一个对话框将会出现,请求用户开启蓝牙,像在图1中展示的那样。如果用户相应”是“,系统将会开启蓝牙,而且一旦完成进程(或失败)就返回你的应用。

    传给startActivityForResult()的REQUEST_ENABLE_BT常量是一个本地定义的整型(必须大于0),系统在你的onActivityResult()实现中返回给你作为requestCode参数。

    如果开启蓝牙成功,你的activity在onActivityResult()回调函数中接受一个RESULT_OK结果代码。如果蓝牙因为一个错误(或者用户相应”否“)没有开启,那么结果代码就是RESULT_CANCELED。

可选的,你的应用也可以监听ACTION_STATE_CHANGED广播Intent,任何时候蓝牙状态改变了系统将会广播该Intent。这个广播包含了额外的变量EXTRA_STATE 和EXTRA_PREVIOUS_STATE,分别包含了新的和老的蓝牙状态。这些额外的变量可能值是STATE_TURNING_ON,STATE_ON,STATE_TURNING_OFF和STATE_OFF。监听这个广播对于检测你的应用运行时蓝牙状态的变化是非常有用的。

提示:开启可发现性将会自动开启蓝牙。如果你打算在执行蓝牙activity之前长期开启设备的可发现性,你可以跳过上面的第二步。阅读下面的enabling discoverability。

Finding Devices

使用BluetoothAdapter,你可以通过设备检测或者查询已配对的设备来找到远程蓝牙设备。

设备检测是一个浏览流程,它查找附近的蓝牙可用设备,然后请求每个设备的相关信息(这有时被称为“检测”“查询”或“浏览”等)。虽然,附近的蓝牙设备仅在它目前是可被检测的状态下时才会回应发现请求。如果一个设备是可被检测的,它将通过分享一些数据来相应检测请求,例如设备名称,类,以及他的唯一的MAC地址。使用这个信息,进行检测的设备可以选择和检测到的的设备初始化一个连接。

一旦一个连接第一次和一个远程设备进行连接,一个匹配的请求会自动呈现在用户面前。当一个设备配对后,设备的基本信息(例如设备名称、类、MAC地址)将被保存,并且可以使用蓝牙接口进行访问。使用已知的MAC地址,一个连接可以在任何时间被初始化,而不需要执行检测(假设设备在可检测范围内)。

记住,配对和连接之间有一点是不同的。配对以为着两台设备是知道彼此的存在的,它们有一个共享的链接密匙,可以用于授权以及建立一个加密的连接。而连接意味着设备目前共享一个RFCOMM通道,并且可以彼此传输数据。目前的安卓蓝牙接口要求设备在建立一个RFCOMM连接之前先进行配对。(当你使用蓝牙接口初始化一个加密的连接时,配对是自动执行的)。

下面的章节描述了如何查找已配对的设备,或者使用设备检测找到新的设备。

注意:安卓设备默认是不可检测的。一个用户可以通过系统设置使设备在一定时间内是可检测的,或者一个应用可以请求用户开启可检测功能而不需要离开应用。下面描述了怎样开启可检测功能。

Querying paired devices

Before performing device discovery, its worth querying the set of paired devices to see if the desired device is already known. To do so, call getBondedDevices(). This will return a Set of BluetoothDevices representing paired devices. For example, you can query all paired devices and then show the name of each device to the user, using an 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());
    }
}

All that's needed from the BluetoothDevice object in order to initiate a connection is the MAC address. In this example, it's saved as a part of an ArrayAdapter that's shown to the user. The MAC address can later be extracted in order to initiate the connection. You can learn more about creating a connection in the section about Connecting Devices.

Discovering devices

To start discovering devices, simply call startDiscovery(). The process is asynchronous and the method will immediately return with a boolean indicating whether discovery has successfully started. The discovery process usually involves an inquiry scan of about 12 seconds, followed by a page scan of each found device to retrieve its Bluetooth name.

Your application must register a BroadcastReceiver for the ACTION_FOUND Intent in order to receive information about each device discovered. For each device, the system will broadcast the ACTION_FOUND Intent. This Intent carries the extra fields EXTRA_DEVICE and EXTRA_CLASS, containing a BluetoothDevice and a BluetoothClass, respectively. For example, here's how you can register to handle the broadcast when devices are discovered:

 
// 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

All that's needed from the BluetoothDevice object in order to initiate a connection is the MAC address. In this example, it's saved as a part of an ArrayAdapter that's shown to the user. The MAC address can later be extracted in order to initiate the connection. You can learn more about creating a connection in the section about Connecting Devices.

Caution: Performing device discovery is a heavy procedure for the Bluetooth adapter and will consume a lot of its resources. Once you have found a device to connect, be certain that you always stop discovery with cancelDiscovery() before attempting a connection. Also, if you already hold a connection with a device, then performing discovery can significantly reduce the bandwidth available for the connection, so you should not perform discovery while connected.

Enabling discoverability

If you would like to make the local device discoverable to other devices, call startActivityForResult(Intent, int) with the ACTION_REQUEST_DISCOVERABLE action Intent. This will issue a request to enable discoverable mode through the system settings (without stopping your application). By default, the device will become discoverable for 120 seconds. You can define a different duration by adding the EXTRA_DISCOVERABLE_DURATION Intent extra. The maximum duration an app can set is 3600 seconds, and a value of 0 means the device is always discoverable. Any value below 0 or above 3600 is automatically set to 120 secs). For example, this snippet sets the duration to 300:

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
Figure 2: The enabling discoverability dialog.

A dialog will be displayed, requesting user permission to make the device discoverable, as shown in Figure 2. If the user responds "Yes," then the device will become discoverable for the specified amount of time. Your activity will then receive a call to the onActivityResult()) callback, with the result code equal to the duration that the device is discoverable. If the user responded "No" or if an error occurred, the result code will be RESULT_CANCELED.

Note: If Bluetooth has not been enabled on the device, then enabling device discoverability will automatically enable Bluetooth.

The device will silently remain in discoverable mode for the allotted time. If you would like to be notified when the discoverable mode has changed, you can register a BroadcastReceiver for the ACTION_SCAN_MODE_CHANGED Intent. This will contain the extra fields EXTRA_SCAN_MODE and EXTRA_PREVIOUS_SCAN_MODE, which tell you the new and old scan mode, respectively. Possible values for each are SCAN_MODE_CONNECTABLE_DISCOVERABLE, SCAN_MODE_CONNECTABLE, or SCAN_MODE_NONE, which indicate that the device is either in discoverable mode, not in discoverable mode but still able to receive connections, or not in discoverable mode and unable to receive connections, respectively.

You do not need to enable device discoverability if you will be initiating the connection to a remote device. Enabling discoverability is only necessary when you want your application to host a server socket that will accept incoming connections, because the remote devices must be able to discover the device before it can initiate the connection.

Connecting Devices

为了在两台设备上创建一个连接,你必须实现服务器端和客户端两头的机制,因为一个设备必须打开一个服务器socket,而另一个设备初始化创建(使用服务器设备的MAC地址来初始化一个连接)。当他们在相同的RFCOMM通道上有一个已连接的 BluetoothSocket 时,服务器和客户被认为是互相连接了。这时,每一个设备可以包含输入和输出流,而且可以开始数据传输,这在Managing a Connection课程中将会讨论。本节课描述了怎样在两台设备之间初始化连接。

服务器设备和客户端设备使用不同的方法来得到需要的 BluetoothSocket 。服务器在接受外来的连接的将会接收到它。客户端在向服务器端打开一个RFCOMM通道时会接收到它。

图3:蓝牙配对对话框

一种实现技术使得每一个设备都可以成为一个服务器,因此每一个都有一个打开的服务器socket,并且随时监听连接。然后另一个设备可以初始化连接,并且成为客户端。对应的,一个设备可以显式地“发起”连接,并且在需要时打开一个服务器socket,而另一个设备可以简单地初始化连接即可。

注意:如果两台设备之前没有配对过,那么Android框架将会自动显示一个请求配对的通知或对话框,正如图3中显示的那样。因此,当尝试连接设备时,你的应用不需要考虑设备是否配对过。你的RFCOMM连接尝试将会阻塞,知道用户成功配对,或者用户拒绝失败时,或者配对失败,或者超时。

Connecting as a server

当你想要连接两台设备时,一个必须通过持有一个打开的 BluetoothServerSocket 来作为服务器端。服务器socket的目的是为了监听外来的连接请求,当一个请求被接受后,提供一个连接的BluetoothSocket。当该BluetoothSocket被BluetoothServerSocket请求时,BluetoothServerSocket 可以(而且应当)被舍弃,除非你想要接收更多的连接。

下面是创建一个服务器socket并且接受一个连接的基本过程:

  1. 用 listenUsingRfcommWithServiceRecord(String, UUID)得到一个BluetoothServerSocket。

    这个String是你的服务的标志名称,系统将会把它写入设备中的一个新的服务发现协议(SDP)数据库条目中(名字是任意的,并且可以只是你应用的名字)。UUID同样被包含在SDP条目中,并且将会成为和客户端设备连接协议的基础。也就是说,当客户端尝试连接这个设备时,它将会携带一个UUID用于唯一指定它想要连接的服务器。这些UUIDs必须匹配以便该连接可以被接受(在下一步中)。 通过调用accept()开始监听连接请求。

  2. 通过调用accept()开始监听连接请求。

    这一个阻塞调用。在一个连接被接受或一个异常出现时,它将会返回。只有当一个远程设备使用一个UUID发送了一个连接请求,并且该UUID和正在监听的服务器socket注册的UUID相匹配时,一个连接才会被接受。成功后,accept() 将会返回一个已连接的 BluetoothSocket。

  3. 调用close(),除非你想要接受更多的连接。

    这将释放服务器socket和它所有的资源,但是不会关闭 accept()返回的已连接的 BluetoothSocket。不同于TCP/IP,RFCOMM仅仅允许每一个通道上在某一时刻只有一个已连接的客户端,因此在大多数情况下在接受一个已连接的socket后,在BluetoothServerSocket上调用 close() 是非常必要的。

accept() 不应该再主活动UI线程上执行,因为它是一个阻塞调用,并且将会阻止任何与应用的交互行为。它通常在你的应用管理的一个新的线程中使用一个BluetoothServerSocket 或 BluetoothSocket 来完成所有工作。为了中止一个阻塞调用,例如accept(),从你的其他线程里在BluetoothServerSocket (或 BluetoothSocket) 上调用 close() ,然后阻塞调用就会立即返回。注意在 BluetoothServerSocket 或 BluetoothSocket 上所有的方法都是线程安全的。

Example

下面是一个简单的线程,用于服务器端接受外来的连接:

 
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 然后打破循环。

注意当accept()返回一个 BluetoothSocket是,该socket已经被连接了,因此你不应该调用connect()(正如你在客户端里所做的)。

manageConnectedSocket()是一个虚构的方法,它将会初始化线程来传输数据,这在 Managing a Connection 中将会讨论。

你通常应该关闭你的 BluetoothServerSocket,一旦你已经监听到了外来的连接。在这个例子里, 一旦得到了 BluetoothSocket 就会调用close()。你也可能想要在你的线程中提供一个公共的方法来关闭事件中私有的 BluetoothSocket ,以便你需要停止在服务器socket上监听。

作为客户端连接

为了和一个远程设备(一个持有服务器socket的设备)初始化一个连接,你必须首先得到一个 BluetoothDevice 对象来表示这个远程设备。(上面的课程Finding Devices讲述了如何得到一个 BluetoothDevice )。然后你必须使用BluetoothDevice来得到一个 BluetoothSocket ,然后初始化该连接。

下面是基本的过程:

  1. 使用 BluetoothDevice,通过调用createRfcommSocketToServiceRecord(UUID)来得到一个 BluetoothSocket 。

    T这将初始化一个BluetoothSocket,它连接到该BluetoothDevice。这里传递的UUID必须和服务器设备开启它的 BluetoothServerSocket时使用的UUID相匹配。

  2. 通过调用connect()初始化一个连接。

    执行这个调用时,系统将会在远程设备上执行一个SDP查找工作,来匹配UUID。如果查找成功,并且远程设备接受了连接,它将会在连接过程中分享RFCOMM通道,而 connect() 将会返回。这个方法是阻塞的。如果,处于任何原因,该连接失败了或者connect()超时了(大约12秒以后),那么它将会抛出一个异常。

    因为connect()是一个阻塞调用,这个连接过程应该总是在一个单独的线程中执行。

    注意:你应该总是确保在你调用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()。你应该在连接前总是这样做,而不需要考虑是否真的有在执行查询任务(但是如果你想要检查,调用 isDiscovering())。manageConnectedSocket()是一个虚拟的方法,它将会初始化一个线程用于传输数据,这在Managing a Connection课程中将会讨论。

manageConnectedSocket() is a fictional method in the application that will initiate the thread for transferring data, which is discussed in the section about Managing a Connection.

当你使用完你的 BluetoothSocket后,总是调用close()来清除资源。这样做将会立即关闭已连接的socket,然后清除所有的内部资源。 Managing a Connection —— 管理一个连接

Managing a Connection

当你成功连接两个(或更多)设备后,每一个都将有一个已连接的 BluetoothSocket。这就是乐趣开始的地方,因为你可以在不同的设备之间共享数据了!使用 BluetoothSocket,常见的二进制数据传输是很简单的:

  1. 分别通过getInputStream() 和 getOutputStream()来得到 InputStream 和 OutputStream 来控制socket之间的传输。
  2. Read and write data to the streams with read(byte[]) and write(byte[]). 使用 read(byte[]) 和 write(byte[])来向数据流中读取和写入数据。

就是这样啦!

当然,实现的细节需要考虑。首先并且最重要的是,你应该为所有输入和输出的数据流使用一个专属的线程。这是十分重要的,因为read(byte[]) 和 write(byte[])方法都是阻塞调用。 read(byte[])将会发生阻塞知道送数据流中读取到了一些东西。 write(byte[])不经常发生阻塞,但是当远程设备没有足够迅速地调用 read(byte[])而中间缓冲区已经负载时可以阻塞。因此,你的线程中的主要循环应该是专门从InputStream中读取数据的。一个单独的公共方法可以被用于初始化向 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[]) 返回一些数值时,数据将会使用一个父类的一个成员变量句柄发送给主活动。然后它返回并继续等待更多的数据。

发送数据只需要在主活动中调用线程的 write()方法,并将需要发送数据传递给它即可。这个方法然后调用 write(byte[])来向远程设备发送数据。

线程的cancel() 方法是重要的,以便连接可以在任何时间被中断(通过关闭BluetoothSocket)。这个方法应该在你完成蓝牙连接后总是被调用。

使用蓝牙APIs的更多例子,详见Bluetooth Chat sample app。

Working with Profiles

从Android 3.0开始,蓝牙API包含了对蓝牙配置文件的支持。一个蓝牙配置文件是一个对设备之间依赖蓝牙交互的无线接口定义。一个例子是Hands-Free 配置文件。对于一个和蓝牙耳机相连接的移动电话,两个设备都必须支持Hands-Free 配置文件。

你可以实现BluetoothProfile接口来定义你自己的类,以便支持特定的蓝牙配置文件。Android蓝牙API提供了以下蓝牙配置文件的实现:

  • 耳机:耳机配置文件提供了对于移动设备使用的蓝牙耳机的支持。Android提供了 BluetoothHeadset 类,它是一个通过进程间的交互(IPC)来控制蓝牙耳机服务的代理。它包含了 Bluetooth Headset 和 Hands-Free (v1.5) 配置文件。 BluetoothHeadset类包含了对AT命令的支持。更多的信息,详见 Vendor-specific AT commands 。
  • A2DP:Advanced Audio Distribution Profile (A2DP)配置文件定义了高质量的音频可以如何通过蓝牙连接从一台设备上传输到另一台设备上。Android提供了 BluetoothA2dp 类,它是一个通过IPC管理蓝牙A2DP服务的代理。
  • 保健设备:Android 4.0(API level 14)介绍了对于蓝牙保健设备配置文件(HDP)。它允许你使用蓝牙和支持蓝牙的保健设备进行交互来创建应用,例如心跳监控器,血压,温度计,直尺等。参见Bluetooth Assigned Numbers atwww.bluetooth.org得到支持设备的列表和它们对应的设备数据定义代码。注意,这些值也可以在 ISO/IEEE 11073-20601 [7] 定义中查找。更多关于HDP的讨论,详见 Health Device

下面是操作一个配置文件的基本步骤:

  1. 得到默认的适配器,正如 Setting Up Bluetooth描述的那样。
  2. 使用 getProfileProxy() 来建立一个和配置代理对象的连接。这下面的实例代码中,配置代理对象是一个 BluetoothHeadset的实例。
  3. 建立一个 BluetoothProfile.ServiceListener。这个监听器通知 BluetoothProfile IPC客户端,当它们已经和服务器连接或取消连接时。
  4. 在 onServiceConnected()里,得到一个配置代理对象的句柄。
  5. 一旦你有了配置代理对象,你可以使用它来监视连接状态,并且执行和配置文件相关的其他操作。

例如,下面的代码片段展示了如何和一个BluetoothHeadset代理进行连接,以便你可以控制 Headset profile:

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开始,应用可以注册来接收系统广播的、耳机发送的、预定义的、厂商特定的AT命令(例如一个Plantronics +XEVENT 命令)。例如,一个应用可以接收广播,来表明一个已连接的设备的电池等级,并且可以告知用于或者采取其他需要的动作。为 ACTION_VENDOR_SPECIFIC_HEADSET_EVENT Intent 创建一个广播接收器来为耳机控制厂商特定的AT命令。

Health Device Profile

Android 4.0(API level 14) 介绍了对蓝牙保健设备配置文件(HDP)的支持。它允许你使用蓝牙和支持蓝牙的保健设备进行交互来创建应用,例如心跳监控器,血压,温度计,直尺等。蓝牙保健设备API包含了BluetoothHealth,BluetoothHealthCallback 和 BluetoothHealthAppConfiguration 类,The Basics.描述了它们。

为了使用Bluetooth Health API,理解下面这些关键的HDP概念是有用的:

Concept Description
Source A role defined in HDP. A source is a health device that transmits medical data (weight scale, glucose meter, thermometer, etc.) to a smart device such as an Android phone or tablet.
Sink A role defined in HDP. In HDP, a sink is the smart device that receives the medical data. In an Android HDP application, the sink is represented by a BluetoothHealthAppConfiguration object.
Registration Refers to registering a sink for a particular health device.
Connection Refers to opening a channel between a health device and a smart device such as an Android phone or tablet.

Creating an HDP Application

下面是创建一个Android HDP应用的基本步骤:

  1. 和通常的耳机和A2DP配置文件设备类似,你必须使用BluetoothProfile.ServiceListener和HEALTH 配置文件类型来调用getProfileProxy(),以建立一个和配置代理对象的连接。
  2. 创建一个BluetoothHealthCallback ,然后注册一个应用配置(BluetoothHealthAppConfiguration)来作为一个health sink。
  3. 建立一个和保健设备的连接,一些设备将会初始化连接。对于这些设备来说,这一步是不需要的。
  4. 当成功建立和保健设备的连接后,使用 file descriptor来向保健设备读取/写入数据。

    收到的数据需要使用保健管理器(实现了 IEEE 11073-xxxxx定义)来解读。

  5. 完成以后,关闭保健通道,然后注销应用。通道也会在静止时关闭。

完整的实例这些步骤的代码,请见Bluetooth HDP (Health Device Profile)。