目前,很多手机已经具备了蓝牙功能。虽然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();
}
}
}
}
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/
jsr82 api介绍(英文)
2. 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 项目
闽公网安备 35060202000074号