ImageShare MIDlet主类: import java.io.IOException; import javax.microedition.lcdui.*; import javax.microedition.media.*; import javax.microedition.media.control.VideoControl; import javax.microedition.midlet.MIDlet; public class BTMIDlet extends MIDlet implements CommandListener { private Display display = null; private List menu = null; private Form form = null; private Player player = null; private VideoControl vc = null; private Thread server = null; // 存储拍照的图片数据 private byte[] image = null; private Command captureCommand = new Command("capture", Command.OK, 1); private Command backCommand = new Command("back", Command.BACK, 1); private Command sendCommand = new Command("send by bluetooth", Command.OK, 1); private Command exitCom = new Command("exit", Command.EXIT, 1); private int picCount = 1; public static final String[] MENUS = { "receiver picture/Server", "send picture/Client" }; public void startApp() { if (display == null) { display = Display.getDisplay(this); menu = new List("share", List.IMPLICIT, MENUS, null); menu.addCommand(exitCom); menu.setCommandListener(this); } // 显示主菜单 display.setCurrent(menu); } public void pauseApp() { } public void destroyApp(boolean unconditional) { releasePlayer(); notifyDestroyed(); } private void startPlayer() { if (player == null) { try { // 创建Player player = Manager.createPlayer("capture://video"); player.realize(); vc = (VideoControl) player.getControl("VideoControl"); if (vc != null) { // 把Item追加到Form上 // initDisplayMode()设定Video的播放模式:USE_GUI_PRIMITIVE form.append((Item) vc.initDisplayMode( VideoControl.USE_GUI_PRIMITIVE, null)); } player.start(); form.addCommand(captureCommand); } catch (IOException ex) { ex.printStackTrace(); } catch (MediaException ex) { ex.printStackTrace(); } } } // 非常重要,释放player private void releasePlayer() { if (player != null) { player.close(); player = null; } } // 用于客户端发送图片时,返回图片数据数组 public byte[] getImage() { return image; } public static Alert getAlert(String msg, AlertType type, int timeout) { // public Alert(String title, String alertText, Image alertImage, // AlertType alertType) Alert a = new Alert("alert", msg, null, type); a.setTimeout(timeout); return a; } // 服务器启动后 回调此函数 public void btServerReady() { form .append("device is ready and waiting other bluetooth device to send picture..."); } // 接收到图片后回调此函数 public void imageReceived(byte[] data) { Image img = Image.createImage(data, 0, data.length); // 显示图片序列, "/n"可以让文字与图片隔行显示 form.append("第" + String.valueOf(picCount++) + "张图片 /n"); // Adds an item consisting of one Image to the Form. form.append(img); form.append("/n/n/n"); } public void commandAction(Command command, Displayable displayable) { if (command.getCommandType() == Command.EXIT) { destroyApp(false); // Used by an MIDlet to notify the application management software // that it has entered into the Destroyed state. notifyDestroyed(); } else if (command == List.SELECT_COMMAND) { int index = menu.getSelectedIndex(); if (form == null) { form = new Form(""); } // Deletes all the items from this Form, leaving it with // zero items. This method does nothing if the Form is // already empty. // 只删除Item,并不删除加在此Form上的Command form.deleteAll(); if (index == 0) { // 启动服务器端 form.setTitle("receive picture"); BTServer bts = new BTServer(this); if (server == null) { server = new Thread(bts); } server.start(); } else if (index == 1) { // 启动客户端,准备拍照 form.setTitle("capture"); startPlayer(); form.addCommand(backCommand); } form.setCommandListener(this); display.setCurrent(form); } else if (command == captureCommand) { // 拍照 new Thread() { public void run() { try { // Get a snapshot of the displayed content. // imageType( null ) - Format and resolution of the // returned image. If null is given, the default capture // format is used. image = vc.getSnapshot(null); } catch (MediaException ex) { ex.printStackTrace(); } // 清除以前的拍照屏幕 form.deleteAll(); form.append(Image.createImage(image, 0, image.length)); form.addCommand(sendCommand); form.removeCommand(captureCommand); // 不要忘记释放Player releasePlayer(); } }.start(); } else if (command == sendCommand) { // 经过蓝牙发送图片 BTClient client = new BTClient(this); } else if (command == backCommand) { showMainForm(); } } public void showMainForm() { // 先删除所有的Item form.deleteAll(); // 再删除所有的项命令 form.removeCommand(captureCommand); form.removeCommand(sendCommand); // 回收form资源 form = null; display.setCurrent(menu); releasePlayer(); } } BTServer: import java.io.*; import java.util.Vector; import javax.bluetooth.*; import javax.microedition.io.*; public class BTServer implements Runnable { // 存储BTMIDlet实例,方便回调 private BTMIDlet midlet = null; private LocalDevice device = null; private StreamConnectionNotifier server = null; private ConnectionHandle handle = null; private boolean stop = false; // private static int count = 0; public BTServer(BTMIDlet _midlet) { this.midlet = _midlet; } public void run() { try { device = LocalDevice.getLocalDevice(); device.setDiscoverable(DiscoveryAgent.GIAC); UUID u = new UUID(0x0001); // 启动SPP服务器,创建并打开服务链接 server = (StreamConnectionNotifier) Connector .open("btspp://localhost:" + u.toString()); // Form提示服务准备好,等待其他蓝牙设备进行图片传送 midlet.btServerReady(); handle = new ConnectionHandle(); while (!stop) { // 此循环不停止,不断检测从客户端传送来的StreamConnection连接,并将ServiceRecord注册到SDDB, // 此句用于测试,只有当有链接时才会有输出,证明了此循环并非一直处于空转状态 // System.out.println("testCycle: " + ++count); StreamConnection conn = null; try { // Returns a StreamConnection object that represents a // server side socket connection. The method blocks until a // connection is made. // 所以并不是让CPU一直处于调整空转状态,而是在有链接时循环执行一次, // 此次循环执行完毕,如果还有链接,则循环继续执行, // 如果没有,则该方法处于阻塞状态,直到等待下一个链接 conn = server.acceptAndOpen(); } catch (IOException ex) { ex.printStackTrace(); continue; } // 处理客户端的连接 handle.addConnection(conn); } } catch (BluetoothStateException ex) { //LocalDevice.getLocalDevice() && device.setDiscoverable() throw BluetoothStateException ex.printStackTrace(); } catch (IOException ex) { //Connector.open() && server.acceptAndOpen() throw IOException ex.printStackTrace(); } } // 根据连接的类型,进行不同方式的处理 //接收图片 private void processConnection(Connection _conn) { if (_conn instanceof StreamConnection) { StreamConnection conn = (StreamConnection) _conn; try { // 流连接 InputStream is = conn.openInputStream(); byte[] buffer = new byte[1024]; int ch = -1; ByteArrayOutputStream baos = new ByteArrayOutputStream(); while ((ch = is.read(buffer)) != -1) { baos.write(buffer, 0, ch); } byte[] img = baos.toByteArray(); //至此,Server完全收到图片数据,剩下的工作只是将byte数组转换为Image并显示在Form上 midlet.imageReceived(img); is.close(); baos.close(); conn.close(); } catch (IOException ex) { ex.printStackTrace(); } } else if (_conn instanceof L2CAPConnection) { L2CAPConnection conn = (L2CAPConnection) _conn; try { // 要充分考虑最大接受单元,避免丢失数据 int max = conn.getReceiveMTU(); byte[] buffer = new byte[max]; int len = 0; ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 在调用 receive()方法之前,首先调用ready()方法检查是否有数据包可被读取 while (conn.ready() && (len = conn.receive(buffer)) != -1) { baos.write(buffer, 0, len); } byte[] img = baos.toByteArray(); midlet.imageReceived(img); baos.close(); conn.close(); } catch (IOException ex) { ex.printStackTrace(); } } } private class ConnectionHandle implements Runnable { private Thread t = null; // 存储客户端连接 private Vector conns = new Vector(); ConnectionHandle() { t = new Thread(this); t.start(); } public void run() { while (!stop) { // 如果conns为空,即没有设备链接,则线程进入等待状态 synchronized (this) { try { if (conns.size() == 0) { // 该线程进入Blocked状态,直到被notify() wait(); } } catch (InterruptedException ex) { ex.printStackTrace(); } } StreamConnection conn = null; synchronized (this) { // 取出第一个连接并删除掉,这是因为上面的if (conns.size() == 0) // 判断,删除掉之后,conns.size重新归零 conn = (StreamConnection) conns.elementAt(0); conns.removeElementAt(0); // 开始处理 processConnection(conn); } } } void addConnection(StreamConnection _conn) { // 新连接到来,唤醒线程处理 synchronized (this) { conns.addElement(_conn); // 随机通知在等待队列中等待的线程进入Runnable状态, // 由于服务器端就main线程和ConnectionHandle两个线程, // 所以ConnectionHandle线程被激进入Runnable状态, // 进而获得可执行的机会进入Running状态.即中止上面if(conns.size() == 0)时所执行的wait()动作 notify(); } } } } BTClient import java.io.*; import java.util.*; import javax.bluetooth.*; import javax.microedition.io.Connector; import javax.microedition.io.StreamConnection; import javax.microedition.lcdui.*; public class BTClient implements DiscoveryListener, CommandListener { private BTMIDlet midlet = null; // 本地设备 private LocalDevice bt = null; // 显示查找到的设备列表 private List devicesList = null; // 存储查找到的设备,以便后面查找设备上的服务 private Vector deviceVector = new Vector(); // 存储我们感兴趣的服务 private ServiceRecord sr = null; private ClientHandle handle = null; public BTClient(BTMIDlet _midlet) { midlet = _midlet; devicesList = new List("inquring devices...", List.IMPLICIT); // 开始查找设备 initBluetooth(); Display.getDisplay(midlet).setCurrent(devicesList); } private void initBluetooth() { try { bt = LocalDevice.getLocalDevice(); bt.setDiscoverable(DiscoveryAgent.GIAC); // 查找设备 // ①: System.out.println("bt begin startInquiry"); bt.getDiscoveryAgent().startInquiry(DiscoveryAgent.GIAC, this); // ①: System.out.println("bt startInquiry after"); } catch (BluetoothStateException ex) { ex.printStackTrace(); showException(ex); } } // 显示异常信息 private void showException(Exception ex) { Alert a = BTMIDlet.getAlert(ex.toString(), AlertType.ERROR, 2000); Display.getDisplay(midlet).setCurrent(a); } public void deviceDiscovered(final RemoteDevice remoteDevice, DeviceClass deviceClass) { /* * 每搜索到一个设备,则这个方法就会被调用一次 * * 标有“①”等的System.out.println()语句为测试语句,测试在本类中实现的DiscoveryListener接口中的四个方法 * (deviceDiscovered → inquiryCompleted → servicesDiscovered → * serviceSearchCompleted)的在运行startInquiry()后的自动调用顺序,因为输出顺序为: bt * beginstartInquiry → bt startInquiry after → deviceDiscovered start → * deviceDiscovered end → inquiryCompleted start → inquiryCompleted end * →(启动DiscoveryAgent.searchServices()后) → servicesDiscovered start → * servicesDiscovered end → serviceSearchCompleted start */ // ②: System.out.println("deviceDiscovered start"); // getFriendlyName()在某些机型中耗费时间,在单独线程中调用 new Thread() { public void run() { try { devicesList.append(remoteDevice.getFriendlyName(false), null); // 将设备存储在deviceVector中 deviceVector.addElement(remoteDevice); } catch (IOException ex) { ex.printStackTrace(); showException(ex); } } }.start(); // ②: System.out.println("deviceDiscovered end"); } // 设备查询完毕将被调用 public void inquiryCompleted(int discoveryType) { // ⑤: System.out.println("inquiryCompleted start"); switch (discoveryType) { case INQUIRY_COMPLETED: devicesList.setTitle("devices"); break; case INQUIRY_ERROR: Alert alert = BTMIDlet.getAlert("query devices error", AlertType.ERROR, 2000); // 在alert显示两秒后,alert消失,屏幕自动转到devicesList Display.getDisplay(midlet).setCurrent(alert, devicesList); break; case INQUIRY_TERMINATED: { break; } default: break; } // 查找完成后再为设备列表添加CommandListener devicesList.addCommand(new Command("back", Command.BACK, 1)); devicesList.setCommandListener(this); // ⑤: System.out.println("inquiryCompleted end"); } public void servicesDiscovered(int transID, ServiceRecord[] serviceRecord) { // 只存储第一个发现的服务 // ③: System.out.println("servicesDiscovered start"); if (serviceRecord.length != 0) { sr = serviceRecord[0]; } // ③: System.out.println("servicesDiscovered end"); } public void serviceSearchCompleted(int transID, int respondCode) { // ④: System.out.println("serviceSearchCompleted start"); String message = ""; switch (respondCode) { case DiscoveryListener.SERVICE_SEARCH_COMPLETED: // 查询完成后,启动客户端 if (sr != null) { devicesList.setTitle("sending picture..."); // handle必须在sr不为空的情况下再进行创建对象, // 否则sr.getConnectionURL将有NullPointerException异常 handle = new ClientHandle(); return; } message = "service search completed"; break; /* * 如果不是非serviceSearchCompleted,否则下面的语句永远不会被执行,因为有return作用 */ case DiscoveryListener.SERVICE_SEARCH_TERMINATED: message = "user cancel the search"; break; case DiscoveryListener.SERVICE_SEARCH_ERROR: message = "error when search service"; break; case DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE: message = "the device is not reachale"; break; case DiscoveryListener.SERVICE_SEARCH_NO_RECORDS: message = "don't find any record"; break; default: break; } devicesList.setTitle("no service"); Alert a = BTMIDlet.getAlert(message, AlertType.INFO, 2000); Display.getDisplay(midlet).setCurrent(a); // ④: System.out.println("serviceSearchCompleted end"); } public void commandAction(Command command, Displayable displayable) { if (command.getCommandType() == Command.BACK) { // 返回 midlet.showMainForm(); } else if (command == List.SELECT_COMMAND) { int i = devicesList.getSelectedIndex(); UUID[] uuids = new UUID[1]; uuids[0] = new UUID(0x0001); try { // 查询指定设备上的服务 devicesList.setTitle("looking for services..."); // Searches for services on a remote Bluetooth device that have // all the UUIDs specified in uuidSet.Returns: // the transaction ID of the service search // System.out.println(bt.getDiscoveryAgent().searchServices(null, // uuids, (RemoteDevice) deviceVector.elementAt(i), this)); bt.getDiscoveryAgent().searchServices(null, uuids, (RemoteDevice) deviceVector.elementAt(i), this); } catch (BluetoothStateException ex) { ex.printStackTrace(); showException(ex); } } } private class ClientHandle implements Runnable { private Thread t = null; ClientHandle() { t = new Thread(this); t.start(); } public void run() { // 通过ServiceRecord连接服务器 System.out.println("sr.getConnectionURL begin"); String url = sr.getConnectionURL( ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false); int i = url.indexOf(":"); String protocol = url.substring(0, i); // 根据不同的协议,采用不同的发送数据方式 if (protocol.equals("btspp")) { StreamConnection conn = null; try { // 流连接情况下发送相对简单 // 创建并打开链接 conn = (StreamConnection) Connector.open(url); // Open and return an output stream for a connection OutputStream os = conn.openOutputStream(); // 将图像数组写进输出流 os.write(midlet.getImage()); // 刷新此输出流,并强制any buffered output bytes to be written out. os.flush(); os.close(); conn.close(); } catch (IOException ex) { ex.printStackTrace(); } } else if (protocol.equals("btl2cap")) { L2CAPConnection conn = null; try { conn = (L2CAPConnection) Connector.open(url); // L2CAPConnection是面向连接的,需要考虑MTU int max = conn.getTransmitMTU(); byte[] img = midlet.getImage(); byte[] buffer = new byte[max]; int index = 0; // 每次发送一个缓冲区的数据,且不超过MTU while (index < img.length) { // 不足max的长度,需要截取一部分发送 if (img.length - index < max) { // img数组中最后一批数据的转移方法 buffer = new byte[img.length - index]; System.arraycopy(img, index, buffer, 0, img.length - index); } else { System.arraycopy(img, index, buffer, 0, max); } // 通过L2CAP链接数据包将数据发送出去 conn.send(buffer); index += max; } conn.close(); } catch (Exception ex) { showException(ex); } } } } }