`

Android中使用蓝牙

阅读更多

    原文地址:http://developer.android.com/guide/topics/connectivity/bluetooth.html,英语水平有限,有什么问题请指正。

   

    安卓平台支持一个设备通过无线的蓝牙网络,与另一个蓝牙设备进行数据交互。应用框架提供了访问蓝牙功能的接口。这些接口使应用能够实现与其他蓝牙设备进行点对点和多点的无线连接。

 

    通过安卓提供的蓝牙接口,安卓应用可以完成以下操作:

  • 浏览其他蓝牙设备
  • 查询本地的蓝牙配对
  • 创建基于RFCOMM的频道
  • 通过蓝牙发现服务与其他设备相连接
  • 在设备之间进行数据转换
  • 管理多点连接
  基础知识

    这篇文档介绍了如何使用安卓提供的接口来实现需要通过蓝牙功能的四个主要任务:对蓝牙进行设置,在附近找到配对过或者可用的其他蓝牙设备,蓝牙设备之间的连接,蓝牙设备之间的数据交互。
 
    所有的蓝牙相关的API都被写在 android.bluetooth 这个包下。这里简单介绍一下创建蓝牙连接需要用到的类和接口。
 
    BuletoothAdapter,表示了蓝牙适配器(蓝牙无线电)。BluetoothAdapter是所有蓝牙交互的入口。通过它,你可以发现周边的蓝牙设备,查看已经配对的设备列表,用一个已知的MAC地址实例化一个BuletoothDevice对象,以及创建一个BlutoothServerSocket对象监听来自其他设备的数据通信。
 
    BuletoothDevice,表示一个远程的蓝牙设备。用来向一个通过BuletoothSocket或者通过名称、地址、种类以及绑定的状态来查询到一个远程设备请求连接。
 
    BluetoothSocket,表示蓝牙的Socket接口(类似TCP协议的Socket)。这是允许蓝牙设备之间通过输入输出流进行数据交互的连接点。
 
    BluetoothServerSocket,表示一个开放的Socket服务用来监听收到的请求(类似于TCP协议的ServerSocket)。为了使两个蓝牙设备进行连接,其中一个设备必须用这个类开放一个Socket服务。当一个远程蓝牙设备向另一个设备发送一个连接请求,当连接的请求被接受时,BluetoothServerSocket类会返回BluetoothSocket。
 
 
    BluetoothClass,描述一个蓝牙设备的基本功能和特性。这是一个定义了设备主要的、次要的设备类和服务的只读集合。然而,它并不能非常准确得描述出所有蓝牙设备的属性和设备支持的服务,不过对于默认的设备类型还是很有用的。
 
    BluetoothProfile,表示一个蓝牙的属性接口。蓝牙的相关属性是基于蓝牙的,两个设备之间进行通信的规范。
 
    BluetoothHeadset,为手机使用的蓝牙耳机提供支持。它包括蓝牙耳机还有免提的规范。
 
    BluetoothHealth,代表了一个医疗设备代理,它控制着蓝牙服务。
 
    BluetoothA2dp,定义了高品质的音频如何在通过蓝牙连接在设备之间传输。“A2DP”表示"Advanced Audio Distribution Profile",高质量音频分发规范
 
    BluetoothHealthCallback,一个抽象类用来实现BluetoothHealth的回调。使用时必须继承这个类并实现它的回调方法,用于接收应用注册状态和蓝牙频道状态的更新和变化。
 
    BluetoothHealthAppConfiguration,用于配置一个第三方应用到蓝牙功能的应用程序和移动蓝牙设备进行通讯。
 
    BluetoothProfile.ServiceListener,一个通知BluetoothProfile IPC客户端是否与服务连接成功的接口(一个内部服务,运行了一个特定的配置文件)。
 
    蓝牙的相关权限

    为了在你的应用程序中使用蓝牙功能,你必须在应用程序的配置文件中声明使用蓝牙的权限 BLUETOOTH。声明了这个权限,才能够在应用中实现蓝牙的通讯,例如请求连接、允许连接以及数据交互。

 

    如果你希望你的应用程序初始化设备搜索或者控制蓝牙功能的配置,你必须声明BLUETOOTH_ADMIN这个权限。大多数应用程序需要这个权限专为完成搜索本地设备的功能。权限中的其他功能一般不常用到,除非你所开发的一个用程序是一个“电池管理”的应用,使用这个权限根据用户的请求修改蓝牙的设置。这里需要注意的是,当你需要使用BLUETOOTH_ADMIN权限时,你必须同时声明BLUETOOTH这个权限。

 

    在应用程序的manifest文件中声明如下:

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

     

    设置蓝牙功能


    在你的应用程序使用蓝牙与其他蓝牙设备通信之前,你需要确保应用所在的设备上支持蓝牙功能,如果设备支持的话,你也需要确保蓝牙是打开状态。

 

    如果你使用的设备不支持蓝牙功能,那你只能禁用所有有关蓝牙的功能了。如果设备支持蓝牙功能,但是设置中蓝牙功能被禁用,那么你可以在不离开你的应用程序的情况下请求启用蓝牙功能。这个设置可以通过BluetoothAdapter这个类分成两步完成。

 

    一、使用BluetoothAdapter类。BluetoothAdpate这个类是任何蓝牙相关的Activity都必须用到的。调用它的静态方法 getDefaultAdapter() 方法,获取BluetoothAdapter类的一个实例。这个静态方法返回了一个BluetoothAdapter对象,代表了这个设备本身的蓝牙适配器。在整个设备上只有一个蓝牙适配器,你的应用程序可以通过这个对象与之交互。如果getDefaultAdapter() 返回的是null,那么你使用的这个设备并不支持蓝牙功能,你也无法完成有关蓝牙的功能开发。代码如下:

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

 

    二、启用蓝牙功能。第二步,你需要确认蓝牙是开启的状态。调用 isEnable() 方法来确认蓝牙模块当前是否已经启用。如果返回值是false,那么蓝牙模块是被禁用的状态。可以使用ACTION_REQUEST_ENABLE作为参数,调用startActivityForResult() 方法来请求启用蓝牙功能。它会想系统设置发送一个请求来启用蓝牙功能(在不停止你当前的应用程序的情况下)。代码如下:

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

   在发送启用蓝牙的请求之后,会弹出一个对话框。当用户点击 Yes 时,系统就会开始去启用蓝牙功能并在成功启用的同时给你打开成功(或失败)的提示。如下图所示:


    在startActivityForResult()方法中使用到的REQUEST_ENABLE_BT 参数,是一个本地第一好了的整形常量(必须大于0),系统会在onActivityResult()中回传给你一个作为请求编号的参数。

    如果启用蓝牙功能成功,那么你的Activity会通过onActivityResult() 的回调收到RESULT_OK 这个参数。相反的,如果失败的话,就会收到 RESULT_CANCELED.

 

    你也可以选择另一个方式,你的应用程序可以监听ACTION_STATE_CHANGED的广播,系统会在蓝牙启用或者禁用状态改变时进行广播。广播的信息中有EXTRA_STATE和EXTRA_PREVIOUS_STATE两个变量,分别用来表示蓝牙的旧的状态和新的状态。这两个变量可能的值有STATE_TURNING_ON,STATE_ON,STATE_TURNING_OFF,STATE_OFF。在应用程序运行时,监听这个广播可以监测到蓝牙设备状态的改变。

 

    启用设备可发现的功能会自动打开蓝牙功能。如果你想要在执行蓝牙相关功能之前始终保持设备科发现,那么你可以忽略以上的步骤二。

 

    发现设备


     借助BluetoothAdapter类,你可以通过设备发现和从已配对(或绑定)的设备列表中找到其他的无线蓝牙设备。

    

    设备搜索是一个扫描程序用于发现周边的开启蓝牙功能的设备,它会向每个设备请求并获取相关信息(这个过程有时被称为发现、调查、扫描)。然而,只有当周边一定范围内允许被发现的蓝牙设备才会对搜索时的请求进行回应。如果设备是可发现的,那么当发送搜索设备的请求是,它会反馈一些基本的信息,例如设备名称、设备类别和该设备唯一的MAC地址编码。通过这些信息,正在执行搜索的设备可以选择一个可发现的设备开始连接。

 

    当一个蓝牙设备第一次连接成功时,配对的请求会自动的发送给用户。当一个蓝牙设备已经成功配对,那么这个设备的基本信息(如设备名称、设备类别和MAC地址)会被保存起来,这些信息也可以被蓝牙相关的API读取到。通过这些已知的设备的MAC地址,可以在不执行设备发现的情况下随时建立起新的连接(假如该设备在可连接的范围之内)。

 

    你必须记住,已配对和已连接是有区别的。已配对表示两个设备已经知道互相的存在,拥有一个共享的连接秘钥,用于身份验证并能够与其他设备建立起一个加密连接。已连接表示几个设备当前正共享了一个RFCOMM频道,相互之间能进行数据的交互。当前设备的Android系统会在两个设备建立起RFCOMM连接之前,登记配对信息(配对是当你初始化加密连接的时候通过蓝牙程序自动执行的)。

 

    接下去的内容描述了如何发现已配对的设备或者通过设备查找发现新的设备。

 

    注意:安卓系统的设备默认是不可被发现的。用户可以通过系统设置来设置一个设备可被发现的限制时间,或者是一个应用程序可以在不退出的情况下启用设备可被发现的功能。

 

    查询已配对的设备

 

    在执行设备搜寻之前,有必要查询一下已配对的设备中有没有期望连接的那个设备。为了完成这个操作,需要调用getBondsDevices(),这个方法会返回一个代表已配对的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地址属性来初始化连接。在这个例子中,MAC地址被保存在ArrayAdapter中用来显示给用户。MAC地址可以在稍后提取用来初始化连接。

 

    发现新的设备

 

    你可以简单得调用startDiscovery()来开始搜索附近的设备。这是一个异步的线程,它会立即返回一个布尔类型的值表示设备搜索启动成功。这个发射设备的线程通常有一个12秒左右的扫描过程,搜索到之后会在页面上显示搜索到的蓝牙设备的名称。

 

    你的应用程序必须注册一个广播接收器,用于接收通过ACTION_FOUND监听到的发现的设备信息。对每个设备,系统会广播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

     所有这些操作都需要通过BluetoothDevice对象中的MAC地址属性来初始化连接。在这个例子中,MAC地址被保存在ArrayAdapter中用来显示给用户。MAC地址可以在稍后提取用来初始化连接。

 

 

    警告:执行设备发现操作对蓝牙适配器来说是一个繁重的过程,它会消耗很多资源。当你找到一个设备用于连接时,确保在进行连接之前调用cancelDiscovery()方法停止继续进行设备搜索。同样的,在你已经于一个设备建立起链接是,继续进行设备搜索会很大程度得减少连接的可用带宽,影响传输速率,所以,当连接建立起来的时候,应该关闭设备发现操作。

 

    允许设备被发现

 

    如果你想使当前设备可以被其他的蓝牙设备所发现,那么你可以调用startActivityForResult(Intent,int)方法并以ACTION_REQUEST_DISCOVERABLE作为Intent的参数。这个操作会向系统设置发送一个请求打开设备可发现模式(在不停止你的应用程序的情况下)。默认情况下,设备会在120秒内可以被其他设备所发现。你也可以自定义个设备可发现的持续时间,方法是在请求时添加一个EXTRA_DISCOVERY_DURATION 的额外参数到请求中去。可设置的最大持续时间为3600秒,如果设置为0的话,则表示设置设备总是可发现。任何超过0~3600秒的数字会被自动设置成默认的120秒。以下代码为例,设置持续时间为300秒:

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

    执行以上操作后,会弹出一个请求设置设备可发现的对话框,如下图所示。如果你点击了Yes,那么设备将会被设置为在你定义的持续时间内可被发现。你的程序会接收到一个onActivityResult()回调函数的返回值(它的值为你设置设备可被发现的持续时间)。如果你点击了No或者运行中出现了错误,回调函数会返回常量 RESULT_CANCELED。

 

    注意:如果蓝牙功能未被启用,那么开启设备发现功能之后蓝牙功能会被自动启用。

 

    设备将会以静默的方式保持可被发现状态。如果你想要在设备被发现状态改变时进行提示,需要注册一个广播监听器监听ACTION_SCAN_MODE_CHANGED行为。它包括EXTRA_SCAN_MODE和EXTRA_PERVIOUS_SCAN_MODE两个额外的参数,用来告知你新的状态和旧的状态。这两个参数可能的值为SCAN_MODE_CONNECTABLE_DISCOVERY,SCAN_MODE_CONNECTABLE或者SCAN_MODE_NONE,用来表示设备分别出于可被发现状态,不可被发现但是可接受连接状态,既不可以被发现也不可以接受连接状态。

 

    在你初始化两个蓝牙设备的链接是,你并不一定需要启用设备被发现。只有当你需要你的应用程序作为一个Socket服务接受别的设备连接到你的设备上时,你才需要启用设备被发现,因为设备首先需要被别的设备发现才能建立连接。

 

    连接到设备


    为了你的应用程序能够在两个设备之间建立连接,你必须在两端实现服务端和客户端机制,因为一个设备必须打开一个Socket服务,而另一个设备必须初始化连接(通过服务端设备的MAC地址来初始化连接)。当服务端设备和客户端设备在同一个RFCOMM频道中相互拥有一个BluetoothSocket时,被认为是已经连接成功的。在这个时候,设备之间可以开始获取输出输出流和就行数据交互,相关内容将会在”管理一个连接“部分进行讨论。这个部分主要讨论如何在两个设备之间建立起连接。

 

    服务端设备和客户端设备用不同的方式获取到请求到的BluetoothSocket对象。服务端会在介入的连接被接受时收到BluetoothSocket对象。客户端设备则在打开一个和服务端进行连接的RFCOMM频道时接收到。

 

    一个可行的方法是将两个设备都自动预备成服务端,那样各个设备都拥有一个打开的Socket服务可以监听连接的请求。然后其中一个设备发起一个连接到另一个设备上,变成了一个客户端设备。两者中的一个设备作为一个主机根据需求打开了一个Socket服务,另一个设备只是简单地建立起了连接。

 

    注意:如果两个设备从未配对过,那么Android框架会在连接过程中自动显示出一个请求的消息或者对话框给用户,如下图所示。所以在连接到设备的过程中,你的应用程序并不用担心设备是否已经配对。你的RFCOMM连接意图会被限制,直到用户设备配对成功、用户拒绝设备进行配对、设备配对失败或者请求超时。

 

 

    做为服务端设备

 

    当你希望使用蓝牙连接两个设备,那么其中一个必须充当服务端的角色并启用一个打开的BluetoothServerSocket。这个打开的Socket服务是为了监听接入连接的请求,当求情被接受时,提供一个已连接的BluetoothSocket对象。当BluetoothSocket从BluetoothServerSocket被获取到,BluetoothServerSocket就会被废弃,除非你想要接入更多的连接。

 

    下面介绍一下创建一个Socket服务和接受一个连接的基本过程:

 

  1. 调用listenUsingRfcommWithServiceRecord(String,UUIR)方法获取一个BluetoothServerSocket对象。函数中的String参数表示你所用服务的名称,这个名称将被系统自生成一个服务发现协议的条目写入到数据库中(这个名称可以是任意的,你可以简单定义成自己的应用程序名称)。UUID也被包含在了这个服务发现协议数据中,它是连接客户端协议的一个基础。就是说当客户端试图去连接这个设备时,它会携带一个UUID作为唯一的标识码与对应的服务端进行连接。如果想要连接的请求被接受,那么UUID码必许能够匹配。
  2. 调用accept()监听连接请求。这个过程是一个阻塞调用(线程挂起,不能做别的事情)。它只有当一个连接被接受或者出现异常时会返回。只有当远程设备发送的带有UUID的连接请求与服务端Socket服务监听的的注册信息匹配时,连接会被接受。一旦成功建立起连接,accept() 会返回一个已连接的 BluetoothSocket。
  3. 如果你不需要接受其他的设备连接,调用close()。这个操作会释放socket服务以及改socket服务所占用的资源,不过它并不会关闭由accept() 返回的已经连接的BluetoothSocket。与TCP/IP 不同,RFCOMM每个频道同一时间只能允许一个客户端接入,所以大多数情况下,在接受了一个连接的socket之后,建议立即调用BluetoothServerSocket的close()方法。

    accept()方法不能在UI线程中被执行,因为它是一个阻塞调用,会阻止其他任务与应用程序的交互。通常,建议在你的应用程序中建立一个线程去执行BluetoothServerSocket 或BluetoothSocket 下的操作。为了中断一个类似accept()的阻塞调用,可以在另一个线程中调用BluetoothServerSocket 的close() 方法,调用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 并停止了accept() 操作的循环。

 

    注意当accept() 返回BluetoothSocket时,socket是已连接的,所以你不需要再调用connect() 方法(就像你在客户端操作的那样)。

 

    manageConnectedSocket() 是一个程序的一个抽象方法,用于初始化数据交互的线程,具体内容会在“管理连接部分进行介绍”。

 

    通常,你需要在你完成对接入连接的监听之后马上关闭你的BluetoothServerSocket。在上面的例子中,close() 方法在BluetoothSocket被获取后立即被调用。你也可能也需要在你的线程中提供一个公共的方法去关闭私有的BluetoothSocket当你需要停止Socket服务监听的时候。

 

    作为客户端设备

    

     为了与一个远程设备(一个开启着socket服务的设备)新建立一个连接,你必须首先获取一个代表这个蓝牙设备的BluetoothDevice对象。在文章中的“发现设备”部分可以找到关于获取BluetoothDevice 的相关内容。在获取到BluetoothDevice 对象之后,你必须使用这个对象获取到BluetoothSocket 并开始建立连接。

 

    这里介绍一下基本的过程:

 

  1. 使用BluetoothDevice对象。调用BluetoothDevice 的createRfcommSocketToServiceRecord(UUID) 方法来获取对应的BluetoothSocket。这个操作会初始化一个连接到BluetoothDevice 的BluetoothSocket。客户端使用的UUID必须与服务端打开BluetoothServerSocket 时使用的UUID(调用listenUsingRfcommWithServiceRecord(String, UUID)获取到)进行匹配。使用相同的UUID是简单得进行将UUID编码到应用程序中,然后在服务端和客户端代码中进行索引。
  2. 调用connect() 新建连接。在执行这个方法之前,系统会执行一次SDP检索对远程设备进行匹配。如果检索成功且设备接受了连接,那么设备会在连接时共享它的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() 方法会在连接建立之前被调用。在进行连接之前,你需要调用cancelDiscovery() 而不用担心设备搜索是否正在运行,因为它是安全的(不过如果你一定要去检查下的话,可以调用isDiscovery() 来查看)。

 

    manageConnectedSocket() 是一个程序的一个抽象方法,用于初始化数据交互的线程,具体内容会在“管理连接部分进行介绍”。

 

    当你完成对BluetoothSocket的操作后,需要调用close() 方法进行清理。进行这个操作会立即关闭Socket连接并清理所有内部资源。

 

    管理连接


    当你使两个设备(或多个)成功进行连接,每一个设备都会有一个自己的BluetoothSocket。接下去,你就可以用在设备之间进行数据的交互了。使用BluetoothSocket,我们可以很简单得进行任意数据的交互:

 

  1. 通过调用getInputStream() 和 getOutputStream() 获取socket 传输来的inputStream 和outputStream 分别进行处理。
  2. 通过调用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) { }
    }
}

    构造方法获取到所需的数据流,且一旦运行,线程会等待通过InputeStream 传来的数据。当read(byte[]) 方法从数据流放回byte类型的数据时,取到的数据会通过一个父类的一个Handler传到对应的Activity 中进行处理。然后它会回到继续等待数据传进来的状态。

 

    通过在Activity调用write()方法,可以很简单得完成数据的发送。这个方法随后会调用write(byte[]) 方法将这些数据发送到远程设备上。

 

    cancel() 方法是该线程中一个关键的方法,通过这个方法,连接可以通过关闭BluetoothSocket来随时终断连接。这个方法通常在你完成对蓝牙连接的操作之后调用。

 

    蓝牙配置文件的操作


    从Android 3.0开始,蓝牙相关的API开放了对蓝牙配置操作的支持。蓝牙配置是指一个基于蓝牙的,设备之间进行交互的无线接口规范。其中一个例子就是免提的规范(HFP,它定义了蓝牙音频网关设备如何通过蓝牙免提设备拨打和接听电话)。如果一个手机支持连接一个无线耳机,那么手机和耳机都必须支持免提规范。

 

    你可以编写一个自己的类实现BluetoothProfile 接口,用来支持一个特定的蓝牙规范。Android的蓝牙编程接口提供了一下蓝牙规范的实现:

 

  • 蓝牙耳机。耳机规范提供了对手机使用的到的蓝牙耳机的支持。Android提供了BluetoothHeadset 类,这是一个通过进程间通信(IPC)对蓝牙耳机服务进行控制的代理。它包括蓝牙耳机规范和免提规范(v1.5)。BluetoothHeadset还支持AT指令。关于更多AT指令的讨论,可以参考“特定于供应商的AT指令”。
  • A2DP(Advance Audio Distribution Profile)意思为高质量音频分发规范,定义了如何将立体声质量的音频通过流媒体的方式从媒体源传输到接收器上。Android 提供了BluetoothA2dp类,用于通过线程通信控制蓝牙的A2DP规范。
  • 医疗设备。Android 4.0(API Level 14)中介绍了对蓝牙医疗设备规范(HDP)的支持。这点可以帮助你编写出可以通过于支持蓝牙的医疗设备进行交互的应用程序,例如心率检测器、血压监测器、温度计、磅秤等等。支持的设备和其相应的谁呗专有代码,请参考 Bluetooth Assigned Numbers(蓝牙分配编号)。你需要注意是是这些参数在 ISO/IEEE 11073-20601 [7] 规范中的 MDC_DEV_SPEC_PROFILE_* 的命名规范附件中被引用。

    下面介绍一下对蓝牙规范的这些操作:

  1. 获取默认的适配器,如 设置蓝牙 中介绍的那样。
  2. 调用getProfileProxy() 建立一个到配置文件对象的连接。在西面的例子中,配置文件的对象是BluetoothHeadset的一个实例。
  3. 设置一个BluetoothProfile.ServiceListener。它会监听BluetoothProfile 的IPC 客户端与服务端连接或断开连接。
  4. 在onServiceConnected()方法中,获取对配置文件对象的处理的一个handle。
  5. 获取了配置问价对象,你就可以用它来监测连接的状态以及执行其他在配置中的有关操作。

    例子,下面的代码片段展示了如何连接到一个BluetoothHeadset代理对象,你可以操作它控制耳机配置:

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);

 

 

  • 大小: 9.3 KB
  • 大小: 12.4 KB
  • 大小: 7.7 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics