| 目前,很多手机已经具备了蓝牙功能。虽然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(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; 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.DataInputStream; import java.io.DataOutputStream; import java.io.IOException;
import java.util.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 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);
//设备集合 Vector devices = new Vector(); //服务集合 Vector records = new 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 Thread(this).start(); } public void commandAction(Command arg0, Displayable arg1) { if(arg0.getCommandType()==Command.CANCEL){ midlet.showMainMenu(); }else{ //匿名内部Thread,访问远程服务。 Thread fetchThread=new 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 { String url = sr.getConnectionURL( ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false); StreamConnection conn = (StreamConnection) Connector.open(url); DataOutputStream dos=conn.openDataOutputStream(); dos.writeUTF(input.getString()); dos.close(); DataInputStream dis=conn.openDataInputStream(); String echo=dis.readUTF(); dis.close(); showInfo("反馈结果是:"+echo); result=true; } catch (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 (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 (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 (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(String s){ StringBuffer sb=new 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(); } }
}
}
|