WEB开发网
开发学院软件开发Java BlueTooth探索系列(五)—J2ME蓝牙实战入门 阅读

BlueTooth探索系列(五)—J2ME蓝牙实战入门

 2007-12-23 12:27:50 来源:WEB开发网   
核心提示:BlueTooth探索系列(五)—J2ME蓝牙实战入门作者Jagie-(cleverpig的好朋友)版权申明: 转载务必保留以下申明和链接作者:Jagie地址:http://www.matrix.org.cn/resource/article/43/43868_BlueTooth.Html关键字:蓝牙,j2me,jsr
BlueTooth探索系列(五)—J2ME蓝牙实战入门

作者Jagie-(cleverpig的好朋友)


版权申明: 转载务必保留以下申明和链接
作者:Jagie
地址:http://www.matrix.org.cn/resource/article/43/43868_BlueTooth.Html
关键字:蓝牙,j2me,jsr82,入门

概述

  目前,很多手机已经具备了蓝牙功能。虽然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.如果该文能对你有所启发,那就很好了。进入讨论组讨论。

(出处:http://www.cncms.com)


Tags:BlueTooth 探索 系列

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接