Android 经典蓝牙用法

概述:

Android平台包括了对蓝牙网络协议栈的支持, 它让设备可以跟其它蓝牙设备实现无线数据交换. 应用框架通过Android Bluetooth API提供了访问蓝牙的功能. 这些API可以让APP无线连接到其它的蓝牙设备, 可以使用点对点和多点无线功能. 通过使用蓝牙API, 一个Android APP可以实现这些功能:

l  扫描其它的蓝牙设备

l  为配对的蓝牙设备查询本地蓝牙适配器

l  建立RFCOMM通道

l  通过”服务发现”连接到其它设备

l  与其它设备交换数据

l  管理多连接

本文描述了如何使用经典蓝牙. 经典蓝牙是电池密集型操作的正确选择, 比如流和Android设备之间通信. 为了实现蓝牙设备低耗电的需求, Android4.3引入了支持Bluetooth Low Energy的API. 该功能可以参考这里. 将在下一篇blog中介绍.

基础:

该文档描述了如何使用Android Bluetooth API来完成四种使用蓝牙通信的主要任务: 设置蓝牙, 在本地区域搜索配对或者可用的设备, 连接设备, 和设备间交换数据. 所有Bluetooth API都在android.bluetooth包中. 这里列举了包中我们需要用到的类和接口的总览:

1.      BluetoothAdapter: 代表本地蓝牙适配器(蓝牙无线电(radio)).BluetoothAdapter是蓝牙交互的入口. 通过它我们可以发现其它的蓝牙设备, 查询配对设备的列表, 使用一个已知的MAC地址实例化一个BluetoothDevice, 还可以创建一个BluetoothServerSocket来监听其它设备发来的通信.

2.      BluetoothDevice: 表示一个远程的蓝牙设备. 使用它可以请求一个与远程设备通过BluetoothSocket建立的链接, 或者用来查询设备的相关信息, 比如名字, 地址, 类, 和配对状态.

3.      BluetoothSocket: 表示为蓝牙socket的接口(类似于TCPSocket). 它是一个连接点, 允许一个APP跟其它的蓝牙设备通过InputStream和OutputStream来交换数据.

4.      BluetoothServerSocket: 代表一个开放的服务器socket,它持续的监听连接进来的需求(类似于一个TCP ServerSocket). 为了连接两个Android设备, 一个设备必须用该类开启一个服务器socket. 当一个远程蓝牙设备请求连接该设备的时候, BluetoothServerSocket将会在连接建立后返回一个连接了的BluetoothSocket.

5.      BluetoothClass: 描述蓝牙设备的一般特征和能力. 这是一组只读的属性, 定义了设备的主要和次要设备类和它的服务. 然而它对所有蓝牙配置文件和服务的描述并不是很牢靠, 但作为设备类型的参考还是有用的.

6.      BluetoothProfile: 一个代表蓝牙配置文件(Bluetoothprofile)的接口. 蓝牙配置文件是一个设备间基于蓝牙通信的无线接口规范. 有一个栗子是Hands-Free(免提)配置文件. 更多信息可以参考”通过配置文件工作”小节.

7.      BluetoothHeadset: 为移动电话的耳机提供支持.包括蓝牙耳机和Hands-Free(v1.5)配置文件.

8.      BluetoothA2dp: 定义高质量音频如何从一个设备流传输到另一个设备. “A2DP”代表Advanced Audio Distribution Profile.

9.      BluetoothHealth: 代表一个控制蓝牙服务的健康设备配置文件代理.

10.  BluetoothHealthCallback: 一个抽象类, 我们可以用它来实现BluetoothHealth的回调. 要使用它则必须要继承该类并实现回调方法来接收关于APP注册状态和蓝牙通道状态的更新.

11.  BluetoothHealthAppConfiguration:代表一个蓝牙健康第三方应用注册到了一个远程蓝牙健康设备的应用配置.

12.  BluetoothProfile.ServiceListener:一个接口, 用于BluetoothProfile IPC客户端与服务连接或者断开连接的时候发出提醒.

蓝牙权限:

为了在APP中使用蓝牙功能, 我们必须声明蓝牙权限:BLUETOOTH. 执行任何的蓝牙通信, 比如请求连接, 接收连接, 数据交换, 都需要该权限. 如果想要我们的APP要启动设备发现或者操作蓝牙设置, 就必须声明BLUETOOTH_ADMIN权限. 大多数APP需要这个权限仅仅是为了发现本地蓝牙设备. 其它被该权限授权的能力不该被使用, 除非APP是”电源管理器”类的, 会根据用户的需求修改蓝牙设置. 注意: 如果使用了BLUETOOTH_ADMIN权限, 那么比需也声明BLUETOOTH权限. 栗子:

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

设置蓝牙:

在使用蓝牙连接和通信之前, 我们必须确认设备可以支持蓝牙, 如果支持的话, 还要确认是否目前是启用的. 如果不支持蓝牙, 那么应该合理的关闭任何蓝牙相关的功能. 如果支持蓝牙, 但是目前蓝牙处于关闭状态, 那么我么你可以在不离开APP的情况下请求用户启动蓝牙. 这个操作需要个步骤, 并且使用BluetoothAdapter类:

1.      得到BluetoothAdapter: 所有的蓝牙activity都需要BluetoothAdapter. 想要得到它需要调用getDefaultAdapter()方法. 该方法返回一个BluetoothAdapter, 代表设备本身的蓝牙适配器(蓝牙无线电). 整个系统有一个蓝牙适配器, 并且APP可以使用该对象跟它通信. 如果getDefaultAdapter()方法返回了null, 那么代表设备不支持蓝牙, 接下来也就没啥事可做了. 栗子:

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

2.      启动蓝牙: 下一步我们就该启动蓝牙了. 调用isEnabled()方法来查看蓝牙当前是否在启动着. 如果返回false, 那么表示蓝牙关闭. 这时候如果想要启动蓝牙, 调用startActivityForResult()方法, 并传给它ACTION_REQUEST_ENABLEaction intent. 这将会通过系统设置发出一个请求启动蓝牙的操作. 栗子:

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

这时候将会弹出一个对话框让用户选择是否启动蓝牙. 如果用户选择了”是”, 系统将会开启蓝牙, 成功(或者失败)后会将焦点返回给APP. 栗子中的REQUEST_ENABLE_BT常量是一个大于0的数字, 它将在onActivityResult()中作为requestCode返回给我们作为参数. 如果成功启动了蓝牙, activity将会在onActivityResult()中收到一个RESULT_OK的结果. 如果失败了, 将会收到RESULT_CANCELED.

还有一个可选项我们可以做的, APP可以监听ACTION_STATE_CHANGED广播intent, 当蓝牙状态改变的时候, 系统将会广播它. 该广播包含额外的域EXTRA_STATE和EXTRA_PREVIOUS_STATE, 分别包含新老蓝牙状态. 这些额外的域可能包含以下值: STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFF, 和STATE_OFF. 如果关注蓝牙状态的话, 监听该广播会很有用.

启动”可以被发现”功能会自动开启蓝牙. 如果我们打算在执行蓝牙activity前持续的启动设备发现功能, 可以跳过上面的两步. 后文详述.

查找设备:

通过BluetoothAdapter, 我们可以通过设备发现和查询配对列表找到远程蓝牙设备.

设备发现是一个扫描程序, 可以搜索附近区域启动了蓝牙的设备并获取各个设备的信息. 但是只有当该远程设备启动了”被发现”功能的时候才有可能被发现. 如果一个设备是可发现的, 那么它将会通过共享信息来回应发现需求, 比如设备的名字, 类, 和MAC地址. 使用这些信息, 就可以选择启动与该设备的连接了.

一旦首次建立了跟远程设备的连接, 一个配对需求会自动展现给用户. 当设备配对了之后, 关于该基础信息就会被保存并可以使用蓝牙API读取到. 使用其中的MAC地址就可以启动一个与它的连接而不用再次处理发现了(假如设备是在范围内的).

配对和连接之间有一个不同. 配对意味着两个设备都知道对方的存在, 有一个共享的link-key在认证的时候可以用, 并可以建立彼此的加密连接. 而”连接上”则意味着设备当前共享一个RFCOMM通道并可以交换数据. 当前Android Bluetooth API要求设备进行RFCOMM连接建立之前需要先进行配对(当通过Bluetooth API启用一个加密连接的时候, 配对将会被自动执行).

下面的小节将会介绍如何找到已经配对的设备以及如何用设备发现功能发现新的设备.

注意: Android设备默认情况下是不可发现的. 用户可以通过系统设置让设备可以被发现一段时间, 或者APP可以请求用户开启发现功能.

查询配对设备:

在执行设备发现之前, 查询一下要连接的设备是否在已经配对的设备列表中是有必要的. 想要实现这个操作, 调用getBonderDevices()方法. 该方法会返回一组BluetoothDevice来描述配对设备. 栗如, 我们可以查询所有的配对设备, 然后用一个列表展示给用户, 用一个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地址. 在该栗子中, 它被保存在一个ArrayAdapter中以便用来展示给用户看. MAC地址可以稍后被提取出来用于启动连接.

发现设备:

想要启动发现设备功能, 只要调用startDiscovery()就可以了. 该过程是同步的, 它会直接返回一个boolean值来表示发现功能是否成功启用. 发现过程通常需要12秒钟来查询扫描, 然后每个发现的设备会用发现的设备名列出来. 我们的APP必须为ACTION_FOUND intent注册一个BroadcastReceiver, 这样可以接收关于每个设备发现的数据. 对于每个设备, 系统都会广播一个ACTION_FOUND的Intent. 这个intent会携带额外的数据域EXTRA_DEVICE和EXTRA_CLASS, 分别包括一个BluetoothDevice和一个BluetoothClass. 栗如, 这是一段关于如何注册处理设备发现的broadcast的代码:

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

跟之前的栗子一样, 我们得到了MAC地址并将其保存起来. 注意, 处理设备发现是一个繁重的任务将会消耗很多资源. 一旦发现了要连接的设备, 请确保尝试连接之前用cancelDiscovery()方法停止发现. 另外, 如果已经与一个设备连接上了, 这时候执行发现动作将会显著降低连接的可用带宽, 所以我们应该在连接的时候不执行发现操作.

启用发现:

如果想要开启本地设备的被发现功能, 调用startActivityForResult(Intent, int)方法并传入ACTION_REQUEST_DISCOVERABLEaction intent. 这将会通过系统设置发出一个需求来启动可发现模式(而不必停止我们自己的APP). 默认情况下, 设备将会开启可发现功能120s. 我们可以通过EXTRA_DISCOVERABLE_DURATION intent extra来定义一个延时时间长度. 最大的开启被发现功能的时间长度是3600秒, 如果设置为0, 则表示永远开启. 任何不在这个范围内的值会被设置为120秒. 栗如, 这是设置为300秒的栗子:

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);

这时将会显示一个对话框请求用户权限来使设备可发现. 如果用户点击了”是”, 那么设备将会被发现指定时长. 然后我们的activity将会在onActivityResult()中收到结果, 返回的result code等于延时时长. 如果用户点击了”否”或者这中间发生了错误, result code将会是RESULT_CANCELED. 如果蓝牙没有开启的话, 启动蓝牙发现功能会自动开启蓝牙.

设备将会默默的在指定时间内保持在可发现模式. 如果我们想要在发现模式发生变化的时候收到提醒, 则需要注册一个广播用来接收ACTION_SCAN_MODE_CHANGED intent. 这将会包含额外数据域EXTRA_SCAN_MODE和EXTRA_PREVIOUS_SCAN_MODE,它们将分别告知我们新老扫描模式. 可能的取值是SCAN_MODE_CONNECTABLE_DISCOVERABLE, SCAN_MODE_CONNECTABLE, 或者SCAN_MODE_NONE,它们分别表示设备时处于可发现模式, 不在可发现模式但是依然可以收到连接, 或者不可发现模式并不能收到连接.

如果我们想要对一个远程设备发起连接, 我们并不需要启用设备发现. 只有在APP要打开一个server socket并接受连接的时候, 我们才有必要启动设备发现功能. 因为只有这样才能被远程设备发现并连接到我们.

连接设备:

为了创建APP在两台设备之间的连接, 我们必须要实现服务器端和客户端机制, 因为一个设备必须打开server socket, 另一个设备必须启动连接(使用server的MAC地址来发起连接). 当客户端和服务端拥有一个通过相同RFCOMM通道连接的BluetoothSocket的时候就可以连接了!(大概是这么个意思. . .). 在此刻, 每个设备可以获取输入和输出流, 可以开始交换数据, 这些将在后面的小节讲到, 本节介绍如何连接两个设备.

服务器设备和客户端设备都会用不同的方法取得BluetoothSocket. 服务器将会在接受请求连接的时候收到BluetoothSocket. 客户端则会在向服务器打开一个RFCOMM通道的时候收到.

一种实现的方法是自动准备每个设备为服务器端, 这样每个设备都有一个服务器socket开启并监听连接. 然后每个设备都可以发起连接成为客户端. 另外还有就是一台设备明确的做主机并在需要的时候打开一个server socket, 然后另一台设备只需要连接就可以了.

注意: 如果两台设备之前不是配对设备, 那么Android framework将会自动的在连接过程中展示一个配对需求提示或者对话框给用户. 所以当尝试连接到设备的时候, 我们的APP不需要确认是否已经配对. RFCOMM连接尝试将会在用户选择成功配对之前阻塞, 或者用户选择不配对的时候失败.

作为服务器连接:

当我们想要连接两个设备, 其中一个必须扮演服务器角色, 并持有一个打开的BluetoothServerSocket. 它的用途是监听接入的连接需求, 当有一个被接受的时候, 提供一个连接的BluetoothSocket.当从BluetoothServerSocket中得到BluetoothSocket后, BluetoothServerSocket可以(应该)被释放, 除非我们想要接受更多的连接.下面是设置server socket和接受连接的基本步骤:

1.      通过listenUsingRfcommWithServiceRecord(String, UUID)得到一个BluetoothServerSocket.方法中的string是一个服务的识别名字, 系统将会自动写入一个Service Discovery Protocol(SDP)数据库(这个名字是随意的, 可以简单的使用APP的名字). UUID也是包含在SDP中的, 并且是连接客户端设备协议的基础. 就是说, 当客户端尝试连接该设备的时候, 它将会携带一个UUID, 这个UUID用来识别唯一的它想要连接的服务. UUID必须匹配才能建立连接.

2.      通过accept()方法启动监听. 这是一个阻塞方法. 它不管连接已经被接受或者是有异常, 都会直接返回. 只有远程设备发起连接请求并且携带一个匹配的UUID的时候连接才会被接受. 当成功之后, accept()将会返回一个连接的BluetoothSocket.

3.      除非我们还需要接受更多的连接, 否则调用close(). 该方法将会释放sever socket和它的所有资源, 但是不会关闭accept()返回的已经连接的BluetoothSocket. 不像TCP/IP, RFCOMM每个通道一次只允许一个客户端连接, 所以大多数情况下都应该在连接建立之后对BluetoothServerSocket直接调用close()方法.

accept()方法不应该在主activity UI线程中调用, 因为它是一个阻塞方法, 会阻止任何其它的app交互. 它通常应该在一个新的线程中跟一个BluetoothServerSocket或者BluetoothSocket合作工作. 想要终止这个阻塞方法的话, 需要从另一个线程调用BluetoothServerSocket(或者BluetoothSocket)的close()方法, 这样所有的阻塞方法就会直接返回了. 注意所有的BluetoothServerSocket和BluetoothSocket方法都是线程安全的.

这里是一个简单的server组件接收连接的栗子:

privateclass AcceptThreadextends Thread{
    private finalBluetoothServerSocket 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, alsoused 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 (ina separate thread)
                manageConnectedSocket(socket);
                mmServerSocket.close();
                break;
            }
        }
    }
 
    /** Will cancelthe listening socket, and cause the thread to finish */
    public void cancel(){
        try {
            mmServerSocket.close();
        } catch(IOException e){ }
    }
}

在该栗子中, 只期望一个接入连接, 所以一旦有一个连接接入了并得到了BluetoothSocket, APP就会发送这个BluetoothSocket到一个独立的线程, 关闭BluetoothServerSocket并停止循环. 注意当accept()返回BluetoothSocket, socket已经连接, 所以我们不应该调用connect()方法. ManageConnectedSocket()是一个虚构的方法, 它将会实例化一个线程用于交换数据. 将在下面的小节讨论它.

我们应该保持在结束监听接入连接后立刻关闭BluetoothServerSocket. 在该栗子中, close()在BluetoothSocket获取到之后立刻就被执行了.我们可能需要在线程中提供一个公共方法来关闭私有的BluetoothSocket.

作为客户端连接:

为了启动一个跟远程设备的连接(一个开启服务端监听的设备), 我们必须先取得一个BluetoothDevice对象来表示远程设备. (获取BluetoothDevice的内容在上面的”查找设备”小节). 然后必须使用BluetoothDevice来获得一个BluetoothSocket并启动连接. 下面是一个基本的步骤:

1.      使用BluetoothDevice, 通过createRfcommSocketToServiceRecord(UUID)得到一个BluetoothSocket.这将初始化一个BluetoothSocket, 它会连接到一个BluetoothDevice. 这里使用的UUID必须匹配服务端设备使用的UUID. 使用相同的UUID很简单只需要在APP中生成硬编码UUID, 然后关联到客户端和服务端代码中.

2.      用connect()方法初始化连接. 一旦调用了该方法, 系统将会在远程设备上执行一个SDP查询来匹配UUID. 如果查询成功并且远程设备接受了连接, 它将会在连接期间共享RFCOMM通道, connect()方法将会返回. 该方法是一个阻塞方法. 如果, 不管啥原因连接失败了或者connect()方法超时(大概12秒), 那么它将会抛出一个异常. 所以它应该在一个独立的线程中运行.

注意, 我们应该总是保证在调用connect()的时候设备没有执行”设备发现”功能. 如果设备发现正在执行, 那么连接尝试将会显著减慢速度并看起来好像失败了.

栗子: 下面的代码展示了一个初始化连接的线程:

privateclass ConnectThreadextends Thread{
    private finalBluetoothSocket mmSocket;
    private finalBluetoothDevice 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, alsoused 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(IOExceptionconnectException){
            // Unable to connect; close the socket andget out
            try {
                mmSocket.close();
            } catch(IOExceptioncloseException){ }
            return;
        }
 
        // Do work to manage the connection (in a separate thread)
        manageConnectedSocket(mmSocket);
    }
 
    /** Will cancelan in-progress connection, and close the socket */
    public void cancel(){
        try {
            mmSocket.close();
        } catch(IOException e){ }
    }
}

注意栗子中的cancelDiscovery()方法在连接建立之前被调用了. 我们应该总是在连接之前这样做, 不管它是不是在运行, 调用该方法都是安全的(但是如果想要检查一下的话, 应该调用isDiscovering()方法). ManageConnectedSocket()是一个虚构的方法, 它将会实例化一个线程用于交换数据. 将在下面的小节讨论它.

当我们处理完了BluetoothSocket, 应该记得调用close()方法来清理. 这样做将会直接关闭连接的socket并清除所有的内部资源.

管理一个连接:

当我们成功的连接了两个(或者更多的)设备, 每个设备都会拥有一个连接着的BluetoothSocket. 这里开始就比较有意思了, 因为我们可以在设备间分享数据了. 使用BluetoothSocket交换数据的一般步骤是这样的:

1.      从socket获取传输的InputStream和OutputStream, 分别使用getInputStream()和getOutputStream()方法.

2.      用read(byte[])和write(byte[])方法读写数据.

完事儿.

当然还有一些实现的细节需要考虑. 首先也是最重要的, 我们应该使用一个专有线程来执行所有的数据读写操作. 这很有必要, 因为read(byte[])和write(byte[])方法都是阻塞方法. Write(byte[])不经常阻塞, 但是可以如果远程设备调用read(byte[])不够快的话会导致中间缓冲区堆满, 并使流控制阻塞. 所以我们的线程主循环应该专门用来从InputStream中读取数据. 线程中可以有一个独立的公共方法来执行OutputStream的写操作.

栗子, 这里是它们实现的样子:

privateclass ConnectedThreadextends Thread{
    private finalBluetoothSocket mmSocket;
    private finalInputStream mmInStream;
    private finalOutputStream 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 UIactivity
                mHandler.obtainMessage(MESSAGE_READ, bytes,-1, buffer)
                       .sendToTarget();
            } catch(IOException e){
                break;
            }
        }
    }
 
    /* Call thisfrom the main activity to send data to the remote device */
    public void write(byte[] bytes){
        try {
            mmOutStream.write(bytes);
        } catch(IOException e){ }
    }
 
    /* Call thisfrom 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了. 完成蓝牙通信之后应该记得调用该方法.

使用配置文件(BluetoothProfile):

从Android3.0开始, Bluetooth API开始提供对使用配置文件的支持. 蓝牙配置文件是一个基于蓝牙通信的设备间的无线接口规范. 其中一个栗子是免提(Hands-Free)配置文件. 如果一个手机想要连接到无线耳机, 那么两个设备必须都支持免提配置文件.

我们可以通过实现BluetoothProfile接口来实现自己的类, 这样就可以支持一个特有的蓝牙配置文件了. Android Bluetooth API提供了这些蓝牙配置文件的实现:

1.      耳机(Headset): 耳机配置文件提供了手机使用蓝牙耳机的支持. Android为我们提供了BluetoothHeadset类, 它是一个通过IPC(inter process communication)控制蓝牙耳机服务的代理. BluetoothHeadset类包括了对AT命令的支持(文后介绍).

2.      A2DP: A2DP是AdvancedAudio Distribution Profile. 它定义了高质量音频如何通过蓝牙实现流传输. Android提供了BluetoothA2dp类, 它是一个通过IPC来控制蓝牙A2DP服务的代理.

3.      健康设备(Health Device): Android4.0引入了对蓝牙健康设备配置文件(BluetoothHealth Device Profile(HDP))的支持. 这让我们可以创建使用蓝牙与支持蓝牙的健康设备通信的APP. 比如心率监测器, 血压计, 温度计等. 想要了解更多相应设备的专业实现代码, 可以到www.bluetooth.org 中的BluetoothAssigned numbers中查找. 还有 ISO/IEEE 11073-20601 [7]中也有.

这里是使用配置文件的基本步骤:

1.      获取默认的adapter, 在前文”设置蓝牙”中有描述.

2.      使用getProfileProxy()来建立一个配置文件到配置文件代理对象的连接. 在下面的栗子中, 配置文件代理对象是一个BluetoothHeadset实例.

3.      设置一个BluetoothProfile.ServiceListener. 当它们从服务器连接或者断开连接的时候该监听器提醒BluetoothProfile IPC客户端.

4.      在onServiceConnected()方法中取得一个配置文件代理对象的句柄.

5.      一旦得到了这个配置文件句柄, 我们就可以用它来监测连接状态和处理其他相应的操作了.

下面的代码片段展示了如何连接到一个BluetoothHeadset代理对象, 以便控制headset配置文件:

BluetoothHeadset mBluetoothHeadset;
 
// Get thedefault adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
 
// Establishconnection to the proxy.
mBluetoothAdapter.getProfileProxy(context, mProfileListener,BluetoothProfile.HEADSET);
 
private BluetoothProfile.ServiceListenermProfileListener= 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;
        }
    }
};
 
// ... callfunctions on mBluetoothHeadset
 
// Close proxyconnection after use.
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);

厂商指定的AT命令:

从Android3.0开始, APP可以注册接收预定义的厂商指定的AT由耳机发送的系统广播命令(比如一个Plantronics +XEVENT命令). 栗如, 一个APP可以接收一个连接设备电量等级的广播, 然后提醒用户或者采取需要的措施. 为ACTION_VENDOR_SPECIFIC_HEADSET_EVENT intent创建一个broadcastreceiver就可以处理耳机厂商指定的AT命令了.

健康设备配置文件:

从Android4.0开始, 引入了对蓝牙健康设备配置文件(HDP)的支持. 这让我们可以创建使用蓝牙跟健康设备(需要支持蓝牙)通信的APP, 比如心率监测器, 血压计, 体温计等. 蓝牙健康API包括BluetoothHealth, BluetoothHealthCallback和BluetoothHealthAppConfiguration类. 要使用蓝牙健康API, 懂得这些关键的HDP概念很重要:

概念

描述

Source

HDP中定义的一个规则. Source是一个健康设备, 该设备必须可以传输医疗数据(体重, 心率等)给一个只能设备, 比如Android手机.

Sink

HDP中定义的一个规则. 在HDP中, 一个sink是一个智能设备, 该设备必须可以接受健康数据. 在Android HDP APP中, sink通过BluetoothHealthAppConfiguration对象表达.

Registration

指为一个指定的健康设备注册一个sink.

Connection

是指在一个健康设备和一个智能设备(比如手机)之间打开一个通道.

创建一个HDP APP:

这是创建一个Android HDP APP 的基本步骤:

1.      获取一个相关的BluetoothHealth代理对象. 就想headset和A2DP配置文件设备一样, 我们必须在BluetoothProfile.ServiceListener中调用getProfileProxy()方法并指定HEALTH配置文件类型来建立一个跟配置文件代理对象的连接.

2.      创建一个BluetoothHealthCallback并注册一个APP配置(BluetoothHealthAppConfiguration)作为健康”sink”.

3.      创建一个连接到健康设备. 一些设备将会启动连接, 这个步骤对于那些设备是不必要的.

4.      当成功连接到一个健康设备, 使用文件描述符读写健康设备. 收到的数据需要使用在IEEE 11073-xxxxx中定义的健康管理规范来解读.

5.      结束之后, 关闭健康通道, 注销APP.

 

总结:

根据类来分析: 一共12个类, 分两组:

BluetoothAdapter

BluetoothDevice

BluetoothSocket

BluetoothServerSocket

BluetoothClass

上面这组是蓝牙的基本类, 会在使用蓝牙发现, 查询和连接中用到.

BluetoothProfile

BluetoothProfile.ServiceListener

BluetoothHeadset

BluetoothA2dp

BluetoothHealth

BluetoothHealthCallback

BluetoothHealthAppConfiguration

这些则相当于扩展类, 用于实现特定功能. 相当于为更丰富的蓝牙功能提供了接口, 而且还对连接等操作做了封装, 使其用法更加简单.

 

参考: https://developer.android.com/guide/topics/connectivity/bluetooth.html

 

发布了81 篇原创文章 · 获赞 4 · 访问量 7万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章