單 JAR 包實現 C/S 雙模式手機藍牙通信:Server 監聽連接、Client 搜索設備發現服務並連接、雙向文本消息收發。採用模組化分層架構,代碼結構清晰,適合作為藍牙應用開發模板。
acceptAndOpen() 等待連接 → 收到消息回显 "server echo: msg"startInquiry() 搜索設備 → 遍歷設備 searchServices() 發現目標 UUID → 連接 → 發送文本 → 接收回显src/
├── core/
│ └── BlueMessage.java # MIDlet 入口:生命周期、界面導航、退出控制
├── components/
│ ├── MainForm.java # 模式選擇界面:ChoiceGroup(Server/Client) + Command
│ ├── BlueClient.java # 客戶端界面:輸入框、發送按鈕、消息列表
│ └── BlueServer.java # 服務端界面:狀態顯示、消息日誌
└── bluetooth/
├── BlueClientService.java # 客戶端藍牙邏輯:DiscoveryListener 實現
└── BlueServerService.java # 服務端藍牙邏輯:Runnable 服務線程
public class BlueMessage extends MIDlet {
public BlueMessage() {
MainForm form = new MainForm(this);
Display.getDisplay(this).setCurrent(form);
}
public void ExitMidlet() { destroyApp(true); notifyDestroyed(); }
public void NavigateTo(Displayable d) { Display.getDisplay(this).setCurrent(d); }
// 生命周期方法留空...
}
設計亮點:提供 ExitMidlet()、NavigateTo() 供子界面調用,實現「界面無 MIDlet 依賴」。
public class MainForm extends Form implements CommandListener {
private ChoiceGroup choiceGp; // EXCLUSIVE 單選:client/server
private Command cmdSelect, cmdExit;
private BlueMessage parent;
public void commandAction(Command c, Displayable d) {
if (c == cmdExit) parent.ExitMidlet();
else if (c == cmdSelect) {
switch (choiceGp.getSelectedIndex()) {
case 0: parent.NavigateTo(new BlueClient(parent, this)); break;
case 1: parent.NavigateTo(new BlueServer(parent, this)); break;
}
}
}
}
public class BlueServerService implements Runnable {
private static final UUID SERVER_UUID = new UUID("F0E0D0C0B0A000908070605040302010", false);
private StreamConnectionNotifier notifier;
private BlueServer serverForm;
private ServiceRecord serviceRecord;
public void run() {
// 1. 獲取本地設備、設置可發現
LocalDevice local = LocalDevice.getLocalDevice();
local.setDiscoverable(DiscoveryAgent.GIAC);
// 2. 打開服務連接器
notifier = (StreamConnectionNotifier) Connector.open(getConnectionString());
serviceRecord = local.getRecord(notifier);
serverForm.appendInfo("Service ready, waiting...");
serverForm.changeForm(); // 切換到等待界面
// 3. 循環接受連接
while (true) {
StreamConnection conn = notifier.acceptAndOpen(); // 阻塞
new Thread(new ProcessConnection(conn)).start(); // 每連接一線程
}
}
private String getConnectionString() {
return "btspp://localhost:" + SERVER_UUID + ";name=BlueMessage;authorize=false";
}
private class ProcessConnection implements Runnable {
private StreamConnection conn;
public void run() {
try {
String input = readInputString(conn);
serverForm.appendInfo("Received: " + input);
String output = input.startsWith("connect...") ? "welcome..." : "server echo " + input;
sendOutputString(output, conn);
conn.close();
} catch (Exception e) { /* log */ }
}
// readInputString/sendOutputString 使用 DataInput/OutputStream 讀寫 UTF
}
}
關鍵點:
SERVER_UUID 必須與客戶端完全一致,標識唯一服務authorize=false 跳過授權確認,簡化測試流程public class BlueClientService implements Runnable, DiscoveryListener {
private static final UUID TARGET_UUID = new UUID("F0E0D0C0B0A000908070605040302010", false);
private Vector devices = new Vector(), records = new Vector();
private DiscoveryAgent agent;
private BlueClient clientForm;
private int[] transIDs;
private int activeIndex = -1;
public synchronized void run() {
// 1. 初始化本地設備、設置不可發現
LocalDevice local = LocalDevice.getLocalDevice();
agent = local.getDiscoveryAgent();
local.setDiscoverable(DiscoveryAgent.NOT_DISCOVERABLE);
// 2. 搜索設備
UUID[] uuidSet = { new UUID(0x1101), TARGET_UUID }; // 0x1101 = SPP 協議
agent.startInquiry(DiscoveryAgent.GIAC, this);
wait(); // 阻塞等待 inquiryCompleted 回調
// 3. 遍歷設備搜索服務
transIDs = new int[devices.size()];
for (int i=0; i= 0) clientForm.changeForm(); // 切換到聊天界面
}
// DiscoveryListener 回調實現
public void deviceDiscovered(RemoteDevice d, DeviceClass c) { devices.addElement(d); }
public void inquiryCompleted(int type) { synchronized(this){ notify(); } }
public void servicesDiscovered(int transID, ServiceRecord[] srs) { for(ServiceRecord sr: srs) records.addElement(sr); }
public void serviceSearchCompleted(int transID, int respCode) {
// 標記完成,全部完成時 notify()
}
// 發送消息入口
public void sendMsg(String msg) { accessService(records.elementAt(activeIndex), msg); }
}
核心機制:
wait() 配合 notify() 實現異步回調同步化transIDs 數組管理多設備並發服務搜索getActiveService() 實際發送測試包確認服務可用F0E0D0C0B0A000908070605040302010 — 應用層服務唯一標識,開發者自定義0x1101 — 標準服務類別(SPP 串口配置文件),由藍牙 SIG 分配uuidSet = {SPP_UUID, APP_UUID},確保協議匹配且服務正確| 現象 | 原因 | 解決 |
|---|---|---|
| 搜索不到設備 | 未設置可發現模式 / 藍牙關閉 / 距離過遠 | Server 必須 setDiscoverable(GIAC);確認硬件開啟 |
| 服務搜索失敗 | UUID 不匹配 / 協議不支持 | 雙端 UUID 字節級一致;確認設備支持 SPP |
| 連接斷開異常 | 流讀寫未關閉 / 線程衝突 | finally 塊關閉流;單線程處理單連接 |
| 模擬器無法通信 | WTK 模擬器藍牙模擬限制 | 必須真機測試;或使用 BlueCove 模擬 PC 端 |
本專案確立了 J2ME 藍牙通信的標準化模板:DiscoveryListener 異步回調同步化、UUID 雙層標識、StreamConnection 線程隔離處理。掌握此模式,可快速構建任意藍牙數據交換應用。