最近公司准备将旧系统的.NET部分翻版,项目除了有后台的还有个与设备对接的客户端用蓝牙连接的,所有这周对相关技术做了一个验证。
搜了一下Java 蓝牙相关信息,我去资料也太少了,少也就算了连bluecove库也是有问题的。经过艰难的查找,最终还是调通了。因为整个过程都是靠大家的资料去解决的,所以本着造福后人的角度我把我的经验分享一下,让后人不用想我一样满世界去找。
BlueCove框架
首先是Java SE虽然支持蓝牙但是支持的并不是很好,反而在Java ME 支持设备方面较好,但是我桌面平台也要啊,所有就有框架BlueCove了。BlueCove的API我用了感觉风格整体都和Java ME差不多,所以当你有比较复杂的需求又找不到文档的时候可以参考一下Java ME的资料。
BlueCove框架最大的问题就是比较老了, 2.1.1-SNAPSHOT.63 文档最后更新是2010年,提供的jar包居然不支持64位系统!还好有解决方案的,不然我就不用开发了。这里首先参考这篇博客 Eclipse + Java + BlueCove + WIN/MAC 蓝牙开发 这篇对解决方法的介绍的比较详细,但是并没有帮我直接解决问题,因为我顺着链接去下载的jar还是不支持64位系统(也可能是我下错了包了)。不过我知道了是可以解决的,于是各种百度和谷歌终于找到了能在64位系统运行的jar包。
64位系无法运行会提示:
Native Library intelbth_x64 not available
Native Library bluecove_x64 not available
官网直接下载的2.1.0版本不支持64位,下面这个支持的版本看路径也是官方的,竟然不提供!亏我找了不知道多久。
64位支持版本jar包下载地址:
http://snapshot.bluecove.org/distribution/download/2.1.1-SNAPSHOT/2.1.1-SNAPSHOT.63/
老铁们,这个地址访问不了直接看文章最后的Demo,里面有lib包
BlueCove还需要Apache的commons-io包,这个顺便下就可以的。
测试环境:
蓝牙是与网卡一体的 bcm94352
蓝牙连接:作为服务端
解决了框架问题接下去就简单了,这方面网上的例子不多但还是有的(都差不多),都是手机控制电脑,这几个而是从上面那个博客找到的(感谢上一个博客让我少走了一点弯路)。
参考以下三篇文章:
http://royal2xiaose.iteye.com/blog/1420138 http://www.eoeandroid.com/thread-264135-1-1.html
这三篇都是将PC作为一个服务端,让手机主动连接。
//本机蓝牙设备
private LocalDevice local = null;
// 流连接
private StreamConnection streamConnection = null;
// 接受数据的字节流
private byte[] acceptdByteArray = new byte[1024];
// 输入流
private DataInputStream inputStream;
//接入通知
private StreamConnectionNotifier notifier;
//线程池
private final static ExecutorService service = Executors.newCachedThreadPool();
public BuletoothService() {
try {
//这两步不一定要
BluCatUtil.doctorDevice(); // 驱动检查
RemoteDeviceDiscovery.runDiscovery(); // 搜索附近所有的蓝牙设备
System.out.println(RemoteDeviceDiscovery.getDevices());
} catch (IOException | InterruptedException e1) {
e1.printStackTrace();
}
try {
local = LocalDevice.getLocalDevice();
if (!local.setDiscoverable(DiscoveryAgent.GIAC))
System.out.println("请将蓝牙设置为可被发现");
/**
* 作为服务端,被请求
*/
String url = "btspp://localhost:" + new UUID(80087355).toString()
+ ";name=RemoteBluetooth";
notifier = (StreamConnectionNotifier)Connector.open(url);
} catch (IOException e) {
e.printStackTrace();
}
service.submit(this);
}
上面的其实就一个Connector.open(url)
是重点,service.submit(this);
获取数据在新线程中。
为了能够手动停止线程,做了一些处理。
@Override
public void run() {
try {
String inStr = null;
streamConnection = notifier.acceptAndOpen(); //阻塞的,等待设备连接
inputStream = streamConnection.openDataInputStream();
int length;
while (true) {
if ((inputStream.available()) <= 0) { //不阻塞线程
if (stopFlag) //UI停止后,关闭
break;
Thread.sleep(800); //数据间隔比较长,手动堵塞线程
} else {
length = inputStream.read(acceptdByteArray);
if(length>0) {
inStr = new String(acceptdByteArray,0,length);
System.out.println(inStr);
}
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null)
inputStream.close();
if (streamConnection != null)
streamConnection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
核心就是streamConnection = notifier.acceptAndOpen();
获得连接这里会堵塞线程(没有作停止处理!!!)。有连接后获得输入流inputStream = streamConnection.openDataInputStream();
接下来就是你自己发挥了。
蓝牙连接:作为客户端
网上关于这个的是真的几乎没有!因为需要一个服务端,现在我手头客户端是挺多的就是缺个服务端。公司的电脑都是没蓝牙的,过几天问同事借太笔记本再测试!所以下面的代码我也只是提供一个思路,具体你可以测试来告诉我。或者我测试通过再告诉你们。整体都是差不多的,先连接服务端,再获取输入流/输出流。这里写了一个工具类先查询了附近的蓝牙设备(详见本文蓝牙发现小节),因为需要 BluetoothAddress
,如果这个是已知的可以直接写。
Set<RemoteDevice> devicesDiscovered = RemoteDeviceDiscovery.getDevices(); //附近所有的蓝牙设备,必须先执行 runDiscovery
if (devicesDiscovered.iterator().hasNext()) { //连接
RemoteDevice first = devicesDiscovered.iterator().next();
streamConnection = (StreamConnection) Connector.open("btspp://" + first.getBluetoothAddress() + ":1");
}
-
Set<RemoteDevice> devicesDiscovered = RemoteDeviceDiscovery.getDevices()
附近可用的蓝牙连接 -
streamConnection = (StreamConnection) Connector.open("btspp://" + first.getBluetoothAddress() + ":1");
直接连接 - url地址 :
btspp://<蓝牙设备地址>:<通道号>
- 有连接后获得输入流
inputStream = streamConnection.openDataInputStream();
一样了
有个简单参考博客:http://blog.sina.com.cn/s/blog_a861feb40102vppo.html
里面没有太多的内容,流程比较清晰。通道号我用pc连接Anrdoid手机的时候,好像不同的通道有不同的功能,有的提示要读取音频,有的提示要读取电话和联系人等。可能这个通道在PC上功能又是不一样的。
2017.8.22 PC读取蓝牙连接的电子秤数据验证通过,客户端可行。
蓝牙设备发现
搜索周边可用的蓝牙是比较方便的。
public final static Set<RemoteDevice> devicesDiscovered = new HashSet<RemoteDevice>();
private static void findDevices() throws IOException, InterruptedException {
final Object inquiryCompletedEvent = new Object();
devicesDiscovered.clear();
DiscoveryListener listener = new DiscoveryListener() {
public void inquiryCompleted(int discType) {
System.out.println("#" + "搜索完成");
synchronized (inquiryCompletedEvent) {
inquiryCompletedEvent.notifyAll();
}
}
@Override
public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass deviceClass) {
devicesDiscovered.add(remoteDevice);
try {
System.out.println("#发现设备" + remoteDevice.getFriendlyName(false));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void servicesDiscovered(int arg0, ServiceRecord[] arg1) {
System.out.println("#" + "servicesDiscovered");
}
@Override
public void serviceSearchCompleted(int arg0, int arg1) {
System.out.println("#" + "serviceSearchCompleted");
}
};
synchronized (inquiryCompletedEvent) {
LocalDevice ld = LocalDevice.getLocalDevice();
System.out.println("#本机蓝牙名称:" + ld.getFriendlyName());
boolean started = LocalDevice.getLocalDevice().getDiscoveryAgent().startInquiry(DiscoveryAgent.GIAC,listener);
if (started) {
System.out.println("#" + "等待搜索完成...");
inquiryCompletedEvent.wait();
LocalDevice.getLocalDevice().getDiscoveryAgent().cancelInquiry(listener);
System.out.println("#发现设备数量:" + devicesDiscovered.size());
}
}
}
可以参考Java ME的流程,核心是:LocalDevice.getLocalDevice().getDiscoveryAgent().startInquiry(DiscoveryAgent.GIAC,listener);
(设备可被发现,搜索监听器),监听器在不同的过程节点提供了回调方法,设备发现的时候添加进集合中:
@Override
public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass deviceClass) {
devicesDiscovered.add(remoteDevice);
try {
System.out.println("#发现设备" + remoteDevice.getFriendlyName(false));
} catch (IOException e) {
e.printStackTrace();
}
}
扩展参考
如果有更多的需求有以下几个文档可以参考:
官方API文档: http://snapshot.bluecove.org/
Java ME 蓝牙:http://www.oracle.com/technetwork/java/javame/tech/index-140411.html
感谢
作者:Old_Me_Mory 提供了我总的解决思路;
还有许多的文章作者提供了解决思路!
Demo下载
没有代码总是很难解决一些问题,点击这里Demo下载