今天周末,天气很好,换换心情,试着写个技术博文,欢迎各种喷,多喷无罪~呵呵
第一个项目是关于Android间的Bluetooth通信,也是新入门,现在写写心得。
关于Bluetooth网上有很多的介绍,有些是分析源码有些是分析应用层的API,而我接下来是分析应用层上面上的,也就是如何调用API来快速开发基于Bluetooth的应用。
首先,蓝牙通信类似于HTTP的socket通信。类似于B/S通信,需要“套接字”。然而在Android的Bluetooth开发中需要以下几个步骤:
Bluetooth Permissions->Setting Up Bluetooth->Finding Devices->Querying paired devices->Discovering devices->Connecting Devices->Managing a Connection;
第一步就不说,直接在配置文件添加权限即可;
下面介绍第二步,Setting Up Bluetooth,首先要获取BluetoothAdapter(这个可以理解为是用来控制蓝牙设备的工具类),然后通过这个“工具类”检查是否开启蓝牙设备,使用
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
通过这个,会开启一个对话框,让用户选择是否开启蓝牙设备,然后会自动返回到应用程序界面。
Figure 1: The enabling Bluetooth dialog.
注意:当要设置开启暴露蓝牙模式时,蓝牙会自动开启而无需用户手动开启。(Tip: Enabling discoverability will automatically enable Bluetooth. To start discovering devices, simply call startDiscovery())
好了,现在到了第三步了,Finding Devices:发现设备;(为甚么要做这个,因为Android中,会使用这个设备来开启通信也就是用这个BluetoothDeviecs获取一个socket,所以需要这个BluetoothDeviecs)
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());
}
}
注意:
1、在搜索设备之前要开启蓝牙设备,在搜索得到结果之后记得将暴露的蓝牙设备关闭掉(设置为不暴露状态),否则会占用大量的带宽影响传输效率。
2、还有一个要注意的是,开启蓝牙暴露模式是一个异步过程,因为开启之后它会自动搜索周边的蓝牙设备这个过程大概需要12秒,因此不仅需要异步线程,还需要对搜索的结果进行处理,这个就需要到注册广播来解决了,一旦搜索到结果系统及发送一个广播,因此这里还学要到广播的使用。
这个系统发出的广播包含了: a BluetoothDevice and a 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
Caution: Performing device discovery is a heavy procedure for the Bluetooth adapter and will consume a lot of its resources. Once you have found a device to connect, be certain that you always stop discovery with cancelDiscovery() before attempting a connection. Also, if you already hold a connection with a device, then performing discovery can significantly reduce the bandwidth available for the connection, so you should not perform discovery while connected.
上面的警告大概就是说,在连接之前一定要关闭掉蓝牙的暴露,否则会严重影响通信的效率。
在暴露管理方面你可以用以下方法进行管理:
Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);//其中 最后一个参数300表示的是300秒之后关闭蓝牙暴露。
startActivity(discoverableIntent);
接下来到连接设备,这一步比较重要,也有些区别。区别在于主动连接与被动连接,也就是常说的是客户端还是服务器端B/S模式。他们的连接设置略有不同。
具体如下:
服务器端:
1、得到一个服务器端的服务接口。通过使用这个方法得到,listenUsingRfcommWithServiceRecord(String, UUID).第一个参数是用来给系统生成一个 (SDP) 协议所需的,你可以自定义为应用程序的名称,而第二个参数就不能乱写了,必须为你客户端的UUID一致。UUID(A Universally Unique Identifier (UUID) is a standardized 128-bit format for a string ID used to uniquely identify information. )一个128位的唯一标识符。
2、监听。通过使用accept()开始监听客户端发来的请求。
3、得到连接套接字。但客户端请求得成功后会返回一个套接字,于此同时一般当建立连接后关闭监听。否则会消耗掉大量的资源,因为蓝牙是分时复用,每一个时刻只允许一个客户端连接。除非你想要与多个客户端通信。但是不要关闭连接即可。
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) { }
}
}
需要注意的是,监听需要开启一个新的线程,否则可能会造成主线程的阻塞。
客户端:
1、首先使用BluetoothDevice获取得到一个BluetoothSocket(其实可以理解为一个Socket端口),很简单,使用createRfcommSocketToServiceRecord(UUID)其中这个UUID就是你程序中的那个128位的唯一标识码。为什么需要这个UUID?因为服务器需要用这个参数来得到一个服务器端的端口,也需要这个UUID来创建一个客户端端口以给客户端使用。这个UUID必须在服务器端进行注册才可以否则会匹配失败。(A connection is accepted only when a remote device has sent a connection request with a UUID matching the one registered with this listening server socket.)
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) { }
}
}
到这里基本算是成功了,因为接下来的跟普通的HTTP通信的差不多了,通过socket得到输入输出流,然后对输入输出流进行相关的读写操作即可完成蓝牙之间的数据通信了。但是需要注意的是,这个读操作可能会使线程阻塞掉,因为读操作需要等待数据的到来,因此一般情况下读写操作都需要开启新的线程。
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) { }
}
}
好了,到此处完成了对蓝牙的基本通信实现了。本人抱着学习的态度,欢迎各大神进行指正或讨论,故此处应有评论 (^o^)/~ :)