最近一直在做与bluetooth相关的应用。主要涉及android手机蓝牙的多连接问题。网上几乎没有与蓝牙多连接相关的具体实现资料,所以我开始从android的官方文档入手,大半夜的一个人坐下面看那英文文档,真TMD不是滋味,现在回想下当年做的英语阅读理解真似一坨shit。不过功夫不负有心人,终于搞清楚了它的构架和通信模式。这里我先讲bluetooth的基本操作,然后再深入讲解它的多连接问题(大家期待的重头戏)。注意:我这里主要讲的是多连接的核心实现,至于蓝牙的一些基础操作,我只是简单的介绍。如果有不懂的可以参考其他资料。我也做了一个测试Demo,里面的代码基本参考的官方文档,若有疑问可以去官网上看看。我试过一次可以连接三个手机。当然这不一定是极限数据,因为设备有限。有条件的朋友可以修改下代码,做下压力测试。
Demo代码下载:http://download.csdn.net/detail/wangwang6233/7188881
官方文档:http://developer.android.com/guide/topics/connectivity/bluetooth.html
参考博客:http://zhouyunan2010.iteye.com/blog/1186021
流程:
(1)蓝牙的介绍,相关API使用说明,使用蓝牙的准备工作。
(2)蓝牙的开启和关闭。
(3)设置设备可被搜索。
(4)搜索设备及广播接收器的注册。
(5)蓝牙的配对。
(6)蓝牙的连接服务端和客户端
(7)蓝牙的多连接操作。
讲解:
(1)蓝牙的介绍,相关API使用说明,使用蓝牙的准备工作。
蓝牙,是一种支持设备短距离通信(一般10m内)的无线电技术。理论上一个蓝牙设备可以连接7个蓝牙设备(我没试过,只是理论上)。首先可行性是没问题的。其他蓝牙信息我就不阐述了,大家问度娘吧!在android app上使用bluetooth时需在AndroidManifest.xml中加上权限:
<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permissionandroid:name="android.permission.BLUETOOTH" />
我简单说下相关的API类,和常用的方法。
BluetoothAdapter:顾名思义,蓝牙适配器,蓝牙的打开、关闭、搜索都和它有关BluetoothAdapter.getDefaultAdapter()获取。
BluetoothDevice:看名字就知道,这个类是描述一个蓝牙设备,从它可以获取蓝牙的地址和设备名getAddress(),getName()。并且蓝牙设备有三种状态BOND_BONDED、BOND_BONDING、BOND_NONE分别是已绑定,绑定中、未绑定。
BluetoothServerSocket:这是服务端,通过accept()返回BluetoothSocket。既然是Socket相信大家都再熟悉不过了吧!如果你不太清楚socket编程,那就先去看java基础吧!这里我也不能偏题。
BluetoothSocket:这是客户端,connect()与服务端进行连接。通过它回去输入输出流。
(2)蓝牙的开启和关闭。
打开蓝牙:
private void startBluetooth() {
if (mBluetoothAdapter == null) {
// 表明此手机不支持蓝牙
Log.d(TAG, "device is not supported bluebooth");
return;
}
if (!mBluetoothAdapter.isEnabled()) { // 蓝牙未开启,则开启蓝牙
mBluetoothAdapter.enable();
}
}
关闭蓝牙:
if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
mBluetoothAdapter.disable();
}
(3)
设置设备可被搜索。
// 使本机蓝牙在300秒内可被搜索
private void ensureDiscoverable() {
if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
Intent discoverableIntent = new Intent(
BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(
BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);//设置被发现时间
startActivity(discoverableIntent);
}
}
(4)
搜索设备及广播接收器的注册。
记得在onCreate()中注册,在onDestory()中unregisterReceiver(searchDevices);这是android广播机制的基础,不懂的可以回去看看android广播基础了。
private void register2Broadcast() {
IntentFilter intent = new IntentFilter();
intent.addAction(BluetoothDevice.ACTION_FOUND);// 用BroadcastReceiver来取得搜索结果
intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
intent.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(searchDevices, intent);
}
private BroadcastReceiver searchDevices = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent
.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//addDevice2List(device); 获取BluetoothDevice后剩下的自己处理了
}
}
};
(5)蓝牙的配对。
这里我们用到的是java的反射机制。
private void paireDevice(BluetoothDevice device) {
try {
Method createBondMethod = BluetoothDevice.class
.getMethod("createBond");
createBondMethod.invoke(device);
} catch (Exception e) {
// TODO: handle exception
e.getStackTrace();
}
}
(6)蓝牙的连接服务端和客户端
服务端:这里我声明了四个BluetoothSocket。因为accept()是阻塞操作,一旦连接成功就会返回BluetoothSocket,然后继续等待下一个连接。如此下去我们就实现了多连接。UUID是作为服务端的标识。获得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(
"eric",
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
} catch (IOException e) {
}
mmServerSocket = tmp;
}
public void run() {
// Keep listening until exception occurs or a socket is returned
while (true) {
try {
Log.d(TAG, "thread is start and accept");
if (socket == null) {
socket = mmServerSocket.accept();
Log.d(TAG, "accept ok");
// If a connection was accepted
if (socket != null) {
// Do work to manage the connection (in a separate
// thread)
Log.d(TAG, "manageConnectedSocket");
manageConnectedSocket(socket);//相关处理函数
// break;
}
}
if (socket_two == null) {
Log.d(TAG, "wait two");
socket_two = mmServerSocket.accept();
Log.d(TAG, "accept ok");
// If a connection was accepted
if (socket != null) {
// Do work to manage the connection (in a separate
// thread)
Log.d(TAG, "manageConnectedSocket");
manageConnectedSocket(socket_two);
}
}
if (socket_three == null) {
socket_three = mmServerSocket.accept();
Log.d(TAG, "accept ok");
// If a connection was accepted
if (socket != null) {
// Do work to manage the connection (in a separate
// thread)
Log.d(TAG, "manageConnectedSocket");
manageConnectedSocket(socket_three);
}
}
if (socket_four == null) {
socket_four = mmServerSocket.accept();
Log.d(TAG, "accept ok");
// If a connection was accepted
if (socket != null) {
// Do work to manage the connection (in a separate
// thread)
Log.d(TAG, "manageConnectedSocket");
manageConnectedSocket(socket_four);
break;
}
}
} catch (IOException e) {
break;
}
}
}
/** Will cancel the listening socket, and cause the thread to finish */
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) {
}
}
private void manageConnectedSocket(BluetoothSocket socket) {
if (socket!=null) {
readThread read = new readThread(socket);
read.start();
}
}
}
客户端:其中的UUID要与服务端的UUID相对应。同理connect()也是阻塞操作。连接成功后即返回BluetoothSocket。
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(UUID
.fromString("00001101-0000-1000-8000-00805F9B34FB"));
} 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
Log.d(TAG, "thread is start and connect");
mmSocket.connect();
Log.d(TAG, "connect ok");
Message msg=mHandler.obtainMessage();
msg.what=3;
msg.sendToTarget();
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
Log.d(TAG, "connect throw expection");
try {
mmSocket.close();
} catch (IOException closeException) {
}
return;
}
// Do work to manage the connection (in a separate thread)
socket=mmSocket;
if(socket!=null){
manageConnectedSocket(socket);
}
}
/** Will cancel an in-progress connection, and close the socket */
public void cancel() {
try {
socket.close();
} catch (IOException e) {
}
}
private void manageConnectedSocket(BluetoothSocket socket) {
readThread read = new readThread(socket)//开启读取数据线程;
read.start();
Log.d(TAG, "the read is start");
}
}
数据读入代码:这里我们另起一个线程,当有输入流时就进行获取。这里我们与Handler一起使用,这样就可以在界面及时更新数据。
// 读取数据
private class readThread extends Thread {
private BluetoothSocket socket;
public readThread(BluetoothSocket socket) {
this.socket = socket;
}
public void run() {
byte[] buffer = new byte[1024];
int bytes;
InputStream mmInStream = null;
try {
mmInStream = this.socket.getInputStream();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
while (flag) {
try {
Log.d(TAG, "get inputstream");
// Read from the InputStream
if ((bytes = mmInStream.read(buffer)) > 0) {
byte[] buf_data = new byte[bytes];
for (int i = 0; i < bytes; i++) {
buf_data[i] = buffer[i];
}
String s = new String(buf_data);
Log.d(TAG, s);
Message msg = new Message();
msg.what = 2;
msg.obj = s;
mHandler.sendMessage(msg);
}
} catch (IOException e) {
try {
mmInStream.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
break;
}
}
}
}
发送数据(写入输出流)
这里可能有人不懂为什么这么麻烦。我这写的是服务端群发数据。所有有连接的客户端都能收到。
private void sendMessage(){
String s = ed_message.getText().toString()+"";
if (socket == null) {
Log.d(TAG, "socket is not connect");
} else {
try {
OutputStream os = socket.getOutputStream();
os.write(s.getBytes());
Log.d(TAG, "write to outputstream success,socket");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (socket_two == null) {
Log.d(TAG, "socket_two is not connect");
} else {
try {
OutputStream os = socket_two.getOutputStream();
os.write(s.getBytes());
Log.d(TAG, "write to outputstream success,socket_two");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (socket_three == null) {
Log.d(TAG, "socket_three is not connect");
} else {
try {
OutputStream os = socket_three.getOutputStream();
os.write(s.getBytes());
Log.d(TAG, "write to outputstream success,socket_three");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (socket_four == null) {
Log.d(TAG, "socket_four is not connect");
} else {
try {
OutputStream os = socket_four.getOutputStream();
os.write(s.getBytes());
Log.d(TAG, "write to outputstream success,socket_four");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
(7)蓝牙的多连接操作。
代码已在(6)中贴出来了,这里我就解释一下,这里所谓的多连接就是一个服务端和多个客户端。重点就是在服务端一个accept()成功获取BluetoothSocket后,我们要继续监听下一个BluetoothSocket,就像我为什么声明了四个BluetoothSocket(socket,socket_two,socket_three,socket_four);其实没有什么技巧,只要你熟悉socket编程。多试试就OK了。大家可以下载测试Demo进行多次调试。多看看就懂了,而且我的代码还有许多可以改进的地方,大家可以互相讨论学习,路总是人走出来了,如果总吃现成的就永远得不到创新。我这里仅仅做个启发。
Demo代码下载:http://download.csdn.net/detail/wangwang6233/7188881