蓝牙学习

2015/7/1 posted in  Android

权限

有两种权限,至少需要一种权限

  1. BLUETOOTH:执行任何蓝牙通信,例如请求一个连接、接受一个连接以及传输数据
  2. BLUETOOTH_ADMIN:初始化设备查找或控制蓝牙设置

如果你使用BLUETOOTH_ADMIN权限,那么必须拥有BLUETOOTH权限。

在manifest文件中声明权限

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

设置蓝牙

在使用蓝牙和其他设备通信之前,要确保本地设备支持蓝牙,并处于打开的状态。

获取BluetoothAdapter

使用getDefaultAdapter方法来获取,如果获取的为null则表示设备不支持蓝牙。

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

开启蓝牙

isEnabled()来检测蓝牙是否开启,若没用使用startActivityForResult()ACTION_REQUEST_ENABLE来启动一个activity来提示用户启动蓝牙

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

执行完后,会有一个对话框出现,提示打开蓝牙,如果用户点击 是,系统则会打开蓝牙,返回打开成功/失败的状态

onActivityResult中接受蓝牙打开的状态

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    //resultCode 是之前startActivityForResult中的REQUEST_ENABLE_BT
    Log.d(TAG, "requestCode:" + requestCode + "\n resultCode: " + resultCode);
    if ((requestCode==RESULT_OK)||(requestCode==RESULT_FIRST_USER)) {
        Log.d(TAG, ";蓝牙开启成功");
    }else {
        Log.d(TAG, ";蓝牙开启失败");
    }
}

查找设备

使用BluetoothAdapter发现远程蓝牙设备或者本地已绑定的设备
设备查找是一个扫描周围蓝牙设备的过程,在此过程中,周围的蓝牙设备会回应一些信息,例如设备名,类,mac地址等。利用这些信息,发起扫描的设备可以选择一个设备进行连接

一旦第一次连接发起时,配对请求会自动呈现给用户,当设备配对后,设备的基本信息(设备名,mac地址)会被保存,而且可以使用蓝牙的API接口读出来,使用已知的mac地址,不用扫描,连接可以随时建立。
配对不等同于连接。配对表示两个设备知道彼此的存在,连接表示两个设备共享一个RFCOMM可以互相发送数据,在进行数据发送之前,设备必须配对。

查询已配对的设备

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

扫描设备

 
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());
                Log.d(TAG, 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

SCAN需要BLUETOOTH_ADMIN权限

可被查找

弹出一个对话框,用户点允许则可被发现

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

如过蓝牙未被开启,用户点击可被发现时,蓝牙被自动开启。

discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); 最后一个参数是可被查找的时间。设置为0,为一直可被发现。
可被查找状态改变时希望获取状态可以有以下实现方式:
注册一个ACTION_SCAN_MODE_CHANGEDintent其中 extra fieldsEXTRA_SCAN_MODEand EXTRA_PREVIOUS_SCAN_MODE表示之前状态和现在状态

连接设备

为了在两个设备建立一个连接,需要实现server和clint的接口,一个设备打开server socker,另一个设备开始连接(用server的mac地址连接),当他们在同一个RFCOMM频道上建立了BluetoothSocket连接时,则认为他们连接上了。此时设备可以获取输入输出流可以开始传输数据,本节描述了两台设备之间初始化连接。

server和client获取BluetoothSocket的方式不同。server在接受连接是会得到BluetoothSocket。client在向服务器端打开一个RFCOMM通道时会得到BluetoothSocket。

server端连接

当你希望连接两台设备,一台设备必须作为server持有并打开BluetoothServerSocket,为了监听接受的连接请求,当一个请求接受后,提供一个BluetoothSocket当该BluetoothSocketBluetoothServerSocket请求时,BluetoothServerSocket可以(而且应当)被舍弃

下面是创建一个服务器socket并且接受一个连接的基本过程:

  1. 通过调用 listenUsingRfcommWithServiceRecord(String, UUID)获取一个BluetoothServerSocket
    string 是独一无二的你的server名,系统将会把它写入设备中的一个新的服务发现协议(SDP)数据库条目中(名字是任意的,并且可以只是你应用的名字)。UUID同样被包含在SDP条目中,并且将会成为和客户端设备连接协议的基础。也就是说,当客户端尝试连接这个设备时,它将会携带一个UUID用于唯一指定它想要连接的服务器。这些UUIDs必须匹配以便该连接可以被接受(在下一步中)。
  2. 通过调用accept()开始监听连接请求。
    这一个阻塞调用。在一个连接被接受或一个异常出现时,它将会返回。只有当一个远程设备使用一个UUID发送了一个连接请求,并且该UUID和正在监听的服务器socket注册的UUID相匹配时,一个连接才会被接受。成功后,accept() 将会返回一个已连接的 BluetoothSocket
  3. 调用close(),除非你想要接受更多的连接。
    这将释放服务器socket和它所有的资源,但是不会关闭 accept()返回的已连接的 BluetoothSocket。不同于TCP/IP,RFCOMM仅仅允许每一个通道上在某一时刻只有一个已连接的客户端,因此在大多数情况下在接受一个已连接的socket后,在BluetoothServerSocket上调用 close() 是非常必要的。

accept() 不应该再主活动UI线程上执行,因为它是一个阻塞调用,并且将会阻止任何与应用的交互行为。它通常在你的应用管理的一个新的线程中使用一个BluetoothServerSocket 或 BluetoothSocket 来完成所有工作。为了中止一个阻塞调用,例如accept(),从你的其他线程里在BluetoothServerSocket (或 BluetoothSocket) 上调用 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) { }
    }
}

注意当accept()返回一个 BluetoothSocket是,该socket已经被连接了,因此你不应该调用connect().
manageConnectedSocket()是一个虚构的方法,它将会初始化线程来传输数据,下下节讨论。
当监听到了外来的连接时,通常是关闭BluetoothServerSocket。在这个例子里, 一旦得到了 BluetoothSocket 就会调用close()。

客户端连接

为了和一个远程设备(一个持有服务器socket的设备)初始化一个连接,你必须首先得到一个 BluetoothDevice 对象来表示这个远程设备。(上面的课程Finding Devices讲述了如何得到一个 BluetoothDevice )。然后你必须使用BluetoothDevice来得到一个BluetoothSocket ,然后初始化该连接。
下面是基本的过程:

  1. 通过调用createRfcommSocketToServiceRecord(UUID)来得到一个 BluetoothSocket 。
  2. 通过调用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()。

管理一个连接

当你成功连接两个(或更多)设备后,每一个都将有一个已连接的 BluetoothSocket。此时就可以在不同的设备之间共享数据了!

  1. 分别通过getInputStream() 和 getOutputStream()来得到 InputStreamOutputStream 来控制socket之间的传输。
  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[]) 返回一些数值时,数据将会使用一个父类的一个成员变量句柄发送给主活动。然后它返回并继续等待更多的数据。

发送数据只需要在主活动中调用线程的 write()方法,并将需要发送数据传递给它即可。这个方法然后调用 write(byte[])来向远程设备发送数据。

线程的cancel() 方法是重要的,以便连接可以在任何时间被中断(通过关闭BluetoothSocket)。这个方法应该在你完成蓝牙连接后总是被调用。

蓝牙sample的例子 :http://developer.android.com/samples/index.html

使用配置文件

待续
参考

http://developer.android.com/guide/topics/connectivity/bluetooth.html
http://blog.csdn.net/candycat1992/article/details/8577927