【 tulaoshi.com - 编程语言 】
概述 目前,很多手机已经具备了蓝牙功能。虽然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.javaimport 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.javaimport 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;irecords.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(""); } 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.javaimport java.io.
DataInputStream;
import java.io.
DataOutputStream;
import java.io.
IOException;
import java.util.
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 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 StringBuffer url =
new 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 we've reached this point. isBTReady =
true; }
catch (
Exception e) { e.printStackTrace(); }
// nothing to do if no bluetooth available if (isBTReady) { showInfo("初始化成功,等待连接");
this.removeCommand(com_pub);
this.addCommand(com_cancel); }
else {