首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> J2ME

J2ME 藍牙開發實戰入門:基於 JSR-82 的客戶端/服務端完整實現

     編輯:J2ME

專案架構概覽

本範例實現一個經典的藍牙回聲服務:服務端將收到的字串轉為大寫後回傳。包含三個核心類別:StupidBTMIDlet(主控制器)、ServerBox(服務端 UI)、ClientBox(客戶端 UI)。已在 WTK 2.2 模擬器、Nokia 6600/6670 真機驗證通過。

主 MIDlet 控制器

public class StupidBTMIDlet extends MIDlet implements CommandListener {
    private List mainMenu;
    private ServerBox serverBox;
    private ClientBox clientBox;

    protected void startApp() {
        mainMenu = new List("藍牙入門範例", List.IMPLICIT);
        mainMenu.append("客戶端", null);
        mainMenu.append("服務端", null);
        mainMenu.setCommandListener(this);
        Display.getDisplay(this).setCurrent(mainMenu);
    }

    public void commandAction(Command cmd, Displayable disp) {
        if (cmd == List.SELECT_COMMAND) {
            int index = mainMenu.getSelectedIndex();
            if (index == 1) { // 服務端
                if (serverBox == null) serverBox = new ServerBox(this);
                serverBox.setString(null);
                Display.getDisplay(this).setCurrent(serverBox);
            } else { // 客戶端
                clientBox = null; System.gc(); // 強制重建
                clientBox = new ClientBox(this);
                Display.getDisplay(this).setCurrent(clientBox);
            }
        }
    }
    
    public void showMainMenu() { Display.getDisplay(this).setCurrent(mainMenu); }
    public void showString(String msg) { /* Alert 提示實現 */ }
}

客戶端核心邏輯:設備發現 → 服務搜尋 → 連線通訊

public class ClientBox extends Form implements Runnable, CommandListener, DiscoveryListener {
    private static final UUID ECHO_SERVER_UUID = 
        new UUID("F0E0D0C0B0A000908070605040302010", false);
    
    private DiscoveryAgent discoveryAgent;
    private Vector devices = new Vector();      // 發現的遠端設備
    private Vector records = new Vector();      // 發現的服務記錄
    private int[] transIDs;                     // 服務搜尋事務 ID
    
    public void run() {
        // 1. 藍牙初始化
        Gauge gauge = new Gauge(null, false, Gauge.INDEFINITE, Gauge.CONTINUOUS_RUNNING);
        append(gauge);
        showInfo("藍牙初始化...");
        
        try {
            LocalDevice local = LocalDevice.getLocalDevice();
            discoveryAgent = local.getDiscoveryAgent();
        } catch (Exception e) { showInfo("藍牙不可用"); delete(1); return; }
        
        // 2. 設備探查
        UUID[] uuidSet = { new UUID(0x1101), ECHO_SERVER_UUID };
        try { discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this); }
        catch (BluetoothStateException e) {}
        try { synchronized(this) { wait(); } } catch (InterruptedException e) {}
        
        showInfo("設備搜尋完成,共 " + devices.size() + " 台,開始服務搜尋");
        
        // 3. 服務搜尋(每台設備獨立事務)
        transIDs = new int[devices.size()];
        for (int i = 0; i < devices.size(); i++) {
            RemoteDevice rd = (RemoteDevice) devices.elementAt(i);
            try {
                transIDs[i] = discoveryAgent.searchServices(null, uuidSet, rd, this);
            } catch (BluetoothStateException e) { continue; }
        }
        try { synchronized(this) { wait(); } } catch (InterruptedException e) {}
        
        // 4. 顯示發送介面
        showInfo("服務搜尋完成,共 " + records.size() + " 項服務");
        if (records.size() > 0) {
            append(inputField);
            addCommand(new Command("發送", Command.OK, 0));
        }
        delete(1); // 移除進度條
    }
    
    // DiscoveryListener 回調實現
    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 (ServiceRecord sr : servRecord) records.addElement(sr);
    }
    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 id : transIDs) if (id != -1) { finished = false; break; }
        if (finished) synchronized(this) { notify(); }
    }
    
    // 服務存取與資料傳輸
    private boolean accessService(ServiceRecord sr) {
        try {
            String url = sr.getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
            StreamConnection conn = (StreamConnection) Connector.open(url);
            DataOutputStream dos = conn.openDataOutputStream();
            dos.writeUTF(inputField.getString());
            dos.close();
            DataInputStream dis = conn.openDataInputStream();
            String echo = dis.readUTF();
            dis.close();
            showInfo("回傳結果: " + echo);
            return true;
        } catch (IOException e) { return false; }
    }
}

服務端核心邏輯:服務註冊 → 連線接受 → 多線程處理

public class ServerBox extends TextBox implements Runnable, CommandListener {
    private static final UUID ECHO_SERVER_UUID = 
        new UUID("F0E0D0C0B0A000908070605040302010", false);
    
    private LocalDevice localDevice;
    private StreamConnectionNotifier notifier;
    private ServiceRecord record;
    private boolean isClosed = false;
    private ClientProcessor processor;
    
    public void run() {
        try {
            localDevice = LocalDevice.getLocalDevice();
            if (!localDevice.setDiscoverable(DiscoveryAgent.GIAC)) {
                showInfo("無法設定可發現模式"); return;
            }
            
            // 建立服務 URL:btspp://localhost:UUID;name=Echo Server;authorize=false
            StringBuffer url = new StringBuffer("btspp://localhost:");
            url.append(ECHO_SERVER_UUID.toString());
            url.append(";name=Echo Server;authorize=false");
            
            notifier = (StreamConnectionNotifier) Connector.open(url.toString());
            record = localDevice.getRecord(notifier);
            
            showInfo("初始化成功,等待連線...");
            removeCommand(publishCmd); addCommand(cancelCmd);
        } catch (Exception e) { showInfo("初始化失敗"); return; }
        
        processor = new ClientProcessor();
        while (!isClosed) {
            try {
                StreamConnection conn = notifier.acceptAndOpen(); // 阻塞等待連線
                processor.addConnection(conn);
            } catch (IOException e) { continue; }
        }
    }
    
    // 內部類:客戶端連線處理器(生產者-消費者模式)
    private class ClientProcessor implements Runnable {
        private Vector queue = new Vector();
        private Thread thread = new Thread(this);
        
        ClientProcessor() { thread.start(); }
        
        public void run() {
            while (!isClosed) {
                StreamConnection conn;
                synchronized (this) {
                    while (queue.isEmpty() && !isClosed) {
                        try { wait(); } catch (InterruptedException e) {}
                    }
                    if (isClosed) return;
                    conn = (StreamConnection) queue.firstElement();
                    queue.removeElementAt(0);
                }
                processConnection(conn); // 處理單一連線
            }
        }
        
        void addConnection(StreamConnection conn) {
            synchronized (this) { queue.addElement(conn); notify(); }
        }
    }
    
    private void processConnection(StreamConnection conn) {
        String input = readInputString(conn);
        String output = input.toUpperCase(); // 核心業務邏輯:轉大寫
        sendOutputData(output, conn);
        try { conn.close(); } catch (IOException e) {}
        showInfo("客戶端輸入: " + input + ",已回應!");
    }
}

關鍵技術總結

  • UUID 設計:自定義 128-bit UUID 確保服務唯一識別
  • 同步機制:客戶端使用 wait()/notify() 協調探查/搜尋階段;服務端採用生產者-消費者隊列處理並發連線
  • 連線 URL 參數:authorize=false 降低配對門檻,提升相容性
  • 資源管理:嚴格關閉 Stream、DataInput/OutputStream,避免洩漏

此範例展示了 JSR-82 核心 API 的標準使用模式,是開發藍牙對戰遊戲、資料同步應用的絕佳起點。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved