程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> J2ME >> J2ME藍牙程序開發實戰入門

J2ME藍牙程序開發實戰入門

編輯: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. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved