J2ME蓝牙和摄像头操作

huafenged 贡献于2011-07-06

作者 HUAFENGED  创建于2010-08-27 15:08:00   修改者FtpDown  修改于2010-08-27 15:21:00字数25823

文档摘要:目前,很多手机已经具备了蓝牙功能。虽然MIDP2.0没有包括蓝牙API,但是JCP定义了JSR82, Java APIs for Bluetooth Wireless Technology (JABWT).这是一个可选API,很多支持MIDP2.0的手机已经实现了,比如Nokia 6600, Nokia 6670,Nokia7610等等。对于一个开发者来说,如果目标平台支持JSR82的话,在制作联网对战类型游戏或者应用的时候,蓝牙是一个相当不错的选择。本文给出了一个最简单的蓝牙应用的J2ME程序,用以帮助开发者快速的掌握JSR82。该程序分别在2台蓝牙设备上安装后,一台设备作为服务端先运行,一台设备作为客户端后运行。在服务端上我们发布了一个服务,该服务的功能是把客户端发过来的字符串转变为大写字符串。客户端起动并搜索到服务端的服务后,我们就可以从客户端的输入框里输入任意的字符串,发送到服务端去,同时观察服务端的反馈结果。<br><br> 本文并不具体讲述蓝牙的运行机制和JSR82的API结构,关于这些知识点,请参考本文的参考资料一节,这些参考资料会给你一个权威的精确的解释
关键词:

蓝牙 目前,很多手机已经具备了蓝牙功能。虽然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;i0){            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(            "F0E0D 0C0B0A000908070605040302010", 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.printStac kTrace();                    }        // 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 Cl ientProcessor 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.toS tring());    }        /**     * 处理客户端连接     * @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. http://developers.sun.com/techtopics/mobility/apis/articles/bluetoothintro/  JSR82 API介绍(英文) 2. http://www.j2medev.com/Article/ShowArticle.asp?ArticleID=249  JSR82 API 介绍(中文) 3. http://www.jcp.org/en/jsr/detail?id=82  JSR82 Specification. 4.WTK22, BluetoothDemo 项目 蓝牙是一种低成本、短距离的无线通信技术。对于那些希望创建个人局域网(PANs)的人们来说,蓝牙技术已经越来越流行了。每个个人局域网都在独立设备的周围被动态地创建,并且为蜂窝式电话和PDA等设备提供了自动连接和即时共享数据的能力。      为了在Java平台上开发支持蓝牙技术的软件,JCP定义了JSR82标准--Java蓝牙无线技术APIs(JABWT)。      在这篇文章中,我将介绍一些关于蓝牙技术的背景,概述一下支持蓝牙技术的MIDlet应用程序的典型要素,然后介绍给你核心的Java蓝牙APIs。最后我们展示一些代码来演示如何使用这些APIs。      实际上JSR82定义了两个独立的可选包:核心蓝牙API和对象交换(OBEX)API。这篇文章将对这两个中更为普遍的部分--核心蓝牙包javax.bluetooth进行详细地阐述,而OBEX API(对象交换),我们留到以后去讨论。      蓝牙无线电技术基于在工业、科学以及医学(ISM)上公用的2.45GHz开放频段,这一频段无需授权并全球通用。当蓝牙设备互相连接时,他们将组成一个微微网(piconet),即以一个主设备和最大7个从设备的形式动态创建网络。蓝牙也支持piconet网之间的连接:当一个piconet中的主设备成为另一个piconet的从设备时,piconet与piconet间将形成桥接。      蓝牙 协议栈提供了一组的高层协议和API以完成发现服务和模拟串行I/O,还有一个关于包分割和重组的低层协议以及多路技术协议和质量服务。蓝牙互操作性profiles--不要与J2ME profiles搞混--它是用来描述跨平台互操作性和一致性需求的。      蓝牙互操作性profiles包括三方面内容:通用访问profile(GAP)定义了设备管理功能性;服务发现应用profiles定义了服务发现方面的内容,串口profiles定义了互操作设备和模拟串口电缆的能力。你可以通过蓝牙规范(Bluetooth specification)学习这些和其它的profiles。蓝牙栈包含一个软件栈来映射一个固件栈(firmware),由图1所示:       图片1:蓝牙协议栈      JSR82揭示了蓝牙软件栈给Java平台的开发者。其中引起我们兴趣的是服务发现协议(SDP),用来模拟串口的串口profile RFCOMM,向上层协议提供诸如分割和重组等导向性连接的数据转换操作的逻辑链路控制及适配profile(L2CAP),以及多路技术协议。注意JABWT不支持无连接L2CAP。      JABWT也包括对象交换API。OBEX也是高层API,它用来交换对象数据,诸如电子商业卡和日历标签之间以vCard和vCalendar的格式进行数据传输。在蓝牙上,对象交换通过RFCOMM发生。OBEX在最开始时是由红外(IrDA)引入的,并且它可以在IrDA协议、TCP/IP或者是其他协议的顶层实现。       典型的蓝牙应用程序实例      一个开启蓝牙功能的应用可以作为一个服务端或是一个客户端--一个服务的提供者或是消费者,或者它可以作为一个真正的点对点终端同时表现出服务和客户的行为。图2所示一个蓝牙规范用例:       图2:一个典型的具有蓝牙功能的实际用例      对这些用例的简要介绍:      .初始化--所有具备蓝牙功能的应用程序必须先要初始化蓝牙栈。      .客户端--一个客户对远端服务进行消费。首先它要发现所有附近的设备,然后对于每一个发现的设备搜索它感兴趣的服务。      .服务器端--一个为客户端提供服务的服务器。它在服务发现数据库(SDDB)中对客户端进行注册,对他们进行有效广播。然后等待引入的连接,在他们进入时接受他们并为他们提供服务。最后,当不再需要服务时,应用程序会在服务发现数据库(SDDB)中将他们移除。图三:用例中参与活动的图表:       图 3: 蓝牙应用程序活动图      蓝牙应用程序中的元素      图4显示了在MIDlet中一个典型蓝牙功能应用程序中的一些元素:       图4: 具有蓝牙功能的 MIDlet (高端组织)      中间的是核心应用程序My Bluetooth MIDlet,它扩展自javax.microedition.midlet.MIDlet。没有显示出来的还有MIDlet中实现的javax.microedition.lcdui.CommandListener以监听从用户接口中输入的命令。应用程序使用的剩余的类和接口都包含在了蓝牙规范中,像设备发现和服务,连接和服务消费,还有广播和提供服务。      使用诸如MVC等设计模式是很好的实践。MVC把应用程序分解成用户接口(视图),应用程序行为和导航(控制器),以及数据(模型),当然在我们的案例中还要加上蓝牙API的支撑类和接口。像将分离的客户端、服务端行为构建成独立的类以便以后可以重用这些组件,也是很好的设计。       Java蓝牙API核心概述      JSR82需求的"最小公分母"是 受限连接设备配置(CLDC),可靠连接设备配置(CDC)是CLDC的超集,所以JABWT可以同时在CLDC和CDC上实现,简要地说,你可以在使用任何J2ME profile的上使用JABWT。在javax.bluetooth中我们可以发现,Java蓝牙API可以被分解为三个部分,在下面我们将讨论到它们:发现、设备管理和通信。      蓝牙发现API      客户端程序使用蓝牙发现API以搜索在其附近的设备和服务。服务代理类(DiscoveryAgent)同时支持设备与服务的发现。当设备和服务被发现时,想得到通知的客户端应用程序必须实现并注册DiscoveryListener接口,这个接口定义了设备发现通知和服务发现通知的回调。发现代理(DiscoveryAgent)与蓝牙客户端应用程序之间是典型的一对一的关系:       图 5: DiscoveryAgent类和DiscoveryListener接口      设备发现API      你使用DiscoveryAgent类的"设备发现"方法来开始和取消设备发现:      .retrieveDevices()重新获得已经发现或者附近的已知设备      .startInquiry() 启动发现附近设备,也叫inquiry      .cancelInquiry()取消当前进行的任何请求      蓝牙发现代理在请求阶段的不同时候会分别调用DiscoveryListener(发现监听器)不同的回调方法:      .deviceDiscovered() 指出是否有设备被发现。      .inquiryCompleted() 指出是否请求已经成功、触发一个错误或已被取消。      在图6中的状态图表阐明了设备发现的状态改变结束于相应的回调方法的返回。       图 6: 设备发现状态表      设备发现以调用startInquiry()函数开始。在请求进行时,蓝牙发现代理会在适当的时候调用回调方法DeviceDiscovered()和inquiryCompleted()。      服务发现API      你可以使用发现代理的服务发现方法来开始或取消服务发现:      . selectService()启动服务发现搜索。(根据API手册应为尝试定位一个服务)      . searchServices()启动服务发现搜索。      . cancelServiceSearch()取消在正在进行中的任何的服务发现搜索操作。蓝牙发现代理在服务发现阶段的不同时候会分别调用DiscoveryListener的服务发现回调方法:      . servicesDiscovered() 表示是否服务已被发现。      . serviceSearchCompleted()表示服务发现是否已经完成。      图7阐明了服务发现的状态改变结束于DiscoveryListener的回调方法的返回。       图 7: 服务发现状态图表      服务发现开始于对searchServices()的调用。当服务搜索进行时,蓝牙发现代理会在适当的时候回调servicesDiscovered()和 serviceSearchCompleted()方法。      除了DiscoveryAgent和DiscoveryListener了,你在服务发现过程中还要使用到的类有UUID,ServiceRecord以及DataElement等。      UUID类在蓝牙中,每个服务和服务属性都唯一地由"全球唯一标识符"(UUID)来校验。正如它的名字所暗示的,每一个这样的标识符都要在时空上保证唯一。UUID类可表现为短整形(16或32位)和长整形(128位)UUID。      他提供了分别利用String和16位或32位数值来创建类的构造函数,提供了一个可以比较两个UUID(如果两个都是128位)的方法,还有一个可以转换一个UUID为一个字符串的方法。UUID实例是不可改变的(immutable),只有被UUID标示的服务可以被发现。      在Linux下你用一个命令uuidgen -t可以生成一个UUID值;在Windows下则执行命令uuidgen 。UUID看起来就像如下的这个形式:2d266186-01fb-47c2-8d9f-10b8ec891363。当使用生成的UUID去创建一个UUID对象,你可以去掉连字符。       SDDB和ServiceRecord接口      在服务发现的中心是服务发现数据库(SDDB)和服务发现协议(SDP)。SDDB由蓝牙实现负责维护的数据库。它包含了服务记录(service records),后者代表了对客户端有效的服务。SDP对于基于JABWT应用程序来说是透明的;可以这么说,SDP是用于服务发现的。为重新获取服务纪录,一个本地设备SDP客户端会向一个远端设备上SDP服务器发出请求。       图 8: SDDB      每一笔服务记录都会由一个ServiceRecord的实例来表现。这个记录包含了描述服务细节的属性。这个类提供了几种有用的方法:      .getAttributeIDs() 和 getAttributeValue()方法返回服务记录的属性。      .getConnectionURL()方法获取链接的URL 数据元素类      一个服务可以有许多的属性,一些是强制性的,其他的是可选的。一个服务属性由一个数据元素对象来表现,这个数据元素对象提供了设置并取得属性值的方法。      强制性属性是在注册一个服务之后被自动设定的。这些属性包括:      ServiceRecordHandle,ServiceClassIDList,ServiceRecordState,ServiceID,还有ProtocolDescriptorList。      如果你想要的话,还可以设置可选属性。可选属性有很多,但是有三个值得关注:ServiceName,ServiceDescription,和ProviderName。想得到更多的关于这些属性的信息,请参看JABWT的文档或蓝牙规范。      设备管理API      有3个主要的类来支持设备管理:      .LocalDevice      .RemoteDevice      .DeviceClass      本地设备类LocalDevice类标识了本地蓝牙设备。蓝牙应用程序和LocalDevice之间的关系是典型的一对一关系:       图 10:本地设备类      本地设备提供了方法来返回关于本地设备的信息,并且能够进入Bluetooth manager:      .getBluetoothAddress()返回蓝牙设备地址。      .getDeviceClass()返回设备类。      .getFriendlyName()返回设备友好名称,蓝牙设备名通常是用户在蓝牙控制中心为其设置的我们将会在后面看到。      .getRecord()返回一个指定蓝牙连接的服务记录。      .updateRecord()方法用来为指定的ServiceRecord更新SDDB服务记录。      .getDiscoverable()返回设备的可发现状态。      .setDiscoverable()设置设备的可发现状态。      .getDiscoveryAgent()返回一个参考给发现代理。      .getProperty()返回一个设备的蓝牙属性。      通过调用getProperty()方法你可以得到的属性包括:      .bluetooth.api.version,蓝牙API版本      .bluetooth.sd.attr.retrievable.max,一次性能够被获得的服务记录属性的最大值      .bluetooth.connected.devices.max,支持的连接设备的最大值      .bluetooth.sd.trans.max,同时发生的服务发现处理的最大值      .bluetooth.l2cap.receiveMTU.max,L2CAP最大发射单元      你可以在 Javadoc文档中或是规范中学习更多的有关蓝牙属性的内容。      远端设备类      一个RemoteDevice的实例代表了一个远端蓝牙设备。在一个蓝牙客户端应用程序可以进行服务,消费之前,它必须发送一个设备请求来发现远端设备。典型的蓝牙应用程序和远端设备之间的关系是一对多:       图 11: 远端设备类      远端设备(RemoteDevice)提供的方法中,有些很类似于本地设备(LocalDevice)里提供的方法:      .getBluetoothAddress()返回蓝牙地址。      .getFriendlyName()返回蓝牙设备名。      .getRemoteDevice()返回相应的被指定蓝牙连接的远端设备。      .authenticate()尝试识别验证远端设备。      .authorize()为指定的蓝牙连接去尝试批准远端设备访问本地设备。      .encrypt()尝试为指定的蓝牙连接开启或关闭加密。      .isAuthenticated() 测试是否远端设备可以被验证。      .isAuthorized()测试是否远端设备已经被蓝牙控制中心授权访问本地设备以进行蓝牙连接。      .isEncrypted()测试是否本地设备和远端设备之间的通信被加密。      .isTrustedDevice()测试是否远端设备被蓝牙控制中心指定为可信任的。      DeviceClass类一个DeviceClass对象代表一个设备的设备类(CoD),例如一个打印机或者一部电话。CoD包括一个主类,一个辅的类,和服务类型或服务类。DeviceClass提供了如下方法:      .getMajorDeviceClass()方法获取设备的主类。      .getMinorDeviceClass()方法获取设备的辅类。      .getServiceClasses()获取设备的服务类。      当一个设备被发现,同时他的类也会被发现;当发现代理调用deviceDiscovered()时,其中一个参数就是DeviceClass。你可以通过它的getDeviceClass()方法找到本地设备的CoD。      蓝牙通信      JABWT连接是基于逻辑链路及适配层协议的。L2CAP是一个低级协议用来管理数据包,直到达到64k。L2CAP中的处理细节像消息分割和重组(SAR),多路连接。另外,串口Profile(SPP)提供RFCOMM,一个通过L2CAP层的串行模拟协议。      L2CAP和RFCOMM连接都是基于通用连接框架(GCF)的,直接通向接口和类的层次去创建连接和执行IO命令。JABWT依靠L2CAP和RFCOMM协议扩展了通用连接框架(GCF)以分别支持通过L2CAPConnection 和StreamConnection类型连接。      就在L2CAPConnection被JSR 82介绍的同时,StreamConnection在原始的javax.microedition.io的GCF中被定义了,它是依靠CLDC发展来的。注意,JABWT中L2CAPConnection仅支持面向连接的L2CAP连接。图12显示了以基于GCF形式的各接口通过蓝牙网络进行通信的过程:       图 12: 通用连接框架和蓝牙连接类型      层次定义了L2CAP和Stream的连接和连接通知器。一个连接定义了一个连接终端,当一个连接通知器执行了服务行为时,它会等待并接受L2CAP连接处理。      处理L2CAP连接比处理流连接更复杂。使用L2CAP时,开发者们必须处理好最大消息的大小(即最大传输单位,或叫做MTU)、打断和重组等。这些复杂的事项在开发者使用流连接的时候被隐藏了,以使他们处理蓝牙连通时获得更好的效率。      如同所有的GCF连接类型,你可以通过调用GCF连接工厂方法javax.microedition.io.Connector创建蓝牙连接。传递给Connector()方法的连接URL决定了要创建的连接类型:一个L2CAPConnection连接的URL格式:      btspp://hostname:[CN | UUID];   parameters      一个RFCOMMStreamConnection连接的URL格式:      btspp://hostname:[CN | UUID];   parameters      细节介绍:      .btl2cap 是为L2CAPConnection设计的URL配置方案。      .btspp是为RFCOMM StreamConnection的URL配置方案。      .hostname 既可是localhost用于架设一个服务器连接,也可是一个用于创建客户端连接的蓝牙地址。      .PSM是协议/服务多路复用值,在一个客户端连接 服务器端时使用。在概念上是模拟一个TCP/IP端口。      .CN是信道数值,在一个客户端连接服务器端时使用,也是模拟TCP/IP端口。      .UUID是UUID(通用唯一标识符)值,在一个服务器上建立服务时使用。      .parameters(参数)包括描述服务名称的名字和有于安全的参数:验证、授权和加密。服务器连接和客户端连接      在连接的URL中,主机名称告诉连接工厂是否它应该创建一个客户端异或服务器端。如果使用单词localhost作为主机名将定义一个服务器连接。客户端想要连接到一个指定的的服务可以通过调用ServiceRecord.getConnectionURL()来找到该服务连接的URL。      异常      javax.bluetooth核心API定义了三个异常类:      .当一个蓝牙L2CAP、RFCOMM或是OBEX-over-RFCOMM连接不能被成功建立会抛出BluetoothConnectionException异常。      .一个试图在错误状态下进行蓝牙操作时,会抛出BluetoothStateException异常。      .当在本地服务发现数据库(SDDB)中,添加或改变服务记录失败时,会抛出ServiceRegistrationException异常。      蓝牙安全      一个安全的蓝牙连接应该是经过验证、可选的获得授权和被加密过的。这样,蓝牙连接在建立之初或以后就可以保证其安全性了。      注意:不是所有的蓝牙实现都提供了安全连接的。为了在建立一个蓝牙连接时使其安全,就要给javax.microedition.io.Connector在连接URL字符串上提供适当的安全参数:      btspp://hostname:[CN | UUID];   authenticate=true;   authorize=true;   encrypt=true      Where:      .authenticate验证一个连接设备的身份。      .authorize授权一个连接中的设备(已被识别)是否被允许进入。      .encrypt指定连接需被加密。      你已经看到了想要连接到一个服务的客户端可以通过调用ServiceRecord.getConnectionURL()方法以获得服务连接的URL。该方法中的一个参数requiredSecurity,指定了返回的这个连接URL是否应该包含可选的authenticate和encrypt等安全参数。关于requiredSecurity的有效值为:      .ServiceRecord.NOAUTHENTICATE_NOENCRYPT      意为      authenticate=false; encrypt=false。      .ServiceRecord.AUTHENTICATE_NOENCRYPT      意为      authenticate=true; encrypt=false。      .ServiceRecord.AUTHENTICATE_ENCRYPT      意为      authenticate=true; encrypt=true。      例如:      ...   ServiceRecord sr = ...;   ...   String connURL = sr.getConnectionURL   (ServiceRecord.AUTHENTICATE 摄像头  现在支持J2ME的手机越来越多,同时不带摄像头的手机也越来越少。支持高版本J2ME和高像素摄像头无疑是手机的两个重要的卖点。而作为J2ME的开发人员,我们关心的是,我们能否把这二者结合起来,简单地说,就是在J2ME程序里使用摄像头来获取图象。      答案是肯定的。在扩展包MMAPI中,我们可以使用VideoControl加上Player来实现从手机摄像头捕获影象数据,并在屏幕上显示出来。      大家都知道,J2ME的用户界面总的来说分成两类,分别是高级界面和低级界面。高级界面使用方便,但自由度不大。低级界面自由度大,但都要靠程序自己来完成。在这两种界面上VideoControl都可以很好地工作,只需要在初始化时指定对应的工作模式。      函数Object initDisplayMode(int mode, Object arg)用来设置VideoControl的显示模式,其中参数mode表示显示的模式,arg则根据mode的取值而变化。目前已有两种显示模式被定义,分别是GUIControl.USE_GUI_PRIMITIVE和VideoControl.USE_DIRECT_VIDEO。如果使用USE_GUI_PRIMITIVE,则说明VideoControl将被放置在一个高级界面的容器中显示出来;相应,使用USE_GUI_PRIMITIVE则说明VideoControl将显示在低级界面上。      在使用USE_GUI_PRIMITIVE时,参数arg用于指定容器的类型。在仅仅支持一种UI的平台上,如J2ME,仅仅支持LCDUI这一种UI,则可以传一个null,系统将使用默认的UI;如果在某个支持多种UI(如AWT和LCDUI)的平台中,则需要指定使用何种UI,可以使用一个包含完整类名的字符串来作为参数arg,如"javax.microedition.lcdui.Item"。相应地,函数initDisplayMode此时返回的Object类型就是参数arg所指定的,或者默认的类型,在J2ME中,是Item。示例如下:      Player p;   VideoControl vc;   try{   p = Manager.createPlayer("capture://video");   p.realize();   vc = (VideoControl) p.getControl("VideoControl");   if (vc != null) {   append((Item)vc.initDisplayMode(VidecControl.USE_GUI_PRIMITIVE, null));//当前类继承了Form类   }   p.start();   }catch(Exception e){}      模式USE_DIRECT_VIDEO只能被用于LCDUI中,这个时候,参数arg必须是一个Canvas或者它的子类,同时函数将返回null。      Player p;   VideoControl vc;   try{   p = Manager.createPlayer("capture://video");   p.realize();   vc = (VideoControl) p.getControl("VideoControl");   if (vc != null) {   vc.initDisplayMode(VideoControl.USE_DIRECT_VIDEO, this);//当前类继承了Canvas类   }   vc.setVisible(true);//设置可见   p.start();   }catch(Exception e){}      这两种模式还有一个很大的区别是,在USE_GUI_PRIMITIVE模式下,默认是可见的,而在USE_DIRECT_VIDEO模式下,默认是不可见的,需要通过方法setVisible(boolean visible)来设置。      无论是哪种模式,我们都可以对画面的大小进行调整。通过方法setDisplaySize(int width, int height),我们可以设置显示画面的大小。这里注意,是设置显示画面的大小,也就是说,它不会影响到后面谈到的获取的快照的大小。在USE_DIRECT_VIDEO模式下,我们还可以设置画面的位置,方法setDisplayLocation(int x, int y)正是提供了这样的功能,两个参数分别是画面左上角的坐标。而画面的源大小、显示大小、显示位置等信息可以通过VideoControl提供的一些方法来获取,分别是getSourceWidth() 、getSourceHeight() 、getDisplayWidth() 、getDisplayHeight() 、getDisplayX() 、getDisplayY()。      到目前位置,我们所做的工作仅仅是如何控制一个摄像头来取景,而最后一个关键步骤则是获取一张静态图片。方法getSnapshot(String imageType)可以按照指定文件格式返回图片的数据,然后可以利用返回的数据创建一个图片。其中ImageType是图片格式,传进null则是默认的png图片。      byte[] data;   Image img=null;   data = vc.getSnapshot(null);   img = Image.createImage(data, 0, data.length);      如果想保存图片,可以存进RMS,或者发送至 服务器上,那已超出本文的范围。      这样,我们就可以利用摄像头的功能来丰富游戏的功能。据笔者所知,目前结合摄像头的游戏还不多,西门子SX1上有一款游戏,内容是打蚊子,游戏的背景就是摄像头实时拍摄到的画面,更重要的是,随着玩家移动或者摇晃手机,游戏能够计算出手机的移动方向,并改变游戏中蚊子的位置及瞄准器的位置,不能不让人击节叫好 在Java ME中通过蓝牙发现设备并传送文件 央邦0首付,低押金先就业后付款 JAVAV工程师权威认证 广州中星报MCSE送CCNA3600元 免费学习北大青鸟专业Java课程 Windows高级工程师的培训地 贺深圳北大青鸟信狮学校学员100%就业 IT专家网 佚名 2009-5-22 保存本文 推荐给好友 收藏本页 欢迎进入Java社区论坛,与200万技术人员互动交流 >>进入   在Java ME设备上执行蓝牙应用程序的首要步骤之一就是发现过程(discovery process)。简而言之就是,发现过程就是带有蓝牙的设备互相找到彼此的过程,然后一起携手找出它们各个可以支持的服务。下一步就是要学习如何在这些两两设备之间传送数据。   在本篇技术小文章中,我将向你展示如何创建一个可以互相查找设备的一个MIDlet,然后让用户发送一个简单的消息到其中一个被找到的设备中。我已经在Nokia N95的机器上测试并核实了这个MIDlet的工作了,通过启用蓝牙支持,它可以连接到一个运行Windows Vista的电脑上。   我把整个过程分成以下几个步骤:   1. 开始发现过程。   2. 查询在发现过程中找到的设备所支持的服务。   3. 使用支持服务的URL开始并处理一个OBEX数据交换。   以下各段将详细说明这些步骤。在这些步骤中遵循代码片段可以查阅这个MIDlet的整个源代码。源代码可以在最后的Resources下的压缩文件中获得。   第一步:开始发现过程   发现过程是用来告诉本地蓝牙堆栈可以和在附近任何蓝牙设备进行配对。在这个MIDlet中,这个堆栈可以通过你的设备提供者所提供的JSR 82来完成。这个发现过程通过发现在本地设备中的代理来开始的,如以下代码所示:         // get the local discovery agent   agent = LocalDevice.getLocalDevice().getDiscoveryAgent();   // start the inquiry for general unlimited inquiry   agent.startInquiry(DiscoveryAgent.GIAC, this);   一旦发现代理启动发现过程,它将在一个执行DiscoveryListener接口的类上调用各种调回方法。就我们而言,这是我们的MIDlet类。   具体来说,必须执行这个接口的四个方法,其中两个是在发现阶段我们所感兴趣的:deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) 和 inquiryCompleted(int discType)。这两个方法处理一个设备的发现并完成发现过程。在以下所展示的来自MIDlet的代码中,一旦它们被发现或是当程序结束的时候,我们使用这些方法来添加我们的设备上的UI。          public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {   try {   // add the devices using the friendly names   listofDevices.append(btDevice.getFriendlyName(false), null);   // add to the devices hashtable   devices.put(new Long(listofDevices.size()), btDevice);   } catch(Exception ex) { handleError(ex); }   }   public void inquiryCompleted(int discType) {   // once the inquiry is completed, show the list of devices to the user   if(listofDevices.size() == 0) {   display.setCurrent(nothing, noteBox);   } else {   display.setCurrent(listofDevices);   }       }  第二步: 在已发现的设备上开始服务发现(service discovery)   由于本文的目的是让数据从我们的MIDlet中传输到一个兼容的设备上,我们需要在已发现的设备上找到这些服务来实现这个目标。为了达到这个目的,我们需要在服务发现过程中定义正确的属性和UUIDs。以下代码将显示如何来做这个:          agent.searchServices(   null,   new UUID[] {new UUID(0x1105L)}, // we want the OBEX PUSH Profile   device,   this);   正如你所猜到的,这些代码使用我们以前用过的本地代理来查找设备。我们不是在一组特定的属性之后,所以我们需要用null作为第一个参数,但是UUID必须是OBEX PUSH配置文件,因为这是传输数据的一个最开放式的方法。   我们讲到DiscoveryListener接口有两个其他的方法可以用来利用发现的服务。这两个方法是servicesDiscovered(int transID, ServiceRecord[] servRecord) 和serviceSearchCompleted(int transID, int respCode)。正如名字所显示的那样,第一个方法是每当一个服务被发现时被调用,第二个方法当服务过程结束时被调用。   每当一个服务被发现的时候,我们需要找到每个设备上的特定的URL服务连接。这个URL连接将使OBEX连接用于我们的数据传送,而且由蓝牙硬件,设备地址组成。在以下的代码中,这个URL连接取自于servicesDiscovered方法:         String connURL = servRecord[i].getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);   通过这个URL连接,现在我们可以把传输数据的程序移动到已发现得设备上。   第三步:使用PBEX PUT传送数据   在这个MIDlet中,我们将运行用户输入一些文本作为一个消息,然后用已发现的设备或是服务来传送它们。为了做这个,我们需要在前一步骤中得到的URL连接(当然,消息数据是作为一个String的)。          // open a client session   ClientSession clientSession =   (ClientSession) Connector.open(connURL);   // connect using no headers   clientSession.connect(null);   if(rHeaders.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) {   // the connection could not be established   handleError(   new Exception("Remote client returned invalid response code: " +   rHeaders.getResponseCode()));   return;   }   // if we are here, then response code was ok   // create a new set of headers   HeaderSet headers = clientSession.createHeaderSet();   headers.setHeader(   HeaderSet.LENGTH,   new Long(noteBox.getString().length()));   headers.setHeader(HeaderSet.NAME, "myNote.txt");   headers.setHeader(HeaderSet.TYPE, "text/plain");   // create an operation using the headers we have just created   Operation op = clientSession.put(headers);   // on this operation, create the output stream   OutputStream out = op.openOutputStream();   // and send the note   out.write(noteBox.getString().getBytes());   为了发送该数据,client session被打开,而且建立一个空标题的连接。在这一点上,你的目标设备要求来自一个新设备的数据接收的确认。如果你以前从来没有配对的设备,也可能要求你提供密钥。   一旦连接建立,解释数据目的地一些标题会被创建,而且一个新的操作通过这些标题也会被创建。这个操作是通过OutputStream来进行传送数据的。   接收到的消息放在目标设备上的默认蓝牙交换文件夹中。

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档