Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 429|回复: 0

[实例教程]Android-*Dev Guide-*Bluetooth

[复制链接]

该用户从未签到

发表于 2011-10-22 12:45:31 | 显示全部楼层 |阅读模式
蓝牙具有point-to-point 和 multipoint两种连接功能。使用蓝牙API,可以做到:

.搜索蓝牙设备

.从本地的Bluetooth adapter中查询已经配对的设备。

.建立RFCOMM通道。

.通过service discovery连接到其它设备。

.在设备之间传输数据。

管理多个连接。



1.The Basics



使用蓝牙进行设备通信,主要包含四个部分:蓝牙设置、搜索设备(配对的或可见的)、连接、传输数据。实现这些功能主要需要下面这几个类:



.BluetoothAdapter

代表本地蓝牙适配器(蓝牙发射器),是所有蓝牙交互的入口。通过它可以搜索其它蓝牙设备,查询已经配对的设备列表,通过已知的MAC地址创建BluetoothDevice,创建BluetoothServerSocket监听来自其它设备的通信。



.BluetoothDevice

代表一个远程的蓝牙设备。通过它可以与远程设备通过BluetoothSocket建立连接,查询远程设备属性。



.BluetoothSocket

.BluetoothServerSocket

.BluetoothClass

描述了一个设备的特性(profile)或该设备上的蓝牙大致可以提供哪些服务(service),但不可信。比如,设备是一个电话、计算机或手持设备;设备可以提供audio/telephony服务等。可以用它来进行一些UI上的提示。

要获取可靠的信息,需要进行SDP请求。当使用createRfcommSocketToServiceRecord(UUID) 或 listenUsingRfcommWithServiceRecord(String, UUID)创建一个RFCOMM socket时,SDP请求会自动进行。



2.Bluetooth Permissions



至少要在AndroidManifest.xml中声明两个权限:BLUETOOTH(任何蓝牙相关API都要使用这个权限) 和 BLUETOOTH_ADMIN(设备搜索、蓝牙设置等)。要请求BLUETOOTH_ADMIN的话,必须要先有BLUETOOTH。



3.Setting Up Bluetooth



程序中使用蓝牙之前,应该先确定蓝牙设备是否存在、蓝牙是否已经被打开。蓝牙设备不存在的话,程序要正常退出。蓝牙存在但没有打开的话,要请求用户打开蓝牙。



.确定蓝牙设备是否存在

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

if (mBluetoothAdapter == null) {

    // Device does not support Bluetooth

}



.打开蓝牙

通过isEnabled()检查蓝牙是否打开,没有打开的话,通过下面的方法弹出对话框提示用户打开。

if (!mBluetoothAdapter.isEnabled()) {

    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);

    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);

}



成功的话,会在onActivityResult()这个回调方法中收到RESULT_OK,否则就是RESULT_CANCELED。



除了通过onActivityResult(),还可以通过监听ACTION_STATE_CHANGED这个broadcast Intent来知道蓝牙状态是否改变。这个Intent包含EXTRA_STATE,EXTRA_PREVIOUS_STATE两个字段,分别代表新旧状态。可能的值是STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFF, 还有STATE_OFF。



另外,enable()也会打开蓝牙,但不会通知用户。



还有,让蓝牙设备可见,也会自动打开蓝牙。



4.Finding Devices



Device discovery是指对本地设备进行扫描的过程,只有把自己设置为可见的设备才能被扫描到。如果设备把设备为可见的话,它会共享设备名字、class、MAC地址等信息。使用这些信息,才可以进行连接。

当第一次连接到一个设备S时,时,S会要求配对,配对成功后,才能获取S的设备名字、class、MAC地址等信息。

通过远程设备的MAC地址,可以与之通信,但设备如果正在进行扫描的话,就不行了。

设备配对是指两个设备之间有一个动态密钥用于认证和传输加密数据。设备连接是指两个设备建立一个RFCOMM通信可以用于数据。Android中,设备配对后才能连接。

Android设备默认不可见,用户可以设置可见性和可见时间。



4.1 Querying paired devices



在进行扫描之前,可以通过getBondedDevices()(会返回已经配对过的设备集合)看一下要寻找的设备是否已经配对过了。

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

    }

}

连接一个设备只需要一个MAC地址。所以,如果这个集合中已经有了你想要的设备,直接取出MAC地址连接就可以了。



4.2 Discoverying devices



调用startDiscovery()开始进行设备搜索。扫描过程是这样的。先进行大约12秒的扫描,然后对扫描到的每个设备查询名字。

要接收扫描结果需要注册一个BroadcastReceiver接收ACTION_FOUND Intent。这个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
设备扫描很费资源,一旦找到你需要的设备,应该马上调用cancelDiscovery()取消扫描。

如果已经连接到远程设备,再扫描时会减少连接带宽。所以,最好不要这么干。



4.3 Enabling discoverability



想要设备可见的话,像下面这样做。然后通过onActivityResult()用户选择接收结果。下面的代码会弹出对话框让用户选择。

默认情况下,设备可见时间是120秒。如果要改变这个时间的话,为Intent添加一个extra数据EXTRA_DISCOVERABLE_DURATION。

Intent discoverableIntent = new

Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);

discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);

startActivity(discoverableIntent);







想在设备可见性改变时得到通知的话,可以注册一个BroadcastReceiver来接收ACTION_SCAN_MODE_CHANGED这个Intent。这个Intent的extra有两个EXTRA_SCAN_MODE和EXTRA_PREVIOUS_SCAN_MODE,分别代表新旧状态。它们的值是SCAN_MODE_CONNECTABLE_DISCOVERABLE, SCAN_MODE_CONNECTABLE, 或SCAN_MODE_NONE其中的一个。

只有想让其它设备主动连接你的时候,才有必要使设备可见。



5.Connecting Devices



为了在两台设备上创建一个连接,你必须在软件上实现服务器端和客户端的机制,因为一个设备必须必须打开一个server socket,而另一个必须初始化这个连接(使用服务器端设备的MAC地址进行初始化)。



当服务器端和客户端在同一个RFCOMM信道上都有一个BluetoothSocket时,就可以认为它们之间建立了一个连接。在这个时刻,每个设备能获得一个输出流和一个输入流,也能够开始数据传输。本节介绍如何在两个设备之间初始化一个连接。



服务器端和客户端获得BluetoothSocket的方法是不同的,服务器端是当一个进入的连接被接受时才产生一个BluetoothSocket,客户端是在打开一个到服务器端的RFCOMM信道时获得BluetoothSocket的。







一种实现技术是,每一个设备都自动作为一个服务器,所以每个设备都有一个server socket并监听连接。然后每个设备都能作为客户端建立一个到另一台设备的连接。另外一种代替方法是,一个设备按需打开一个server socket,另外一个设备仅初始化一个到这个设备的连接。



Note: 如果两个设备在建立连接之前并没有配对,那么在建立连接的过程中,Android框架将自动显示一个配对请求的notification或者一个对话框,如Figure 3所示。所以,在尝试连接设备时,你的应用程序无需确保设备之间已经进行了配对。你的RFCOMM连接将会在用户确认配对之后继续进行,或者用户拒绝或者超时之后失败。



5.1 Connecting as a server



当你想要连接两个设备时,其中一个必须保持一个打开的BluetoothServerSocket,作为服务器。服务器socket将监听进入的连接请求,一旦连接被接受,将产生一个BluetoothSocket。



About UUID

一个Universally Unique Identifier(UUID)是一个字符串ID的标准化128位格式,将被用于唯一标识信息。你可以使用web上的任何一款UUID产生器为你的程序获取一个UUID,然后使用fromString(String)初始化一个UUID。你可以用UUID来标识你的蓝牙服务。



这儿是展示设置一个server socket并接受一个连接的基础过程:



调用listenUsingRfcommWithServiceRecord(String, UUID)获得一个BluetoothServerSocket。

字符串参数是你的服务的标识名,系统将自动将这个标识名写到设备上一个新的Service Discovery Protocol(SDP)数据库条目中(这个标识名可以简单地作为你的程序的名字,也就是说可以把你的程序的名字作为命名)。UUID也会被包含在这个新的SDP条目中,并作为与客户端设备建立连接的基础。 That is, when the client attempts to connect with this device, it will carry a UUID that uniquely identifies the service with which it wants to connect. These UUIDs must match in order for the connection to be accepted (in the next step).



调用accept()开始监听连接请求

这是一个阻塞调用。当一个连接被接受或者发生异常时将返回。一个连接,仅当一个远程设备发出的请求包含UUID匹配正在监听的server socket所注册的UUID时被接受。成功的时候,accept()将返回一个连接的BluetoothSocket。



除非你要接受另外一个连接,否则调用close,关闭server socket。

这将释放server socket和它占用的所有资源,但不要用来关闭accept返回的已连接的BluetoothSocket。不像TCP/IP,RFCOMM仅允许一个信道在某一时刻有一个连接的客户端。所以,创建了一个连接的socket之后立即调用close()来关闭BluetoothServerSocket。



accept()方法不应该在主Activity UI线程中执行,因为它是一个阻塞调用,如果在主Activity UI线程中条也能够将会阻止与用户的交互。一般使用BluetoothServerSocket或者BluetoothSocket进行相关工作时都是在一个新的线程中。为了避免调用诸如accept()这样的阻塞调用,针对来自其他线程的BluetoothServerSocket或者BluetoothSocket调用close()将会使阻塞调用立即返回。注意,针对BluetoothServerSocket或者BluetoothSocket调用的方法都是线程安全的,也就是说可以在多个线程中使用。



5.2 示例 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 = mAdapter.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) { }  

    }  

}  
manageConnectedSocket()用于初始化传输数据的线程。

另外,你接收到一个连接并建立起BluetoothSocket后,通常应该关掉BluetoothServerSocket。



5.3 Connecting as a client



要连接到远程设备,可以按下面几步进行:

.通过BluetoothDevice的createRfcommSocketToServiceRecord(UUID)方法获取到BluetoothSocket。其中,UUID必须与远程设备的UUID匹配。

.调用BluetoothSocket.connect()方法。调用这个方法时,会在远程设备上做SDP查询,看UUID是否匹配。一切OK的话,会建立RFCOMM通道。connect()阻塞这时才会返回。如果失败,或者connect超时(一般是12秒),会抛出异常。

在connect()前,一定确保扫描结束,否则会很慢,而且容易失败。



5.4 Example



这个例子演示了如何连接到远程设备 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

        mAdapter.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) { }

    }

}
  1. private class ConnectedThread extends Thread {
  2.     private final BluetoothSocket mmSocket;
  3.     private final InputStream mmInStream;
  4.     private final OutputStream mmOutStream;
  5.     public ConnectedThread(BluetoothSocket socket) {
  6.         mmSocket = socket;
  7.         InputStream tmpIn = null;
  8.         OutputStream tmpOut = null;
  9.         // Get the input and output streams, using temp objects because
  10.         // member streams are final
  11.         try {
  12.             tmpIn = socket.getInputStream();
  13.             tmpOut = socket.getOutputStream();
  14.         } catch (IOException e) { }
  15.         mmInStream = tmpIn;
  16.         mmOutStream = tmpOut;
  17.     }
  18.     public void run() {
  19.         byte[] buffer = new byte[1024];  // buffer store for the stream
  20.         int bytes; // bytes returned from read()
  21.         // Keep listening to the InputStream until an exception occurs
  22.         while (true) {
  23.             try {
  24.                 // Read from the InputStream
  25.                 bytes = mmInStream.read(buffer);
  26.                 // Send the obtained bytes to the UI Activity
  27.                 mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
  28.                         .sendToTarget();
  29.             } catch (IOException e) {
  30.                 break;
  31.             }
  32.         }
  33.     }
  34.     /* Call this from the main Activity to send data to the remote device */
  35.     public void write(byte[] bytes) {
  36.         try {
  37.             mmOutStream.write(bytes);
  38.         } catch (IOException e) { }
  39.     }
  40.     /* Call this from the main Activity to shutdown the connection */
  41.     public void cancel() {
  42.         try {
  43.             mmSocket.close();
  44.         } catch (IOException e) { }
  45.     }
  46. }
复制代码
上面代码很简单,值得一提的是cancel()。任何时候都可以通过关闭Bluetooth来关闭连接。这个方法是可以从主线程里面调用的。



Android源码自带了一个Bluetooth Chat程序,可以参考。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

GMT+8, 2025-1-22 16:57 , Processed in 0.371819 second(s), 46 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表