扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
  目前,很多手机已经具备了蓝牙功能。虽然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 项目
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。