概述
目前,很多手机已经具备了蓝牙功能。虽然MIDP2.0没有包括蓝牙API,但是JCP定义了JSR82, Java APIs for Bluetooth Wireless Technology (JABWT).这是一个可选API,很多支持MIDP2.0的手机已经实现了,比如Nokia 6600, Nokia 6670,Nokia7610等等。对于一个开发者来说,如果目标平台支持JSR82的话,在制作联网对战类型游戏或者应用的时候,蓝牙是一个相当不错的选择。本文给出了一个最简单的蓝牙应用的J2ME程序,用以帮助开发者快速的掌握JSR82。该程序分别在2台蓝牙设备上安装后,一台设备作为服务端先运行,一台设备作为客户端后运行。在服务端上我们发布了一个服务,该服务的功能是把客户端发过来的字符串转变为大写字符串。客户端起动并搜索到服务端的服务后,我们就可以从客户端的输入框里输入任意的字符串,发送到服务端去,同时观察服务端的反馈结果。
本文并不具体讲述蓝牙的运行机制和JSR82的API结构,关于这些知识点,请参考本文的参考资料一节,这些参考资料会给你一个权威的精确的解释。
实例代码
该程序包括3个java文件。一个是MIDlet,另外2个为服务端GUI和客户端GUI。该程序已经在wtk22模拟器和Nokia 6600,Nokia 6670两款手机上测试通过。
StupidBTMIDlet.java
- import javax.microedition.lcdui.Alert;
- import javax.microedition.lcdui.AlertType;
- import javax.microedition.lcdui.Command;
- import javax.microedition.lcdui.CommandListener;
- import javax.microedition.lcdui.Display;
- import javax.microedition.lcdui.Displayable;
- import javax.microedition.lcdui.List;
- import javax.microedition.midlet.MIDlet;
- import javax.microedition.midlet.MIDletStateChangeException;
- /**
- * @author Jagie
- *
- * MIDlet
- */
- public class StupidBTMIDlet extends MIDlet implements CommandListener {
- List list;
- ServerBox sb;
- ClientBox cb;
- /*
- * (non-Javadoc)
- *
- * @see javax.microedition.midlet.MIDlet#startApp()
- */
- protected void startApp() throws MIDletStateChangeException {
- list = new List("傻瓜蓝牙入门", List.IMPLICIT);
- list.append("Client", null);
- list.append("Server", null);
- list.setCommandListener(this);
- Display.getDisplay(this).setCurrent(list);
- }
- /**
- * debug方法
- * @param s 要显示的字串
- */
- public void showString(java/lang/String.java.html" target="_blank">String s) {
- Displayable dp = Display.getDisplay(this).getCurrent();
- Alert al = new Alert(null, s, null, AlertType.INFO);
- al.setTimeout(2000);
- Display.getDisplay(this).setCurrent(al, dp);
- }
- /**
- * 显示主菜单
- *
- */
- public void showMainMenu() {
- Display.getDisplay(this).setCurrent(list);
- }
- protected void pauseApp() {
- // TODO Auto-generated method stub
- }
- public void commandAction(Command com, Displayable disp) {
- if (com == List.SELECT_COMMAND) {
- List list = (List) disp;
- int index = list.getSelectedIndex();
- if (index == 1) {
- if (sb == null) {
- sb = new ServerBox(this);
- }
- sb.setString(null);
- Display.getDisplay(this).setCurrent(sb);
- } else {
- //每次都生成新的客户端实例
- cb = null;
- java/lang/System.java.html" target="_blank">System.gc();
- cb = new ClientBox(this);
- Display.getDisplay(this).setCurrent(cb);
- }
- }
- }
- protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
- // TODO Auto-generated method stub
- }
- }
ClientBox.java
- import java.io.java/io/DataInputStream.java.html" target="_blank">DataInputStream;
- import java.io.java/io/DataOutputStream.java.html" target="_blank">DataOutputStream;
- import java.io.java/io/IOException.java.html" target="_blank">IOException;
- import java.util.java/util/Vector.java.html" target="_blank">Vector;
- import javax.microedition.io.Connector;
- import javax.microedition.io.StreamConnection;
- import javax.microedition.lcdui.Command;
- import javax.microedition.lcdui.CommandListener;
- import javax.microedition.lcdui.Displayable;
- import javax.microedition.lcdui.Form;
- import javax.microedition.lcdui.Gauge;
- import javax.microedition.lcdui.StringItem;
- import javax.microedition.lcdui.TextField;
- //jsr082 API
- import javax.bluetooth.BluetoothStateException;
- import javax.bluetooth.DeviceClass;
- import javax.bluetooth.DiscoveryAgent;
- import javax.bluetooth.DiscoveryListener;
- import javax.bluetooth.LocalDevice;
- import javax.bluetooth.RemoteDevice;
- import javax.bluetooth.ServiceRecord;
- import javax.bluetooth.UUID;
- /**
- * 客户端GUI
- * @author Jagie
- *
- * TODO To change the template for this generated type comment go to
- * Window – Preferences – Java – Code Style – Code Templates
- */
- public class ClientBox extends Form implements java/lang/Runnable.java.html" target="_blank">Runnable, CommandListener,
- DiscoveryListener {
- //字串输入框
- TextField input = new TextField(null, "", 50, TextField.ANY);
- //loger
- StringItem result = new StringItem("结果:", "");
- private DiscoveryAgent discoveryAgent;
- private UUID[] uuidSet;
- //响应服务的UUID
- private static final UUID ECHO_SERVER_UUID = new UUID(
- "F0E0D0C0B0A000908070605040302010", false);
- //设备集合
- java/util/Vector.java.html" target="_blank">Vector devices = new java/util/Vector.java.html" target="_blank">Vector();
- //服务集合
- java/util/Vector.java.html" target="_blank">Vector records = new java/util/Vector.java.html" target="_blank">Vector();
- //服务搜索的事务id集合
- int[] transIDs;
- StupidBTMIDlet midlet;
- public ClientBox(StupidBTMIDlet midlet) {
- super("");
- this.midlet=midlet;
- this.append(result);
- this.addCommand(new Command("取消",Command.CANCEL,1));
- this.setCommandListener(this);
- new java/lang/Thread.java.html" target="_blank">Thread(this).start();
- }
- public void commandAction(Command arg0, Displayable arg1) {
- if(arg0.getCommandType()==Command.CANCEL){
- midlet.showMainMenu();
- }else{
- //匿名内部Thread,访问远程服务。
- java/lang/Thread.java.html" target="_blank">Thread fetchThread=new java/lang/Thread.java.html" target="_blank">Thread(){
- public void run(){
- for(int i=0;i<records.size();i++){
- ServiceRecord sr=(ServiceRecord)records.elementAt(i);
- if(accessService(sr)){
- //访问到一个可用的服务即可
- break;
- }
- }
- }
- };
- fetchThread.start();
- }
- }
- private boolean accessService(ServiceRecord sr){
- boolean result=false;
- try {
- java/lang/String.java.html" target="_blank">String url = sr.getConnectionURL(
- ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
- StreamConnection conn = (StreamConnection) Connector.open(url);
- java/io/DataOutputStream.java.html" target="_blank">DataOutputStream dos=conn.openDataOutputStream();
- dos.writeUTF(input.getString());
- dos.close();
- java/io/DataInputStream.java.html" target="_blank">DataInputStream dis=conn.openDataInputStream();
- java/lang/String.java.html" target="_blank">String echo=dis.readUTF();
- dis.close();
- showInfo("反馈结果是:"+echo);
- result=true;
- } catch (java/io/IOException.java.html" target="_blank">IOException e) {
- }
- return result;
- }
- public synchronized void run() {
- //发现设备和服务的过程中,给用户以Gauge
- Gauge g=new Gauge(null,false,Gauge.INDEFINITE,Gauge.CONTINUOUS_RUNNING);
- this.append(g);
- showInfo("蓝牙初始化…");
- boolean isBTReady = false;
- try {
- LocalDevice localDevice = LocalDevice.getLocalDevice();
- discoveryAgent = localDevice.getDiscoveryAgent();
- isBTReady = true;
- } catch (java/lang/Exception.java.html" target="_blank">Exception e) {
- e.printStackTrace();
- }
- if (!isBTReady) {
- showInfo("蓝牙不可用");
- //删除Gauge
- this.delete(1);
- return;
- }
- uuidSet = new UUID[2];
- //标志我们的响应服务的UUID集合
- uuidSet[0] = new UUID(0x1101);
- uuidSet[1] = ECHO_SERVER_UUID;
- try {
- discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);
- } catch (BluetoothStateException e) {
- }
- try {
- //阻塞,由inquiryCompleted()回调方法唤醒
- wait();
- } catch (java/lang/InterruptedException.java.html" target="_blank">InterruptedException e1) {
- e1.printStackTrace();
- }
- showInfo("设备搜索完毕,共找到"+devices.size()+"个设备,开始搜索服务");
- transIDs = new int[devices.size()];
- for (int i = 0; i < devices.size(); i++) {
- RemoteDevice rd = (RemoteDevice) devices.elementAt(i);
- try {
- //记录每一次服务搜索的事务id
- transIDs[i] = discoveryAgent.searchServices(null, uuidSet,
- rd, this);
- } catch (BluetoothStateException e) {
- continue;
- }
- }
- try {
- //阻塞,由serviceSearchCompleted()回调方法在所有设备都搜索完的情况下唤醒
- wait();
- } catch (java/lang/InterruptedException.java.html" target="_blank">InterruptedException e1) {
- e1.printStackTrace();
- }
- showInfo("服务搜索完毕,共找到"+records.size()+"个服务,准备发送请求");
- if(records.size()>0){
- this.append(input);
- this.addCommand(new Command("发送",Command.OK,0));
- }
- //删除Gauge
- this.delete(1);
- }
- /**
- * debug
- * @param s
- */
- private void showInfo(java/lang/String.java.html" target="_blank">String s){
- java/lang/StringBuffer.java.html" target="_blank">StringBuffer sb=new java/lang/StringBuffer.java.html" target="_blank">StringBuffer(result.getText());
- if(sb.length()>0){
- sb.append("\n");
- }
- sb.append(s);
- result.setText(sb.toString());
- }
- /**
- * 回调方法
- */
- public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
- if (devices.indexOf(btDevice) == -1) {
- devices.addElement(btDevice);
- }
- }
- /**
- * 回调方法,唤醒初始化线程
- */
- public void inquiryCompleted(int discType) {
- synchronized (this) {
- notify();
- }
- }
- /**
- * 回调方法
- */
- public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
- for (int i = 0; i < servRecord.length; i++) {
- records.addElement(servRecord[i]);
- }
- }
- /**
- * 回调方法,唤醒初始化线程
- */
- public void serviceSearchCompleted(int transID, int respCode) {
- for (int i = 0; i < transIDs.length; i++) {
- if (transIDs[i] == transID) {
- transIDs[i] = -1;
- break;
- }
- }
- //如果所有的设备都已经搜索服务完毕,则唤醒初始化线程。
- boolean finished = true;
- for (int i = 0; i < transIDs.length; i++) {
- if (transIDs[i] != -1) {
- finished = false;
- break;
- }
- }
- if (finished) {
- synchronized (this) {
- notify();
- }
- }
- }
- }
ServerBox.java
- import java.io.java/io/DataInputStream.java.html" target="_blank">DataInputStream;
- import java.io.java/io/DataOutputStream.java.html" target="_blank">DataOutputStream;
- import java.io.java/io/IOException.java.html" target="_blank">IOException;
- import java.util.java/util/Vector.java.html" target="_blank">Vector;
- import javax.bluetooth.DiscoveryAgent;
- import javax.bluetooth.LocalDevice;
- import javax.bluetooth.ServiceRecord;
- import javax.bluetooth.UUID;
- import javax.microedition.io.Connector;
- import javax.microedition.io.StreamConnection;
- import javax.microedition.io.StreamConnectionNotifier;
- import javax.microedition.lcdui.Command;
- import javax.microedition.lcdui.CommandListener;
- import javax.microedition.lcdui.Displayable;
- import javax.microedition.lcdui.TextBox;
- import javax.microedition.lcdui.TextField;
- /**
- * 服务端GUI
- * @author Jagie
- *
- * TODO To change the template for this generated type comment go to
- * Window – Preferences – Java – Code Style – Code Templates
- */
- public class ServerBox extends TextBox implements java/lang/Runnable.java.html" target="_blank">Runnable, CommandListener {
- Command com_pub = new Command("开启服务", Command.OK, 0);
- Command com_cancel = new Command("终止服务", Command.CANCEL, 0);
- Command com_back = new Command("返回", Command.BACK, 1);
- LocalDevice localDevice;
- StreamConnectionNotifier notifier;
- ServiceRecord record;
- boolean isClosed;
- ClientProcessor processor;
- StupidBTMIDlet midlet;
- //响应服务的uuid
- private static final UUID ECHO_SERVER_UUID = new UUID(
- "F0E0D0C0B0A000908070605040302010", false);
- public ServerBox(StupidBTMIDlet midlet) {
- super(null, "", 500, TextField.ANY);
- this.midlet = midlet;
- this.addCommand(com_pub);
- this.addCommand(com_back);
- this.setCommandListener(this);
- }
- public void run() {
- boolean isBTReady = false;
- try {
- localDevice = LocalDevice.getLocalDevice();
- if (!localDevice.setDiscoverable(DiscoveryAgent.GIAC)) {
- showInfo("无法设置设备发现模式");
- return;
- }
- // prepare a URL to create a notifier
- java/lang/StringBuffer.java.html" target="_blank">StringBuffer url = new java/lang/StringBuffer.java.html" target="_blank">StringBuffer("btspp://");
- // indicate this is a server
- url.append("localhost").append(:);
- // add the UUID to identify this service
- url.append(ECHO_SERVER_UUID.toString());
- // add the name for our service
- url.append(";name=Echo Server");
- // request all of the client not to be authorized
- // some devices fail on authorize=true
- url.append(";authorize=false");
- // create notifier now
- notifier = (StreamConnectionNotifier) Connector
- .open(url.toString());
- record = localDevice.getRecord(notifier);
- // remember weve reached this point.
- isBTReady = true;
- } catch (java/lang/Exception.java.html" target="_blank">Exception e) {
- e.printStackTrace();
- }
- // nothing to do if no bluetooth available
- if (isBTReady) {
- showInfo("初始化成功,等待连接");
- this.removeCommand(com_pub);
- this.addCommand(com_cancel);
- } else {
- showInfo("初始化失败,退出");
- return;
- }
- // 生成服务端服务线程对象
- processor = new ClientProcessor();
- // ok, start accepting connections then
- while (!isClosed) {
- StreamConnection conn = null;
- try {
- conn = notifier.acceptAndOpen();
- } catch (java/io/IOException.java.html" target="_blank">IOException e) {
- // wrong client or interrupted – continue anyway
- continue;
- }
- processor.addConnection(conn);
- }
- }
- public void publish() {
- isClosed = false;
- this.setString(null);
- new java/lang/Thread.java.html" target="_blank">Thread(this).start();
- }
- public void cancelService() {
- isClosed = true;
- showInfo("服务终止");
- this.removeCommand(com_cancel);
- this.addCommand(com_pub);
- }
- /*
- * (non-Javadoc)
- *
- * @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command,
- * javax.microedition.lcdui.Displayable)
- */
- public void commandAction(Command arg0, Displayable arg1) {
- if (arg0 == com_pub) {
- //发布service
- publish();
- } else if (arg0 == com_cancel) {
- cancelService();
- } else {
- cancelService();
- midlet.showMainMenu();
- }
- }
- /**
- * 内部类,服务端服务线程。
- * @author Jagie
- *
- * TODO To change the template for this generated type comment go to
- * Window – Preferences – Java – Code Style – Code Templates
- */
- private class ClientProcessor implements java/lang/Runnable.java.html" target="_blank">Runnable {
- private java/lang/Thread.java.html" target="_blank">Thread processorThread;
- private java/util/Vector.java.html" target="_blank">Vector queue = new java/util/Vector.java.html" target="_blank">Vector();
- private boolean isOk = true;
- ClientProcessor() {
- processorThread = new java/lang/Thread.java.html" target="_blank">Thread(this);
- processorThread.start();
- }
- public void run() {
- while (!isClosed) {
- synchronized (this) {
- if (queue.size() == 0) {
- try {
- //阻塞,直到有新客户连接
- wait();
- } catch (java/lang/InterruptedException.java.html" target="_blank">InterruptedException e) {
- }
- }
- }
- //处理连接队列
- StreamConnection conn;
- synchronized (this) {
- if (isClosed) {
- return;
- }
- conn = (StreamConnection) queue.firstElement();
- queue.removeElementAt(0);
- processConnection(conn);
- }
- }
- }
- /**
- * 往连接队列添加新连接,同时唤醒处理线程
- * @param conn
- */
- void addConnection(StreamConnection conn) {
- synchronized (this) {
- queue.addElement(conn);
- notify();
- }
- }
- }
- /**
- * 从StreamConnection读取输入
- * @param conn
- * @return
- */
- private java/lang/String.java.html" target="_blank">String readInputString(StreamConnection conn) {
- java/lang/String.java.html" target="_blank">String inputString = null;
- try {
- java/io/DataInputStream.java.html" target="_blank">DataInputStream dis = conn.openDataInputStream();
- inputString = dis.readUTF();
- dis.close();
- } catch (java/lang/Exception.java.html" target="_blank">Exception e) {
- e.printStackTrace();
- }
- return inputString;
- }
- /**
- * debug
- * @param s
- */
- private void showInfo(java/lang/String.java.html" target="_blank">String s) {
- java/lang/StringBuffer.java.html" target="_blank">StringBuffer sb = new java/lang/StringBuffer.java.html" target="_blank">StringBuffer(this.getString());
- if (sb.length() > 0) {
- sb.append("\n");
- }
- sb.append(s);
- this.setString(sb.toString());
- }
- /**
- * 处理客户端连接
- * @param conn
- */
- private void processConnection(StreamConnection conn) {
- // 读取输入
- java/lang/String.java.html" target="_blank">String inputString = readInputString(conn);
- //生成响应
- java/lang/String.java.html" target="_blank">String outputString = inputString.toUpperCase();
- //输出响应
- sendOutputData(outputString, conn);
- try {
- conn.close();
- } catch (java/io/IOException.java.html" target="_blank">IOException e) {
- } // ignore
- showInfo("客户端输入:" + inputString + ",已成功响应!");
- }
- /**
- * 输出响应
- * @param outputData
- * @param conn
- */
- private void sendOutputData(java/lang/String.java.html" target="_blank">String outputData, StreamConnection conn) {
- try {
- java/io/DataOutputStream.java.html" target="_blank">DataOutputStream dos = conn.openDataOutputStream();
- dos.writeUTF(outputData);
- dos.close();
- } catch (java/io/IOException.java.html" target="_blank">IOException e) {
- }
- }
- }
小结
本文给出了一个简单的蓝牙服务的例子。旨在帮助开发者快速掌握JSR82.如果该文能对你有所启发,那就很好了。
参考资料
1. http://developers.sun.com/techtopics/mobility/apis/articles/bluetoothintro/
JSR82 API介绍(英文)
2. asp?ArticleID=249">http://www.j2medev.com/Article/ShowArticle.asp?ArticleID=249
JSR82 API 介绍(中文)
3. http://www.jcp.org/en/jsr/detail?id=82
JSR82 Specification.
4.WTK22, BluetoothDemo 项目
作者简介
陈万飞,网名Jagie,培训师,爱好java技术.可通过chen_cwf@163.com与他联系