科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航



ZDNet>软件频道>中间件-zhiding>J2ME蓝牙实战入门

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

目前,很多手机已经具备了蓝牙功能。虽然MIDP2.0没有包括蓝牙API,但是JCP定义了JSR82, Java APIs for Bluetooth Wireless Technology (JABWT).这是一个可选API,很多支持MIDP2.0的手机已经实现了,比如Nokia 6600, Nokia 6670,Nokia7610等等。

来源:中国IT实验室 2007年09月22日

关键字:java 编程 J2ME

  目前,很多手机已经具备了蓝牙功能。虽然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领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

重磅专题