A​n​d​r​o​i​d​ ​B​l​u​e​t​o​o​t​h​详​解(Android英文文档相关译文)

时间:2021-06-17 02:04:15
一、Bluetooth

Android平台包含了对Bluetooth协议栈的支持,允许机器通过Bluetooth设备进行无线数据交换。应用框架通过Android Bluetooth API访问Bluetooth功能模块。 这些API能让应用无线连接其他Bluetooth设备,实现点对点和多点之间的通信。 运用蓝牙API,Android应用程序可以完成如下操作:

1、扫描其他Bluetooth设备。

2、查询配对Bluetooth设备的本地Bluetooth适配器。

3、建立RFCOMM通道。

4、通过服务探索连接到其他设备。

 5、与其他设备进行数据传输。

6、管理多个连接

二、The Basics
本文描述如何使用Android Bluetooth APIs完成Bluetooth通讯的4个必要任务:设置Bluetooth,搜寻本地配对或者可用的Bluetooth设备,连接Bluetooth设备,与Bluetooth设备进行数据传输。
所有可用的Bluetooth APIs都包含在android.bluetooth包中。下面是建立Bluetooth连接需要用到的类和接口的总结:

1、BluetoothAdapter
描述本地Bluetooth适配器(Bluetooth接收器)。BluetoothAdapter是所有Bluetooth相关活动的入口。运用BluetoothAdapter可以发现其他Bluetooth设备,查询连接(或配对)的设备列表,用已知MAC地址实例化一个BluetoothDevice对象,创建一个BluetoothServerSocket对象侦听其他设备的通信。
2、BluetoothDevice
描述一个远程Bluetooth设备。可以用它通过一个BluetoothSocket请求一个远程设备的连接,或者查询远程设备的名称、地址、类、连接状态等信息。
3、BluetoothSocket
描述一个Bluetooth Socket接口(类似于TCP Socket)。应用通过InputStream和OutputStream.与另外一个Bluetooth设备交换数据,即它是应用与另外一个设备交换数据的连接点。
4、BluetoothServerSocket
描述一个开放的socket服务器,用来侦听连接进来的请求(类似于RCP ServerSocket)。为了连接两个Android设备,一个设备必须使用该类来开启一个socket做服务器,当另外一个设备对它发起连接请求时并且请求被接受时,BluetoothServerSocket会返回一个连接的BluetoothSocket对象

5、BluetoothClass
描述一个Bluetooth设备的一般规格和功能。这个是用来定义设备类和它的服务的只读属性集。然而,它并不是可靠的描述设备支持的所有Bluetooth配置和服务,而只是一些设备类型的有用特征。
6、BluetoothProfile
描述Bluetooth Profile的接口。Bluetooth Profile是两个设备基于蓝牙通讯的无线接口描述。 (对Bluetooth Profile的详细解释,来自百度:为了更容易的保持Bluetooth设备之间的兼容,Bluetooth规范中定义了 Profile。Profile定义了设备如何实现一种连接或者应用,你可以把Profile理解为连接层或者应用层协议。 比如,如果一家公司希望它们的Bluetooth芯片支援所有的Bluetooth耳机,那么它只要支持HeadSet Profile即可,而无须考虑该芯片与其它Bluetooth设备的通讯与兼容性问题。如果你想购买Bluetooth产品,你应该了解你的应用需要哪些Profile来完成,并且确保你购买的Bluetooth产品支持这些Profile。)

7、BluetoothHeadset
提供移动电话的Bluetooth耳机支持。包括Bluetooth耳机和Hands-Free (v1.5) profiles。
8、BluetoothA2dp
定义两个设备间如何通过Bluetooth连接进行高质量的音频传输。 A2DP(Advanced Audio Distribution Profile):高级音频传输模式。
9、BluetoothProfile.ServiceListener
一个接口描述,在与服务连接或者断连接的时候通知BluetoothProfile IPC(这是内部服务运行的一个特定的模式<profile>)。
三、Bluetooth Permissions
要使用Bluetooth功能,至少需要2个Bluetooth权限:BLUETOOTH和BLUETOOTH_ADMIN.
BLUETOOTH:用来授权任何Bluetooth通信,如请求连接,接受连接,传输数据等。 BLUETOOTH_ADMIN:用来授权初始化设备搜索或操作Bluetooth设置。大多数应用需要
它的唯一场合是用来搜索本地Bluetooth设备。本授权的其他功能不应该被使用,除非是需要修改Bluetooth设置的“power manager(电源管理)”应用。
注意:需要BLUETOOTH_ADMIN权限的场合,BLUETOOTH权限也是必需的。 需要在manifest文件中声明Bluetooth权限,示例如下: <manifest ... > 
  <uses-permission android:name="android.permission.BLUETOOTH" />    ...  </manifest>
四、Setting Up Bluetooth
在用Bluetooth通讯之前,需要确认设备是否支持Bluetooth,如果支持,还得确保Bluetooth是可用的。
如果设备不支持Bluetooth,需要优雅的将Bluetooth置为不可用。如果支持Bluetooth,但没有开启,可以在应用中请求开启Bluetooth。该设置使用BluetoothAdapter.通过两个步骤完成
1、获取BluetoothAdapter
BluetoothAdapter是每个Bluetooth的Activity都需要用到的。用静态方法getDefaultAdapter()获取BluetoothAdapter,返回一个拥有Bluetooth 适配器的
BluetoothAdapter对象。如果返回null,说明设备不支持Bluetooth,关于Bluetooth的故事到此就结束了(因为你干不了什么了)。示例:

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

if (mBluetoothAdapter == null) {

// Device does not support Bluetooth

}
2、Enable Bluetooth
接下来,就是确保Bluetooth功能是开启的。调用isEnabled()来检查Bluetooth当前是否是开启的。用ACTION_REQUEST_ENABLE action Intent调用startActivityForResult()来请求开启Bluetooth,这会通过系统设置发出一个Bluetooth使能请求(并且不会停止本应用程序)。示例:
if (!mBluetoothAdapter.isEnabled()) {     Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);     
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);

}
    用户请求使能Bluetooth时,会显示一个对话框。选择“Yes”,系统会使能Bluetooth,并且焦点会返回你的应用程序。
    如果使能Bluetooth成功,你的Activity会在onActivityResult()回调函数中收到RESULT_OK的结果码。如果Bluetooth使能因发生错误(或用户选择了“No”)而失败,收到的结果码将是RESULT_CANCELED。
    作为可选项,应用也可以侦听ACTION_STATE_CHANGED broadcast Intent,这样无论Bluetooth状态何时被改变系统都会发出broadcast(广播)。该广播包含附加的字段信息EXTRA_STATE和EXTRA_PREVIOUS_STATE分别代表新的和旧的Bluetooth状态,该字段可能的值为STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFF, 和STATE_OFF。应用运行时,侦听ACTION_STATE_CHANGED广播来检测Bluetooth状态的改变是很有用的。
    提示:启用Bluetooth可被发现功能能够自动开启Bluetooth。如果在完成Activity之前需要持续的使能Bluetooth可被发现功能,那么上面的第2步就可以忽略。

焦点会返回你的应用程序。
如果使能Bluetooth成功,你的Activity会在onActivityResult()回调函数中收到RESULT_OK的结果码。如果Bluetooth使能因发生错误(或用户选择了“No”)而失败,收到的结果码将是RESULT_CANCELED。
作为可选项,应用也可以侦听ACTION_STATE_CHANGED broadcast Intent,这样无论Bluetooth状态何时被改变系统都会发出broadcast(广播)。该广播包含附加的字段信息EXTRA_STATE和EXTRA_PREVIOUS_STATE分别代表新的和旧的Bluetooth状态,该字段可能的值为STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFF, 和STATE_OFF。应用运行时,侦听ACTION_STATE_CHANGED广播来检测Bluetooth状态的改变是很有用的。
提示:启用Bluetooth可被发现功能能够自动开启Bluetooth。如果在完成Activity之前需要持续的使能Bluetooth可被发现功能,那么上面的第2步就可以忽略。
五、Finding Devices
    使用BluetoothAdapter可以通过设备搜索或查询配对设备找到远程Bluetooth设备。 Device discovery(设备搜索)是一个扫描搜索本地已使能Bluetooth设备并且从搜索到的设备请求一些信息的过程(有时候会收到类似“discovering”,“inquiring”或“scanning”)。但是,搜索到的本地Bluetooth设备只有在打开被发现功能后才会响应一个discovery请求,响应的信息包括设备名,类,唯一的MAC地址。发起搜寻的设备可以使用这些信息来初始化跟被发现的设备的连接。
    一旦与远程设备的第一次连接被建立,一个pairing请求就会自动提交给用户。如果设备已配对,配对设备的基本信息(名称,类,MAC地址)就被保存下来了,能够使用Bluetooth API来读取这些信息。使用已知的远程设备的MAC地址,连接可以在任何时候初始化而不必先完成搜索(当然这是假设远程设备是在可连接的空间范围内)。 需要记住,配对和连接是两个不同的概念:
    配对意思是两个设备相互意识到对方的存在,共享一个用来鉴别身份的链路键(link-key),能够与对方建立一个加密的连接。
    连接意思是两个设备现在共享一个RFCOMM信道,能够相互传输数据。

    目前Android Bluetooth API's要求设备在建立RFCOMM信道前必须配对(配对是在使用Bluetooth API初始化一个加密连接时自动完成的)。 下面描述如何查询已配对设备,搜索新设备。
    注意:Android的电源设备默认是不能被发现的。用户可以通过系统设置让它在有限的时间内可以被发现,或者可以在应用程序中要求用户使能被发现功能。

    1、Querying paired devices
在搜索设备前,查询配对设备看需要的设备是否已经是已经存在是很值得的,可以调用getBondedDevices()来做到,该函数会返回一个描述配对设备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地址。
    2、Discovering devices
    要开始搜索设备,只需简单的调用startDiscovery()。该函数时异步的,调用后立即返回,返回值表示搜索是否成功开始。搜索处理通常包括一个12秒钟的查询扫描,然后跟随一个页面显示搜索到设备Bluetooth名称。
    应用中可以注册一个带CTION_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

警告:完成设备搜索对于Bluetooth适配器来说是一个重量级的处理,要消耗大量它的资源。一旦你已经找到一个设备来连接,请确保你在尝试连接前使用了cancelDiscovery()来停止搜索。同样,如果已经保持了一个连接的时候,同时执行搜索设备将会显著的降低连接的带宽,所以在连接的时候不应该执行搜索发现。
3、Enabling discoverability
如果想让本地设备被其他设备发现,可以带ACTION_REQUEST_DISCOVERABLE action Intent调用startActivityForResult(Intent, int) 方法。该方法会提交一个请求通过系统刚设置使设备出于可以被发现的模式(而不影响应用程序)。默认情况下,设备在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);
询问用户是否允许打开设备可以被发现功能时会显示一个对话框。如果用户选择“Yes”,设备会在指定时间过后变为可以被发现的。Activity的onActivityResult()回调函数被调用,结果码等于设备变为可以被发现所需时长。如果用户选择“No”或者有错误发生,结果码会是Activity.RESULT_CANCELLED。
提示:如果Bluetooth没有启用,启用Bluetooth可被发现功能能够自动开启Bluetooth。 在规定的时间内,设备会静静的保持可以被发现模式。如果想在可以被发现模式被更改时受到通知,可以用ACTION_SCAN_MODE_CHANGED Intent注册一个BroadcastReceiver,包含额外的字段信息EXTRA_SCAN_MODE和EXTRA_PREVIOUS_SCAN_MODE分别表示新旧扫描模式,其可能的值为SCAN_MODE_CONNECTABLE_DISCOVERABLE(discoverable mode),SCAN_MODE_CONNECTABLE(not in discoverable mode but still able to receive connections),SCAN_MODE_NONE(not in discoverable mode and unable to receive connections)。
如果只需要连接远程设备就不需要打开设备的可以被发现功能。只在应用作为一个服务器socket的宿主用来接收进来的连接时才需要使能可以被发现功能,因为远程设备在初始化连接前必须先发现了你的设备。

六、Connecting Devices
    为了建立两个设备之间的应用的连接,需要完成服务器端和客户端,因为一个设备必须打开一个服务器socket而另外一个设备必须初始化连接(用服务器端的MAC地址)。服务器和客户端在各自获得一个基于同一个RFCOMM信道的已连接的BluetoothSocket对象后就被认为连接已经建立。这个时候,双方设备可以获取输入输出流,数据传输可以开始了。本节描述如何在两个设备之间初始化连接。
    服务器设备和客户端设备用不同的方式获取各自需要的BluetoothSocket对象。服务器端的在接收一个进来的连接时获取到,客户端的在打开一个与服务器端的RFCOMM信道的时候获取到。
    一个实现技巧是自动把每个设备作为服务器,这样就拥有了一个打开的socket用来侦听连接。然后任一设备就能够发起与另一个设备的连接,并成为客户端。另外,一个设备也可以明确的成为“host”,并打开一个服务端socket,另一个设备可以简单的发起连接。 注意:如果两个设备之前没有配对,那么在连接处理过程中Android应用框架会自动显示一个配对请求的通知或对话框给用户。因此,当尝试连接设备时,应用不需要关心设备是否已经配对。RFCOMM连接会阻塞直到用户成功将设备配对(如果用户拒绝配对或者配对超时了连接会失败)
1、Connecting as a server
如果要连接两个设备,其中一个必须充当服务器,通过持有一个打开的
BluetoothServerSocket对象。服务器socket的作用是侦听进来的连接,如果一个连接被接受,提供一个连接好的BluetoothSocket对象。从BluetoothServerSocket获取到
BluetoothSocket对象之后,BluetoothServerSocket就可以(也应该)丢弃了,除非你还要用它来接收更多的连接。
下面是建立服务器socket和接收一个连接的基本步骤:
1.通过调用listenUsingRfcommWithServiceRecord(String, UUID)得到一个BluetoothServerSocket对象。
该字符串为服务的识别名称,系统将自动写入到一个新的服务发现协议(SDP)数据库接入口到设备上的(名字是任意的,可以简单地是应用程序的名称)项。 UUID也包括在SDP接入口中,将是客户端设备连接协议的基础。也就是说,当客户端试图连接本设备,它将携带一个UUID用来唯一标识它要连接的服务,UUID必须匹配,连接才会被接受。 2.通过调用accept()来侦听连接请求。
这是一个阻塞的调用,知道有连接进来或者产生异常才会返回。只有远程设备发送一个连接请求,并且携带的UUID与侦听它socket注册的UUID匹配,连接请求才会被接受。如果成功,accept()将返回一个连接好的BluetoothSocket对象。 3.除非需要再接收另外的连接,否则的话调用close()。
close()释放server socket和它的资源,但不会关闭连接accept()返回的连接好的
BluetoothSocket对象。与TCP/IP不同,RFCOMM同一时刻一个信道只允许一个客户端连接,因此大多数情况下意味着在BluetoothServerSocket接受一个连接请求后应该立即调用close()。
accept()调用不应该在主Activity UI线程中进行,因为这是个阻塞的调用,会妨碍其他的交互。经常是在在一个新线程中做BluetoothServerSocket或BluetoothSocket的所有工作来避免UI线程阻塞。注意所有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()返回BluetoothSocket后,socket已经连接了,所以在客户端不应该呼叫connnect()。
manageConnectedSocket()是一个虚方法,用来初始化线程好传输数据。
通常应该在处理完侦听到的连接后立即关闭BluetoothServerSocket。在本例中,close()在得到BluetoothSocket后马上被调用。还需要在线程中提供一个公共的方法来关闭私有的BluetoothSocket,停止服务端socket的侦听。

2、Connecting as a client
为了实现与远程设备的连接,你必须首先获得一个代表远程设备BluetoothDevice对象。然后使用BluetoothDevice对象来获取一个BluetoothSocket来实现来接。
下面是基本的步骤:
1.用BluetoothDevice调用createRfcommSocketToServiceRecord(UUID)获取一个BluetoothSocket对象。
这个初始化的BluetoothSocket会连接到BluetoothDevice。UUID必须匹配服务器设备在打开BluetoothServerSocket 时用到的UUID(用
listenUsingRfcommWithServiceRecord(String, UUID))。可以简单的生成一个UUID串然后在服务器和客户端都使用该UUID。 2.调用connect()完成连接
当调用这个方法的时候,系统会在远程设备上完成一个SDP查找来匹配UUID。如果查找成功并且远程设备接受连接,就共享RFCOMM信道,connect()会返回。这也是一个阻塞的调用,不管连接失败还是超时(12秒)都会抛出异常。
注意:要确保在调用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()在连接操作前被调用。在连接之前,不管搜索有没有进行,该调用都是安全的,不需要确认(当然如果有要确认的需求,可以调用isDiscovering())。 manageConnectedSocket()是一个虚方法,用来初始化线程好传输数据。
在对BluetoothSocket的处理完成后,记得调用close()来关闭连接的socket和清理所有的内部资源。
七、Managing a Connection
如果已经连接了两个设备,他们都已经拥有各自的连接好的BluetoothSocket对象。那就是一个有趣的开始,因为你可以在设备间共享数据了。使用BluetoothSocket,传输任何数据通常来说都很容易了

1.通过socket获取输入输出流来处理传输(分别使用getInputStream()和getOutputStream())。
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) { }      }  }
构造函数中得到需要的流,一旦执行,线程会等待从InputStream来的数据。当read(byte[])返回从流中读到的字节后,数据通过父类的成员Handler被送到主Activity,然后继续等待读取流中的数据。
向外发送数据只需简单的调用线程的write()方法。

线程的cancel()方法时很重要的,以便连接可以在任何时候通过关闭BluetoothSocket来终止。它应该总在处理完Bluetooth连接后被调用。
八、Working with Profiles
从Android 3.0开始,Bluetooth API就包含了对Bluetooth profiles的支持。Bluetooth profile是基于蓝牙的设备之间通信的无线接口规范。例如Hands-Free profile(免提模式)。如果移动电话要连接一个无线耳机,他们都要支持Hands-Free profile。
你在你的类里可以完成BluetoothProfile接口来支持某一Bluetooth profiles。Android Bluetooth API完成了下面的Bluetooth profile:
Headset:Headset profile提供了移动电话上的Bluetooth耳机支持。Android提供了BluetoothHeadset类,它是一个协议,用来通过IPC(interprocess communication)控制Bluetooth Headset Service。BluetoothHeadset既包含Bluetooth Headset profile也包含Hands-Free profile,还包括对AT命令的支持。
A2DP:Advanced Audio Distribution Profile (A2DP) profile,高级音频传输模式。Android提供了BluetoothA2dp类,这是一个通过IPC来控制Bluetooth A2DP的协议。 下面是使用profile的基本步骤: 1.获取默认的Bluetooth适配器

2.使用getProfileProxy()来建立一个与profile相关的profile协议对象的连接。在下面的例子中,profile协议对象是BluetoothHeadset的一个实例。
3.设置BluetoothProfile.ServiceListener。该listener通知BluetoothProfile IPC客户端,当客户端连接或断连服务器的时候。
4.在onServiceConnected()内,得到一个profile协议对象的句柄。
5. 一旦拥有了profile协议对象,就可以用它来监控连接的状态,完成于该profile相关的其他操作。
例如,下面的代码片段显示如何连接到一个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);
1、Vendor-specific AT commands
从Android 3.0开始,应用程序可以注册侦听预定义的Vendor-specific AT命令这样的系统广播(如Plantronics +XEVENT command)。例如,应用可以接收到一个广播,该广播表明连接的设备电量过低,然后通知用户做好其他需要的操作。创建一个带
ACTION_VENDOR_SPECIFIC_HEADSET_EVENT intent的broadcast receiver来为耳机处理
Vendor-specific AT commands。