Android Bluetooth

来源:互联网 发布:新西兰导航软件中文 编辑:程序博客网 时间:2024/06/05 02:46
本章内容提要
    1. 概述
    2. Bluetooth 权限
    3. Bluetooth 设置
    4. 查找设备
        (1)查询匹配设置 (2)发现设备
    5. 连接设备
       (1)以 server 方式连接 (2)以 client 方式连接
    6. 管理连接
    7. 使用配置文件

    关键类
        (1)BluetoothAdapter (2)BluetoothDevice
        (3)BluetoothSocket   (4)BluetoothServerSocket

    Android 平台包含对 Bluetooth 网络协议栈的支持,通过 Bluetooth,设备之间可以进行数据的无线传输。应用框架层提供了调用 Bluetooth 的功能的 API 。通过这些 API 可以实现 Bluetooth 设备间的点对点及点对多点的无线通信功能 。
    Bluetooth APIs 提供的主要功能如下:
    1)扫描其它的 Bluetooth 设备;
    2)查询本机 Bluetooth 适配器,以匹配特定的 Bluetooth 设备;
    3)建立 RFCOMM 信道(串口仿真信道);
    4)通过 service discovery 连接到其它设备;
    5)与其它 Bluetooth 设备进行数据交互;
    6)管理多连接;
    本章主要介绍一下传统的 Bluetooth 的用法,传统的 Bluetooth 在进行设备间数据通信时的功耗是比较大的 。为了让 Bluetooth 设备间通信的功耗更低,Android 4.3(API Level 18) 引进了新的 API (下一章我会继续给你们翻译)。

1. 概述
    通过 Bluetooth APIs 通信的步骤可简述为:设置 Bluetooth, 发现设备,连接设备,进行设备间数据通信 。所有可用的 Bluetooth APIs 都放在 android.bluetooth 包里 。下面简要概括一下,在进行 Bluetooth 连接和通信时主要用到的类和接口:
    1)BluetoothAdapter
        代表本地蓝牙适配器(无线蓝牙),BluetoothAdapter 是所有 Bluetooth 交互的入口 。你可以通过它来发现 Bluetooth 设备,查询 Bluetooth 设备列表,使用已知的 MAC 地址初始化一个 BluetoothDevice 实例,并创建一个 BluetoothServerSocket(用来监听来自其它 Bluetooth 设备的连接)。
    2)BluetoothDevice
        代表一个远程的 Bluetooth 设备,BluetoothDevice 可以通过 BluetoothSocket 对远程的 Bluetooth 设备发起连接请求,查询远程 Bluetooth 的名称、地址、class 和绑定状态等 。
    3)BluetoothServerSocket
        代表服务端已经开启的一个 socket,它可以监听其它 Bluetooth 设备的连接请求(类似于 TCP 的 ServerSocket)。要想让两个 Android 能够实现 Bluetooth 连接,其中一个设备必须通过这个类来打开一个 socket(作为 server 端) 。这样当一个远程的 Bluetooth 设备对该设备发起连接请求时,BluetoothServerSocket 就会在连接成功后返回一个 BluetoothSocket 。
    4)BluetoothClass
        该类描述了一个特定 Bluetooth 设备的一般特征和能力,这些相关属性集是只读的 。但是该类描述的属性集并不是绝对可靠的 。
    5)BluetoothProfile
        这是 Bluetooth profile 的接口,可以说一个 Bluetooth profile 是用于描述基于 Bluetooth 通信的设备所要双方所要遵循的协议。
    6)BluetoothA2dp
        定义了高品质的 audio 如何以 streamed 的形式通过 Bluetooth 在不同的设备间传输(A2DP 即 Advanced Audio Distribution Profile)。
    7)BluetoothHealth
        代表一个用于控制 Bluetooth 服务的 Health Device Profile 的代理 。
    8)BluetoothHealthCallback
        这是一个抽象类,可用它来实现 BluetoothHealth 的回调 。你必须扩展该类,并实现相应的回调方法,以接收 app 注册状态的改变及 Bluetooth channel 状态的改变 。
    9)BluetoothHealthAppConfiguration
        用于描述一个已注册的第三方 Bluetooth 应用的配置信息 。
    10)BluetoothProfile.ServiceListener
        这是用于通知 BluetoothProfile IPC 客户端的接口,当设备连接成功或连接断开时,该接口的相应方法就会被系统回调 (系统内部有它自己特定的协议)。

2. Bluetooth Permissions
    如果你要在你的应用中使用 Bluetooth,那么你就得在配置文件中声明使用 BLUETOOTH 权限 。如果你想让你的 app 能够操作 Bluetooth 设置或者想扫描 Bluetooth 设备,那么你就要声明 BLUETOOTH_ADMIN 权限 。注意:如果你使用了 BLUETOOTH_ADMIN 权限,那么你就一定要同时使用 BLUETOOTH 权限 。

3. 设置 Bluetooth
    在使用 Bluetooth 前,你必须进行相应的设置,如下
    
    如果设备支持 Bluetooth 但是被禁用了,那么你可以在不离开 app 的前提下让用户使能 Bluetooth,这可以通过 BluetoothAdapter 来完成,有两步骤
    1)获取 BluetoothAdapter
        对于所有要使用到 Bluetooth 的 activity 来说,BluetoothAdapter 都是必不可少的 。你可以通过 getDefaultAdapter() 方法来获取到 BluetoothAdapter 。该方法会返回设备本身的 Bluetooth adapter 对象 BluetoothAdapter 。在整个系统中,只有一个 Bluetooth adapter,app 可以通过 BluetoothAdapter 对象与设备的 Bluetooth adapter 进行交互 。如果说 getDefaultAdapter() 返回 null,那么说明设备不支持 Bluetooth,如:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
   
// Device does not support Bluetooth
}

    2)Enable Bluetooth
        接下来,你要确认一下 Bluetooth 是否是 enabled,可以通过调用 isEnabled() 方法来检查 Bluetooth 的当前使能状态 。如果该方法返回 false,说明 Bluetooth 处于 disabled 状态。要想使能它,可以调用 startActivityForResult(),在调用时传递一个 action 类型为 ACTION_REQUEST_ENABLE 的 Intent 就行了 。这样系统就会在不关闭你的 app 的情况下,使用 Bluetooth 了,如:
if (!mBluetoothAdapter.isEnabled()) {
   
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult
(enableBtIntent, REQUEST_ENABLE_BT);
}
    这样,系统会弹出一个对话框,要求用户确认是否打开 Bluetooth,就像 Figure 1 一样,你点 yes 就可以了。在 startActivityForResult() 方法中传递的 REQUEST_ENABLE_BT 常量是一个局部的常量,其值必须大于 0,系统会在 onActivityResult() 中返回相应的值,而 REQUEST_ENABLE_BT 就是你用于判定系统返回的是不是 enableBtIntent 请求的返回值 。如果使能成功,activity 会在其 onActivityResult() 中收到 RESULT_OK,如果使能失败(可能是因为出现了什么错误或者用户点击 No),那么返回的是 RESULT_CANCELED 。
    你的 app 还可以监听 ACTION_STATE_CHANGED Intent 广播,每当 Bluetooth 的状态发生改变时,系统都会对这个状态改变进行广播 。该广播消息包含另外的域:EXTRA_STATE 和 EXTRA_PREVIOUS_STATE ,分别指示了 Bluetooth 的新状态和旧状态 。可能还包含的值有  STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFF, and STATE_OFF 。监听这个广播可以让你的 app 在运行过程中知道 Bluetooth 的状态变化 。
    注意:如果你想让设备自动发现 Bluetooth 设备,那么你可以使能 discoverability,这样在你的 activity 运行起来之前,Bluetooth 会被系统使能,这样你就可以跳过上边的第 2 步了,具体如何使能 discoverability,稍后会给你介绍 。

4. 查找设备
    有了 BluetoothAdapter,你就可以通过发现设备或查询匹配的设备列表来找到远程 Bluetooth 了 。发现设备(device discovery)其实就是一个扫描程序,通过扫描可以查找到附近的蓝牙设备,并获取到它们的相关信息(当然前提是这些设备处于 enabled 状态)。但是如果一个 Bluetooth 设备已使能了 discoverable,那么它就只响应 discovery request,在响应这个请求时,它会给出设备名、类别、MAC 地址等信息 。通过这些信息,发出 discovery 请求的设备就可以连接上其中一个已发现的设备了 。
    一旦连接建立起来了,相应设备的信息就会被保存起来,这时就可以通过 Bluetooth 的 APIs 来查询相关的设备的基本信息了(如设备名、类型、MAC 地址等)。通过已知的远程 Bluetooth 设备的 MAC 地址,可以随时与之进行连接(当然前提是远程设备在可连接范围内),而不用再进行 discovery 操作 。
    注意:已匹配(paired)和已连接(connected)是两个不同的概念 。已匹配表示两设备意识到对方的存在,有共享的 link-key ,可建立连接 。而已连接表示两设备已经连接上了对方,它们之间已建立起了 RFCOMM 信道(此信道可用于两设备间的数据传输)。Android Bluetooth 规范要求两设备在建立 RFCOMM 信道前,必须进行匹配(匹配操作会在你通过 Bluetooth APIs 初始化连接时自动进行)。稍后会介绍如何查询已匹配的设备,如何通过 discovery 来发现设备 。

5. 查询已匹配设备
    在进行 device discovery 前,很有必要对已匹配列表进行查询,看一下所要的设备是否已经在这个列表中了 。为了进行查询操作,你可调用 getBondedDevices() ,该方法会返回一个集合 BluetoothDevices,该集合中列出了所有已匹配的设备 。你可以把所有已匹配设备的名字和 MAC 地址逐一放到一个 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 地址连接指定设备 。

6. Discovering devices(发现设备)
    要想发现设备,调用 startDiscovery() 方法就可以了 。该方法是异步的,它会立即返回一个 boolean 值,表明 discovery 操作是否成功启动了 。discovery 进程通常要进行 12 秒左右的扫描,以获取到所有它发现的 Bluetooth 的名字信息 。因为发现设备是个异步操作,所以你的 app 必须注册 action 为 ACTION_FOUND 的 Intent 广播接收器,以接收 discovery 操作所发现的设备的信息 。对于每一个设备的发现,系统都会广播 ACTION_FOUND Intent,该 Inent 携带 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
其实连接一个特定的 Bluetooth 设备,你所需要的仅仅相应设备的 MAC 地址 。
注意:对于 Bluetooth adapter 来说,执行 device discovery 操作是非常耗时耗资源的,所以在扫描时,一旦你发现了你要连接的设备,就应该立即通过 cancelDiscovery() 方法来停止扫描操作,然后才连接你要连接的设备 。如果你的设备已连接上了,就不应该再进行 discovery 操作了,因为这会减少你的带宽,会响应到你当前的连接的通信 。

7. Enabling discoverability(使能 discoverability)
    如果你想让你的 Bluetooth 设备是 discoverable 的,以便让其它设备可以发现它,那么你可以调用 startActivityForResult(Intent, int) 方法,其中 Intent 的 action 类型为 ACTION_REQUEST_DISCOVERABLE 。这样系统会在不停止你的 app 的情况下,为你使能 discoverable 模式 。默认情况下,discoverable 的使能时间为 120秒,你也可以通过 Intent 的指定不同的时间,action 类型为  EXTRA_DISCOVERABLE_DURATION 。可设置的最大的时间值为 3600 秒,如果设置为 0 表示让设备一直处于 discoverable 状态,如果设置的值小于 0 或大于 3600,那么系统会给它设置为 120 秒 。如下代码设置这个时间值为 300 秒:
Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent
.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity
(discoverableIntent);
执行这段代码,会调出如下的设置对话框

    点击 Yes,那么 discoverable 时间就会被设置为程序中所指定的值,相应的 activity 中的onActivityResult() 会被回调,result code 等这个设置的时间值 。如果点击 No 或出错,那么 result code 值为 RESULT_CANCELED 。
    注意:如果设备还没使能 Bluetooth,那么使能 device discoverability 时会自动使能 Bluetooth 。
    设置好 discoverable 时间后,设备就会在指定的时间内处于可被发现状态 。如果你想监听 discoerable 状态的改变,那么你可以注册一个广播接口器(BroadcastReceiver),监听的 Intent 类型为 ACTION_SCAN_MODE_CHANGED 。(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.
如果你只是想让你的设备主动去连接别的设备,那么你就没必要使能 discoverability 属性了 。如果你想让别的设备主动连接你,而不是你主动去连接别的设备,即此时你充当服务器端,那么你就要使能该属性了,这样别的设备才能发现并连接到你 。

8. Connecting Devices(连接设备)
    为了实现两 Bluetooth 设备的连接,你必须同时实现服务器端(server-side)和客户端(client-side)机制,因为其中一个设备必须打开一个 server socket (即充当 server 端),而别一个设备要连接到这个 server socket (即充当 client 端)上,在连接时,要以 server 端的 MAC 地址作为连接参数 。当 server-side 和 client-server 都获取到相同的 RFCOMM 信道的 BluetoothSocket 对象时,它们就真正的连接起来了 。此时,两设备都可获取到各自的 input 和 output streams,从而进行数据通信了,稍后会详细解说如何进行数据通信 。
    server-side 和 client-side 获取 BluetoothSocket 对象的途径和时机是不同的,server-side 会在收到其它设备 的连接请求时获取到这个对象 。而 client-side 会在它向 server-side 打开 RFCOMM 信道后获取到该对象 。


    也可以这样实现:让两个设备都做为 server,这样它们都可以监听连接请求 。当其中一个设备向另一个设备发起连接请求时,请求的发起者即为 client-side,而另一个就是 server-side 了 。这样,设备就可以根据需要确定谁到充当 client,谁来充当 server 了 。
    注意:如果在发起连接的时候,设备还没 paired,那么 Android framework 会自动地弹出一个对话框,(如上 Figure 所示)要求你启动 paired 过程,所以你不必担心在你发起连接的时候,设备还没 paired 。RFCOMM 连接会阻塞等待,直到 paired 成功,如果用户拒绝 pairing 或 pairing 失败,那么 RFCOMM 的建立也会失败 。

9. Connecting as a server
    当你要连接两个设备时,其中一个设备必须充当 server 端,server 持有已开启的 BluetoothServerSocket,server 端的 socket 的主要工作是监听外部发来的连接请求,且当一个连接请求被 accepted 的时候,server 端会给返回一个 BluetoothSocket 。连接成功后,如果 BluetoothServerSocket 再请求 BluetoothSocket ,那么你应该忽略这个请求了,除非你想让它接受更多的连接 。

10. About UUID
    UUID(即 Universally Unique Identifier),它是由 128 位组成的一个用于表示 ID 的字符串,每个 UUID 都是唯一的,所以可以用它来唯一标识不同的信息 。所以 Bluetooth 用它来唯一标识一个 app 的 Bluetooth service 。要想获取一个 UUID,你可以利用 UUID 生成器来生成,然后用 fromString(String) 来对它进行初始化 。下面是设置 server socket 和接受连接的基本过程:
    1)通过 listenUsingRfcommWithServiceRecord(String, UUID) 来获取一个  BluetoothServerSocket  对象 。这里的参数 String 是 service 的名字,可任意取,但是必须是一个合法的字符串,系统会把它写到 SDP(即 Service Discovery Protocol ) 数据库条目中 。UUID 也会被包含到 SDP 条目中,而且它会作为客户端连接的基础 。即当 client 要连接一个设备时,它会携带一个唯一的 UUID 信息,用于指明它想要连接哪个 service 。所以 client 的 UUID 和 server 的必须相互匹配,要不然连接请求是不会被接受的 。
    2)通过调用 accept() 启动对连接请求的监听 。该方法是阻塞的,只有当连接请求被接受或出错时才返回 。只有当远程设备发送的连接请求的 UUID 与注册到该 server socket 上的 UUID 相匹配时,连接请求才会被接受 。连接成功时,accept() 方法会返回一个  BluetoothSocket 对象 。
    3)连接成功后,你应该立即调用 close() 方法 。该方法会释放掉 server socket 持有的资源,但是不会释放掉由 accept() 返回的 BluetoothSocket 对象。和 TCP/IP 不同,RFCOMM 规定每个 channel 在同一时刻只能有一个 client 连接 ,所以当连接成功时,你应该立即调用 BluetoothServerSocket 的  close() 方法 。
    不应该在主线程中调用执行 accept() 方法,因为它是一个阻塞的操作,所以在等待调用返回期间,其它交互都得不到响应 。所以你应该在 app 中开启单独的线程来操作 BluetoothServerSocket or BluetoothSocket 。如果你要中止阻塞的方法调用,如中止  accept(),那么你可以从别的线程中调  BluetoothServerSocket (or BluetoothSocket)  的  close() 方法,这样这个阻塞操作就立即返回了 。注意:所有在 BluetoothServerSocket or BluetoothSocket 的方法都是线程安全的 。

例子,该例子演示了一个简单的 server 端如何接受它接到的连接请求
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 对象,app 就会把 BluetoothSocket 对象发给一个独立的线程,然后关闭 BluetoothServerSocket 并退出 while 循环 。
    注意当 accept() 返回  BluetoothSocket 的时候,socket 就已经连接好了,所以你不要再调用 connect() 方法了(在 client-side 也一样)。上边的 manageConnectedSocket() 方法只是一个虚构的方法,可在该方法中初始化用于进行数据交互的线程 。

11. Connecting as a client
    为了初始化与一个远程设备的连接(即与持有 server socket 设备的连接),你必须先获取到代表远程设备的 BluetoothDevice 对象 。然后再通过这个对象获取到一个 BluetoothSocket 并初始化连接 。下面是基本的实现流程:
    1)通过调用 BluetoothDevice 对象的 createRfcommSocketToServiceRecord(UUID) 获取一个 BluetoothSocket 对象 。调用时传入的 UUID 必须和 server device 打开 BluetoothServerSocket 时(即调用 listenUsingRfcommWithServiceRecord(String, UUID) 时)所用的 UUID 是相匹配的 。
    2)通过调用 connect() 初始化连接 。在执行这个调用时,系统会执行一次对 SDP 的查询,确认是否有匹配的 UUID 。如果查询成功且远程设备接受这个连接请求,那么 RFCOMM 信道就建立起来了, connect() 也就返回了  。该方法的调用是阻塞式的,如果因为任何原因导致连接失败或者连接超时(大约 12 秒),那么它就会抛出一个异常 。由于  connect() 方法是阻塞的,所以该方法的执行应该被放到单独的一个线程中执行,而不是放到 UI 线程中执行 。
    注意:在你进行 connect() 调用的同时,你必须保证设备不执行 discovery 操作,否则 connect() 操作就非常慢,而且连接很可能失败 。

例子,下面是在单独的线程中初始化 Bluetooth 连接的一个示例程序
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() 方法是在连接进行之前调用的,你得确保在连接前调用这个方法,而且你也不用检查它是不是真的在执行 discovery 操作(如果你非要检查,那可调用  isDiscovering()来实现)。其中的 manageConnectedSocket()方法是一个虚构的方法,在实现时,可在该方法中初始化用于处理数据交互的线程,具体处理方法稍后再议 。当你完成连接时,记得调用  close() 以关闭连接,释放资源 。

12. Managing a Connection
    当你成功地把两个设备连接起来时,每个设备都会有一个已连接的 BluetoothSocket 对象,这样你就可以通过这两对象进行设备间的数据交互了 。通过 BluetoothSocket 进行任意数据交互的一般过程如下:
    1)通过 getInputStream() 和  getOutputStream() 获取 socket 的 InputStream  和 OutputStream ,这两个 Stream 都是用来处理 socket 的数据传输的(一个处理输入流,一个处理输出流)。
    2)通过  read(byte[]) 和 write(byte[]) 对 streams 进行读和写操作 。
    整个基本就是这样了,简单吧?
    当然,有很多实现细节是需要好好考虑的,首先,因为  read(byte[]) and write(byte[])  方法都是阻塞的,所以你得在单独的专用的线程中来进行这些操作 。read(byte[]) 方法会一直阻塞,直到它读取到数据为止,实际上 write(byte[]) 方法不会真的阻塞,但是如果远程的接收端来不及读取数据,导致接收缓冲区满了,那么 write(byte[]) 还是会被阻塞的 。

例子,下面这个例子简单演示一下上边讨论的内容:
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) { }
   
}
}
For a demonstration of using the Bluetooth APIs, see the Bluetooth Chat sample app.

13. Working with Profiles
    从 Android 3.0 开始,Buletooth API 引入了对 Bluetooth profiles 的支持,Bluetooth profiles 定义了基于蓝牙的应用之间的通信规范(每个 profile 规范主要包括针对开发者的接口、消息的格式和标准、使用蓝牙协议栈的组件等)。如 Hands-Free profile,它定义了手机连接到蓝牙耳机的规范,所以只有两者都遵循这个规范,才能进行通信 。
    你可以通过实现 BluetoothProfile 接口来定义你自己的通信规范类,Android Bluetooth API 提供了一些已经定义好的 Bluetooth profiles 类,如下:
    1)Headset:Headset 规范定义了手机与蓝牙耳机之间的通信规范,Android 提供了  BluetoothHeadset 类,该类是通过 IPC 通信控制蓝牙耳机服务(Bluetooth Headset Service)的代理类 ,它包含了 Bluetooth Headset 和 Hands-Free(v1.5) 规范 。BluetoothHeadset 还包含了对 AT commands 的支持 (For more discussion of this topic, seeVendor-specific AT commands)。
    2)A2DP:即 Advanced Audio Distribution Profile A2DP 定义了高品质的 audio 如何以 streamed 的形式从一个设备传输到另一个设备上 。Android 提供了 BluetoothA2dp 代理类,该类通过 IPC 机制控制 Bluetooth A2DP  Service 。
    3)Health Device:Android 4.0 (API level 14) 引入了对 HDP(Bluetooth Health Device Profile) 的支持,你可以通过此类与支持蓝牙的健康设备(如心跳监视器、体温器等)进行通信 。关于所有已支持的设备及相关数据参数,请查询 Bluetooth Assigned Numbers at www.bluetooth.org 。(For more discussion of HDP, see Health Device Profile)
    使用一个规范的基本流程如下:
    1)获取默认的 adapter;
    2)通过 getProfileProxy() 建立与特定 profile 相关联的 profile proxy 对象的连接 。在下面的这个例子中,这个 profile proxy 对象是一个  BluetoothHeadset 类的实例 ;
    3)设置监听器 BluetoothProfile.ServiceListener, 这个监听器会在连接建立成功或连接断开时通知  BluetoothProfile IPC 客户端 ;
    4)在 onServiceConnected() 方法中,获取 profile proxy 对象的句柄;
    5)当你获取 profile proxy 对象的引用后,你就可以监视连接的状态,也可以执行与 profile 相关的其它操作 。
    如下的代码片段演示了如何建立与  BluetoothHeadset proxy 对象的连接,进而可以控制 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);
14. Vendor-specific AT commands
    从 Android 3.0 开始,app 可以向系统注册监听由 headsets 发出的与 AT commands 相关的广播 。如一个 app 可以接收指示已连接外部设备电量信息的广播,这样 app 就可以告知用户这个外部设备的电量了 。为 broadcast receiver 增加属性为 ACTION_VENDOR_SPECIFIC_HEADSET_EVENT 的 intent,就可以监听到与 headset 发出的 AT commands 相关的广播了 。

15. Health Device Profile
    Android 4.0 (API level 14) 引入了对 HDP( Bluetooth Health Device Profile)的支持 。这样,app 就可以与支持 Bluetooth 的 health devices(健康设备,如心跳监视器等)进行通信了 。与之相关的 API 类有 BluetoothHealth, BluetoothHealthCallback, and BluetoothHealthAppConfiguration 
    在使用 Bluetooth Health API 的前,理解一下关于 HDP 的相关概念是很有帮助的:
    1)Source:这是 HDP 中的一种角色,一个 source 代表一个健康设备,source 可收集相关的医疗数据(如体重、血糖、体温等),并且可以把这些医疗数据发送给指定的智能设备,如 Android phone 或平板电脑等 ;
    2)Sink:也是 HDP 中的一种角色,表示接收医疗数据的智能设备,在一个 Android 的 HDP app 中, BluetoothHealthAppConfiguration 对象就代表一个 sink ;
    3)Registration:指把一个 sink 注册到一个指定的 health device 上;
    4)Connection:指打开一个 health 设备和智能设备(如 Android phone 或 tablet)之间的通信信道 。

16. Creating an HDP Application
    创建一个 Android HAP app 的基本流程如下:
    1)获取到 BluetoothHealth 代理对象的引用 。和 headset、A2DP profile 设备类似,你必须通过 BluetoothProfile.ServiceListener 的 getProfileProxy() 方法(profile 类型为 HEALTH)与 profile proxy 对象建立连接;
    2)创建一个 BluetoothHealthCallback 对象,并注册一个 app configuration(BluetoothHealthAppConfiguration),让它充当 health sink 角色;
    3)建立与 health 设备的连接;
    4)当成功地与 health 设备连接上后,就可以通过 file descriptor(文件描述符,Linux 把所有设备都抽象成文件)来 read/write health 设备的数据了;对于接收的数据,需要由遵循了IEEE 11073-xxxxx specifications 标准 health manager 来解析;
    5)当数据交互完成后,要关闭掉通信信道,unregister app 。

For a complete code sample that illustrates these steps, see Bluetooth HDP (Health Device Profile))
0 0