上篇文章講述了開發環境的搭建和一些相關知識的介紹,這篇文章准備介紹下怎樣實現手機和手機之間通過藍牙實現互聯通信的程序,然後接下來的日子可能會寫個簡單的通過藍牙互聯的手機小游戲(其他的事情比較多,加上筆者比較懶,呵呵,見諒~)。
這個小程序時個C/S結構的,但是只有一個Jar包。運行程序後的首頁會有一個二選一選項(server或者client),當你選擇server後單擊 select按鈕會進入服務器界面,單擊setup按鈕,那麼便會開啟服務器端的程序,並且循環監聽來自客戶端的藍牙連接;而如果選擇client選項,則會進入客戶端界面,單擊connect則會開始搜索周圍的設備並遍歷設備上的目標服務,如果搜索到服務的話則會連接上服務器,這時候你可以在文本框中輸入信息並點擊發送,服務器則會反饋相應的信息。
呵呵,雖然實現的功能簡單,但是想要做更復雜的應用,這一步還是必須得走的~先看下我的程序文件結構吧:
---core //核心包名
---BlueMessage.java //Midlet主類,程序入口
---components //組件包名
---MainForm.java //起始主界面(在此選擇客戶端還是服務器端)
---BlueClient.java //客戶端界面,繼承自Form,實現CommandListener接口
---BlueServer.java //服務器端界面,繼承自Form,實現CommandListener接口
---bluetooth
---BlueClientService.java //封裝了客戶端藍牙服務的類,實現Runnable和DiscoveryListener接口
---BlueServerService.java //封裝了服務器端藍牙服務的類,實現Runnable接口
好了,對於程序文件結構有了一定的了解後,來看下部分代碼吧:
BlueMessage.Java文件:
/**
* Midlet應用程序主類
* @author royen
* @since 2010.1.24
*/
public class BlueMessage extends MIDlet {
public BlueMessage() {
MainForm form=new MainForm(this);
Display.getDisplay(this).setCurrent(form);
}
/**
* 退出應用程序
*/
public void ExitMidlet() {
try{
this.destroyApp(true);
}
catch(Exception ex){
System.out.println("occur exception "+ex.getMessage());
}
}
/**
* 導航到其他界面
* @param dis
*/
public void NavigateTo(Displayable dis){
Display.getDisplay(this).setCurrent(dis);
}
protected void startApp() throws MIDletStateChangeException {
}
protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
}
protectedvoid pauseApp() {
// TODO Auto-generated method stub}
該文件中在構造函數中指出了MainForm為初始界面,並提供了ExitMidlet和NavigateTo應用程序級的函數供調用。
components包中的MainForm.java文件:
/**
* 程序主界面
* @author royen
* @since 2010.1.24
*/
public class MainForm extends Form implements CommandListener{
//應用程序主類
private BlueMessage parent=null;
//選擇項控件
private ChoiceGroup choiceGp=null;
//按鈕控件
private Command cmdSelect=null;;
private Command cmdExit=null;
//客戶端和服務器端
private BlueClient client=null;
private BlueServer server=null;
public MainForm( BlueMessage parent ) {
super("type select");
this.parent=parent;
FormLoad();
}
/**
* 窗體加載初始化
*/
private void FormLoad(){
//生成選擇項控件
choiceGp=new ChoiceGroup("select application type:",Choice.EXCLUSIVE);
choiceGp.append("client", null);
choiceGp.append("server", null);
//生成按鈕控件
cmdSelect=new Command("select",Command.OK,1);
cmdExit=new Command("exit",Command.EXIT,1);
//添加控件
this.append(choiceGp);
this.addCommand(cmdSelect);
this.addCommand(cmdExit);
//添加按鍵事件監聽
this.setCommandListener(this);
}
/**
* 按鈕事件處理函數
*/
public void commandAction(Command cmd, Displayable dis){
//點擊退出按鈕
if(cmd==cmdExit){
parent.ExitMidlet();
}
else if(cmd==cmdSelect){ //點擊選擇按鈕
switch(choiceGp.getSelectedIndex()){
case 0:
client=new BlueClient(parent,this);
parent.NavigateTo(client);
break;
case 1:
server=new BlueServer(parent,this);
parent.NavigateTo(server);
break;
default:
break;
}
}
}
}
Mainform類中主要提供Server和Client選項供用戶選擇,詳細代碼不解釋,最後會給出完整的源程序下載~
components包中的BlueClient.java文件內容:見文末
components包中的BlueServer.java文件內容:見文末
以上兩個類分別是客戶端和服務器端的界面類,所有的邏輯操作都被放到bluetooth包中的對應的類中了,幾個文件筆者都注釋的比較翔實了,所以暫不解釋。
bluetooth包中的BlueServerService.java文件:
/**
* 實現服務器端藍牙服務的類
* @author royen
* @since 2010.1.25
*/
public class BlueServerService implements Runnable{
//服務標示符
private final static UUID SERVER_UUID=new UUID("F0E0D0C0B0A000908070605040302010",false);
//流連接通知器
private StreamConnectionNotifier notifier;
//服務器端界面
private BlueServer serverForm;
//服務記錄
ServiceRecord serviceRecord;
public BlueServerService(BlueServer frm){
this.serverForm=frm;
}
/**
* 開啟服務線程
*/
public void run() {
boolean btReady=false;
try{
//獲取本地藍牙設備
LocalDevice localDevice=LocalDevice.getLocalDevice();
if(!localDevice.setDiscoverable(DiscoveryAgent.GIAC)){
System.out.println("set discoveryMode failed~");
return ;
}
notifier=(StreamConnectionNotifier)Connector.open(getConnectionStr());
serviceRecord=localDevice.getRecord(notifier);
btReady=true;
}
catch(Exception ex){
System.out.println("occur exception "+ex.getMessage());
}
if(!btReady){
System.out.println("bluetooth init failed~");
return ;
}
serverForm.appendInfo("service setup,waiting for connect...");
//切換界面
serverForm.changeForm();
while(true){
StreamConnection conn=null;
try{
conn=notifier.acceptAndOpen();
}
catch(Exception ex){
System.out.println("occur exception when accept connection~");
continue;
}
//開啟對連接的處理線程
new Thread(new ProcessConnection(conn)).start();
}
}
/**
* 獲取連接字符串
* @return
*/
private String getConnectionStr(){
StringBuffer sb=new StringBuffer("btspp://");
sb.append("localhost").append(":");
sb.append(SERVER_UUID.toString());
sb.append(";name=BlueMessage");
sb.append(";authorize=false");
return sb.toString();
}
/**
* 處理客戶端連接的線程
* @author royen
* @since 2010.1.25
*/
private class ProcessConnection implements Runnable{
//連接流
private StreamConnection conn=null;
public ProcessConnection(StreamConnection conn){
this.conn=conn;
}
public void run() {
try{
String inputStr=readInputString(conn);
serverForm.appendInfo("recive message from client: "+inputStr);
String outputString;
if(inputStr.startsWith("connect...")){
outputString="welcome...";
}
else{
//生成響應
outputString="server echo "+inputStr;
}
sendOutputString(outputString,conn);
conn.close();
}
catch(Exception ex){
System.out.println("occur exception ,message is "+ex.getMessage());
}
}
/**
* 讀取接受的數據
* @param conn
* @return
*/
private String readInputString(StreamConnection conn){
try{
DataInputStream dis=conn.openDataInputStream();
String msg=dis.readUTF();
dis.close();
return msg;
}
catch(Exception ex){
System.out.println("occur exception when read data~");
return ex.getMessage();
}
}
/**
* 發送反饋信息
* @param msg
* @param conn
*/
private void sendOutputString(String msg,StreamConnection conn){
try{
DataOutputStream dos=conn.openDataOutputStream();
dos.writeUTF(msg);
dos.close();
}
catch(Exception ex){
System.out.println("occur exception when send data~");
}
}
}
}
在該文件中需要解釋下的是SERVER_UUID,這個是全球用戶唯一標示符,在藍牙服務中我們服務器端其實就是將帶有這一唯一標識的服務發布出去,而客戶端則是根據這個UUID來在設備中搜索這一服務的。其分為長短標識,短標識說明采用的鏈接協議,長標識則代表服務的標識。其他的筆者都有比較詳細的注釋,這兒不再贅述。
bluetooth包中的BlueClientService.java文件:
/**
* 實現客戶端藍牙服務的類
* @author royen
* @since 2010.1.24
*/
public class BlueClientService implements Runnable,DiscoveryListener{
//目標服務標識
private final static UUID TARGET_UUID=new UUID("F0E0D0C0B0A000908070605040302010",false);
//存儲發現的設備和服務的集合
private Vector devices=new Vector();
private Vector records=new Vector();
//存儲服務屬性的集合
private UUID[] uuidSet=null;
//藍牙發現代理類
private DiscoveryAgent discoveryAgent=null;
//客戶端界面
private BlueClient clientForm;
//服務搜索的事務id集合
private int[] transIDs;
//存活的服務索引
private int activeIndex=-1;
public BlueClientService(BlueClient frm){
this.clientForm=frm;
}
public synchronized void run() {
//發現設備和服務的過程中,給用戶以Gauge
Gauge g=new Gauge(null,false,Gauge.INDEFINITE,Gauge.CONTINUOUS_RUNNING);
clientForm.append(g);
//如果初始化失敗
if(!initLocalDevice()){
clientForm.appendInfo("bluetooth init failed~");
return;
}
//生成服務和屬性的全球唯一標示符
uuidSet=new UUID[2];
uuidSet[0]=new UUID(0x1101); //0x1101表示 采用btspp協議
uuidSet[1]=TARGET_UUID; //目標服務標示
try{
discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);
}
catch(Exception ex){
clientForm.appendInfo(ex.getMessage());
return;
}
try{
//阻塞,由inquiryCompleted函數回調喚醒
wait();
}
catch(InterruptedException ex){
clientForm.appendInfo(ex.getMessage());
return;
}
//添加顯示信息
clientForm.appendInfo("search device completed,find "+devices.size()+" devices~");
clientForm.appendInfo("now begin to search service...");
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 ex){
continue;
}
}
try{
//阻塞,由serviceSearchCompleted函數回調喚醒
wait();
}
catch(InterruptedException ex){
clientForm.appendInfo(ex.getMessage());
return;
}
//添加顯示信息
clientForm.appendInfo("service search finished~,find "+records.size()+" services");
if(records.size()>0){
//搜索到服務,改變客戶端界面
clientForm.changeForm();
}
//獲取存活的服務索引
activeIndex=getActiveService();
}
/**
* 獲取存活的服務索引
* @return
*/
private int getActiveService(){
for(int i=0;i<records.size();i++){
ServiceRecord sr=(ServiceRecord)records.elementAt(i);
if(accessService(sr,"connect...")){
return i;
}
}
return -1;
}
/**
* 發送信息到服務器
* @param msg
*/
public void sendMsg(String msg){
accessService((ServiceRecord)records.elementAt(activeIndex),msg);
}
/**
* 訪問指定的服務
* @param sr
* @return
*/
private boolean accessService(ServiceRecord sr,String msg){
boolean result=false;
try {
String url = sr.getConnectionURL(
ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
StreamConnection conn = (StreamConnection) Connector.open(url);
DataOutputStream dos=conn.openDataOutputStream();
dos.writeUTF(msg);
dos.close();
DataInputStream dis=conn.openDataInputStream();
String echo=dis.readUTF();
dis.close();
clientForm.appendInfo("echo from server:"+echo);
result=true;
}
catch (IOException e) {
System.out.println("exception here");
}
return result;
}
/**
* 初始化本地藍牙設備
* @return
*/
private boolean initLocalDevice(){
boolean isReady=false;
try{
LocalDevice localDevice=LocalDevice.getLocalDevice();
discoveryAgent=localDevice.getDiscoveryAgent();
//設置自身設備不可訪問性
localDevice.setDiscoverable(DiscoveryAgent.NOT_DISCOVERABLE);
isReady=true;
}
catch(Exception ex){
isReady=false;
}
return isReady;
}
/**
* 設備發現
*/
public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
if(devices.indexOf(btDevice)==-1){
devices.addElement(btDevice);
}
}
/**
* 搜索完畢
*/
public void inquiryCompleted(int disType){
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;
}
}
//如果對所有的設備的服務都搜索完畢
boolean finished=false;
for(int i=0;i<transIDs.length;i++){
if(transIDs[i]==-1){
finished=true;
break;
}
}
if(finished){
synchronized(this){
notify();
}
}
}
}
這個文件中需要解釋的是BlueClientService類實現了DiscoveryListener接口,由於客戶端需要進行周圍藍牙設備以及發布的服務搜索,所以必須實現DiscoveryListener接口的四個回調函數,分別為deviceDiscovered(搜索設備時候每發現一個設備時回調)、inquiryCompleted(搜索設備結束時回調)、servicesDiscovered(搜索服務時候回調)和 serviceSearchCompleted(服務搜索完畢的時候回調)。
本文配套源碼