Chinaunix首页 | 论坛 | 博客
  • 博客访问: 284180
  • 博文数量: 82
  • 博客积分: 2607
  • 博客等级: 少校
  • 技术积分: 785
  • 用 户 组: 普通用户
  • 注册时间: 2006-09-14 15:23
文章分类

全部博文(82)

文章存档

2012年(4)

2010年(1)

2009年(2)

2008年(8)

2007年(34)

2006年(33)

我的朋友

分类: Java

2006-11-21 13:07:03

  概述

  目前,很多手机已经具备了蓝牙功能。虽然MIDP2.0没有包括蓝牙API,但是JCP定义了JSR82, JavaAPIsforBluetoothWirelessTechnology(JABWT).这是一个可选API,很多支持MIDP2.0的手机已经 实现了,比如Nokia6600,Nokia6670,Nokia7610等等。对于一个开发者来说,如果目标平台支持JSR82的话,在制作联网对战类 型游戏或者应用的时候,蓝牙是一个相当不错的选择。本文给出了一个最简单的蓝牙应用的J2ME程序,用以帮助开发者快速的掌握JSR82。该程序分别在2 台蓝牙设备上安装后,一台设备作为服务端先运行,一台设备作为客户端后运行。在服务端上我们发布了一个服务,该服务的功能是把客户端发过来的字符串转变为 大写字符串。客户端起动并搜索到服务端的服务后,我们就可以从客户端的输入框里输入任意的字符串,发送到服务端去,同时观察服务端的反馈结果。

  本文并不具体讲述蓝牙的运行机制和JSR82的API结构,关于这些知识点,请参考本文的参考资料一节,这些参考资料会给你一个权威的精确的解释。

  实例代码

  该程序包括3个java文件。一个是MIDlet,另外2个为服务端GUI和客户端GUI。该程序已经在wtk22模拟器和Nokia6600,Nokia6670两款手机上测试通过。

 

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 {

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();
}
}

}

}



ServerBox.java



import 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 {
showInfo("初始化失败,退出");
return;

}

// 生成服务端服务线程对象
processor = new ClientProcessor();

// ok, start accepting connections then
while (!isClosed) {
StreamConnection conn = null;

try {
conn = notifier.acceptAndOpen();
} catch (IOException e) {
// wrong client or interrupted
- continue anyway
continue;
}
processor.addConnection(conn);
}

}

public void publish()
{
isClosed = false;
this.setString(null);
new 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 Runnable
{
private Thread processorThread;

private Vector queue = new Vector();

private boolean isOk = true;

ClientProcessor() {
processorThread = new Thread(this);
processorThread.start();
}

public void run() {
while (!isClosed) {

synchronized (this) {
if (queue.size() == 0)
{
try {
//阻塞,直到有新客户连接
wait();
}
catch (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 String readInputString
(StreamConnection conn)
{
String inputString = null;

try {

DataInputStream dis
= conn.openDataInputStream();
inputString = dis.readUTF();
dis.close();
} catch (Exception e)
{
e.printStackTrace();
}
return inputString;
}

/**
* debug
* @param s
*/

private void showInfo(String s)
{
StringBuffer sb =
new StringBuffer(this.getString());
if (sb.length() > 0) {
sb.append("\n");
}

sb.append(s);
this.setString(sb.toString());

}


/**
* 处理客户端连接
* @param conn
*/
private void processConnection
(StreamConnection conn)
{

// 读取输入
String inputString =
readInputString(conn);
//生成响应
String outputString =
inputString.toUpperCase();
//输出响应
sendOutputData(outputString, conn);

try {
conn.close();
} catch (IOException e) {
} // ignore
showInfo("客户端输入:" +
inputString + ",已成功响应!");
}

/**
* 输出响应
* @param outputData
* @param conn
*/

private void sendOutputData
(String outputData, StreamConnection conn)

{

try
{
DataOutputStream dos =
conn.openDataOutputStream();
dos.writeUTF(outputData);
dos.close();
} catch (IOException e) {
}

}
}

  小结

  本文给出了一个简单的蓝牙服务的例子。旨在帮助开发者快速掌握JSR82.如果该文能对你有所启发,那就很好了。

  参考资料

  1.http://developers.sun.com/techtopics/mobility/apis/articles/bluetoothintro/

  JSR82API介绍(英文)

  2.http://www.j2medev.com/Article/ShowArticle.asp?ArticleID=249

  JSR82API介绍(中文)

  3.

  JSR82Specification.

  4.WTK22,BluetoothDemo项目

  作者简介

  陈万飞,网名Jagie,培训师,爱好java技术.可通过chen_cwf@163

阅读(764) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~