话说自己今年的清明节过的很带劲,总结起来就是:清明时节雪纷纷,码农编程欲断魂。虽然有很多事情需要做,但是哥们找我帮忙,我还是腾出一天多的时间帮忙写了一个APP,主要以蓝牙为基础实现主要功能,短时间内也只是实现了界面显示切换和主要功能。虽然去年写过类似的APP,但是后来不幸手欠格式化硬盘时忘了备份,而且今年再用谷歌的蓝牙开发源码貌似更新了一些细节,今年用起来连接流畅多了,但是去年什么样子老衲也是无从记起了。
为了以后在遇到蓝牙开发方便,我决定写一下这次的理解。
本文主要讨论谷歌提供的BluetoothChatService类(个人认为这才是官方文档里最亮的星)。
首先,你要问操作系统要一个BluetoothAdapter,即本地蓝牙适配器,通俗点解释它就好比是你手里的手机,有了手机你才可以拨号、打电话等等与手机有关的功能。有了这个适配器你就可以发现其他蓝牙设备,获取已配对的蓝牙列表,让你的设备可以被发现等等功能。我们需要用一行代码来获取它。
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
这行代码出现在BluetoothChatService的构造函数中
虽然构造函数中大家会看到还有一个handler在这里扎眼,大家可以先认为它就相当于一个信鸽,将产生的消息传递给activity,handler不觉明历的话,它的机制和作用大家可以自己去搜搜,很多帖子讲的不错,这里就不在赘述了。
再往下看,大家会注意到start、connect、connected、stop等几个成员方法,细心的朋友会注意到,这几个方法的中心都是围绕相应三个内部线程类的操作,是的,本类的精华就在于这三个内部线程类,且听老衲一一道来。
AcceptThread:服务端的线程
先说一下这个UUID,我们可以理解为电话号码(你给我打电话,咱们俩得有通话的唯一标识),这个UUID就像是蓝牙相互连接的“电话号码”,有了这个唯一标识,电话才能打得出去,接收的到。
学过网络socket编程的朋友,还记的第一次学socket的时候,服务器端的socket有一个accept()方法吗?这个类就是实现服务端的接收操作的,run方法里面调用了蓝牙socket的accept方法,让你的“手机”可以被“打通”。然后如果是连接中的状态就可以继续管理连接了。
public void run() {
setName("AcceptThread" + mSocketType);
BluetoothSocket socket = null;
// Listen to the server socket if we're not connected
while (mState != STATE_CONNECTED) {
try {
// This is a blocking call and will only return on a
// successful connection or an exception
socket = mmServerSocket.accept();
} catch (IOException e) {
break;
}
// If a connection was accepted
if (socket != null) {
synchronized (BluetoothChatService.this) {
switch (mState) {
case STATE_LISTEN:
case STATE_CONNECTING:
// Situation normal. Start the connected thread.
connected(socket, socket.getRemoteDevice(),
mSocketType);
Log.e("connected:","accept");
break;
case STATE_NONE:
case STATE_CONNECTED:
// Either not ready or already connected. Terminate new socket.
try {
socket.close();
} catch (IOException e) {
}
break;
}
}
}
}
}
ConnectThread:客户端的线程
说完服务端,就到了客户端,同样它也少不了UUID(显然,一厢情愿是谈不了“恋爱”的,需要两情相悦)。值得注意的是,务必保证两端的UUID是一样的!!!
首先run方法停掉了蓝牙可以发现,这是因为一边被发现,一边去连接可能会减慢连接,甚至导致失败。随后调用蓝牙socket的connect方法连接服务端连接成功后开始管理连接。
public void run() {
setName("ConnectThread" + mSocketType);
// Always cancel discovery because it will slow down a connection
mAdapter.cancelDiscovery();
// Make a connection to the BluetoothSocket
try {
// This is a blocking call and will only return on a
// successful connection or an exception
mmSocket.connect();
} catch (IOException e) {
// Close the socket
try {
mmSocket.close();
} catch (IOException e2) {
}
connectionFailed();
Log.e("error",e.getMessage());
return;
}
// Reset the ConnectThread because we're done
synchronized (BluetoothChatService.this) {
mConnectThread = null;
}
// Start the connected thread
connected(mmSocket, mmDevice, mSocketType);
}
ConnectedThread:管理连接的线程
去年第一次接触的时候,当时还年轻,自己看到这里犯了嘀咕,怎么还有?再往下看豁然开朗。回想网络编程,服务端和客户端是分离的,所以我们会在各自连接好之后就可以开始进行操作了(当然一般也都是另起一个线程进行),可是这是要实现互连的蓝牙,服务端和客户端是绑定在一起的,谷歌工程师进行了封装,将流的读写合并在了一起。Run方法中出现的是会引起阻塞的读操作,写数据方法单独成一个方法。
public void run() {
Log.e("connected:","run");
byte[] buffer = new byte[1024];
int bytes;
// Keep listening to the InputStream while connected
while (mState == STATE_CONNECTED) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
Log.e("connectedmg","length:"+bytes);
Log.e("connectedmg",new String(buffer));
// Send the obtained bytes to the UI Activity
mHandler.obtainMessage(MainActivity.MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
connectionLost();
// Start the service over to restart listening mode
BluetoothChatService.this.start();
break;
}
}
}
/**
* Write to the connected OutStream.
*
* @param buffer The bytes to write
*/
public void write(byte[] buffer) {
try {
mmOutStream.write(buffer);
// Share the sent message back to the UI Activity
mHandler.obtainMessage(MainActivity.MESSAGE_WRITE, -1, -1, buffer)
.sendToTarget();
} catch (IOException e) {
}
}
最后只得提醒的是,如果是6.0以上的系统需要动态申请蓝牙权限,记得在运行时做一些调整。