权限
有两种权限,至少需要一种权限
BLUETOOTH
:执行任何蓝牙通信,例如请求一个连接、接受一个连接以及传输数据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_CHANGED
intent其中 extra fieldsEXTRA_SCAN_MODE
and EXTRA_PREVIOUS_SCAN_MODE
表示之前状态和现在状态
连接设备
为了在两个设备建立一个连接,需要实现server和clint的接口,一个设备打开server socker,另一个设备开始连接(用server的mac地址连接),当他们在同一个RFCOMM频道上建立了BluetoothSocket
连接时,则认为他们连接上了。此时设备可以获取输入输出流可以开始传输数据,本节描述了两台设备之间初始化连接。
server和client获取BluetoothSocket
的方式不同。server在接受连接是会得到BluetoothSocket。client在向服务器端打开一个RFCOMM通道时会得到BluetoothSocket。
server端连接
当你希望连接两台设备,一台设备必须作为server持有并打开BluetoothServerSocket
,为了监听接受的连接请求,当一个请求接受后,提供一个BluetoothSocket
,当该BluetoothSocket
被BluetoothServerSocket
请求时,BluetoothServerSocket
可以(而且应当)被舍弃
下面是创建一个服务器socket并且接受一个连接的基本过程:
- 通过调用
listenUsingRfcommWithServiceRecord(String, UUID)
获取一个BluetoothServerSocket
string 是独一无二的你的server名,系统将会把它写入设备中的一个新的服务发现协议(SDP)数据库条目中(名字是任意的,并且可以只是你应用的名字)。UUID同样被包含在SDP条目中,并且将会成为和客户端设备连接协议的基础。也就是说,当客户端尝试连接这个设备时,它将会携带一个UUID用于唯一指定它想要连接的服务器。这些UUIDs必须匹配以便该连接可以被接受(在下一步中)。 - 通过调用accept()开始监听连接请求。
这一个阻塞调用。在一个连接被接受或一个异常出现时,它将会返回。只有当一个远程设备使用一个UUID发送了一个连接请求,并且该UUID和正在监听的服务器socket注册的UUID相匹配时,一个连接才会被接受。成功后,accept() 将会返回一个已连接的BluetoothSocket
。 - 调用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
,然后初始化该连接。
下面是基本的过程:
- 通过调用createRfcommSocketToServiceRecord(UUID)来得到一个 BluetoothSocket 。
- 通过调用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。此时就可以在不同的设备之间共享数据了!
- 分别通过getInputStream() 和 getOutputStream()来得到
InputStream
和OutputStream
来控制socket之间的传输。 - 使用 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