上篇文章讲述了开发环境的搭建和一些相关知识的介绍,这篇文章准备介绍下怎样实现手机和手机之间通过蓝牙实现互联通信的程序,然后接下来的日子可能会写个简单的通过蓝牙互联的手机小游戏(其他的事情比较多,加上笔者比较懒,呵呵,见谅~)。
这个小程序时个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...")){ utputString="welcome..."; } else{ //生成响应 utputString="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;i0){ //搜索到服务,改变客户端界面 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 if(transIDs[i]==transID){ transIDs[i]=-1; } } //如果对所有的设备的服务都搜索完毕 boolean finished=false; for(int i=0;i if(transIDs[i]==-1){ finished=true; break; } } if(finished){ synchronized(this){ notify(); } } }}
这个文件中需要解释的是BlueClientService类实现了DiscoveryListener接口,由于客户端需要进行周围蓝牙设备以及发布的服务搜索,所以必须实现DiscoveryListener接口的四个回调函数,分别为deviceDiscovered(搜索设备时候每发现一个设备时回调)、inquiryCompleted(搜索设备结束时回调)、servicesDiscovered(搜索服务时候回调)和 serviceSearchCompleted(服务搜索完毕的时候回调)。
本文配套源码
用最少的悔恨面对过去