多线程技术是java me中的关键技术,应用十分频繁,尤其是在游戏中。但是对于新手来说,又容易忽略或错误的使用多线程,导致程序堵塞,而无法响应用户的输入请求。
由于笔者对于游戏开发不是十分了解,所以本文将仅就多线程技术在java me应用程序中的使用展开讨论。本文主要包含如下部分:
- 多线程与联网
- 多线程与拍照
- timer与timertask
多线程与联网
手机中,所有的midlet程序都是由application manager software(ams)管理的。当midlet初始化后,ams就会调用midlet的startapp()方法,此时midlet就进入了acitive状态。在java me中有些操作可能会导致程序堵塞,比如连接网络等。如果这些操作与主程序在同一个主线程中完成,那么由于堵塞会造成程序长时间无法返回,也就无法响应用户的其他操作了。所以,如果我们在commandaction()中进行了联网的操作,则会造成如上所述的情况。
下面,将通过一个例子来演示如上的情况,并使用多线程最终解决此问题。这是一个“echo message”实例,手机端向服务器端发送一条消息,服务器得到此消息后直接返回给手机端。
首先,创建一个networkconnection类来封装联网相关的操作,这样,midlet中只需调用此类中的方法就可以完成联网的操作。代码如下:
| 以下是引用片段: /* * networkconnection.java * * created on 2006年7月20日, 下午2:54 * */ package nju.hysteria.thread.connection; import java.io.datainputstream; import java.io.dataoutputstream; import java.io.ioexception; import javax.microedition.io.connector; import javax.microedition.io.httpconnection; /** * * @author magic */ public class networkconnection { private static final string url = "http://localhost:8080/thread/"; private httpconnection httpconnection; private string message; public networkconnection(string message) { this.message = message; connect(); } /** * connect to web server. * */ public void connect(){ try { httpconnection = (httpconnection) connector.open(url); httpconnection.setrequestmethod(httpconnection.post); } catch (ioexception ex) { system.out.println("can not open connection!"); ex.printstacktrace(); } } /** * send message to server. * @throws java.io.ioexception */ public void sendmessage() throws ioexception{ dataoutputstream out = httpconnection.opendataoutputstream(); out.writeutf(message); out.close(); } /** * receive message from server. * @throws java.io.ioexception * @return */ public string receivemessage() throws ioexception { datainputstream in = httpconnection.opendatainputstream(); string message = in.readutf(); in.close(); return message; } /** * close connection. */ public void close(){ if(httpconnection!=null){ try { httpconnection.close(); } catch (ioexception ex) { ex.printstacktrace(); } } } } |
接着,我们写一个midlet调用类中的方法。malconnectionmidlet在commandaction()方法中直接调用networkconnection中的方法,而没有重新创建一个线程。代码如下:
| 以下是引用片段: /* * malconnectionmidlet.java * * created on 2006年7月20日, 下午2:53 */ package nju.hysteria.thread.connection; import java.io.ioexception; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; /** * * @author magic * @version */ public class malconnectionmidlet extends midlet implements commandlistener { private display display; private textbox text; private command showcommand; public malconnectionmidlet(){ display = display.getdisplay(this); text = new textbox("message","请使用‘问候’命令发送消息",100,textfield.any); showcommand = new command("问候",command.screen,1); text.addcommand(showcommand); text.setcommandlistener(this); } public void startapp() { display.setcurrent(text); } public void pauseapp() { } public void destroyapp(boolean unconditional) { } public void commandaction(command command, displayable displayable) { if(command==showcommand){ /** * 在当前的线程中直接进行联网操作,造成程序堵塞。 */ string message = null; networkconnection connection = new networkconnection("hello world"); try { connection.sendmessage(); message = connection.receivemessage(); connection.close(); } catch (ioexception ex) { ex.printstacktrace(); } text.setstring(message); } } } |
运行程序,如图1所示。当用户按下“问候”命令后,程序就僵死了,并在console窗口中得到如下警告:

图1
| 以下是引用片段: warning: to avoid potential deadlock, operations that may block, such as networking, should be performed in a different thread than the commandaction() handler. |
这就是因为没有使用多线程造成的。下面,就来看看如何使用多线程来解决此问题。
[nextpage]
新建类networkthread,它继承在thread,并将原先commandaction()中发送,接受消息的操作移到此类中完成。代码如下:
| 以下是引用片段: /* * networkthread.java * * created on 2006年7月20日, 下午4:16 * */ package nju.hysteria.thread.connection; import java.io.ioexception; import javax.microedition.lcdui.textbox; /** * * @author magic */ public class networkthread extends thread { private networkconnection connection; private textbox text; public networkthread(textbox text) { super(); this.text = text; } public void run() { string message = null; connection = new networkconnection("hello world"); try { connection.sendmessage(); message = connection.receivemessage(); connection.close(); } catch (ioexception ex) { ex.printstacktrace(); } text.setstring(message); } } |
| 以下是引用片段: /* * connectionmidlet.java * * created on 2006年7月20日, 下午2:53 */ package nju.hysteria.thread.connection; import java.io.ioexception; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; /** * * @author magic * @version */ public class connectionmidlet extends midlet implements commandlistener { private display display; private textbox text; private command showcommand; public connectionmidlet(){ display = display.getdisplay(this); text = new textbox("message","请使用‘问候’命令发送消息",100,textfield.any); showcommand = new command("问候",command.screen,1); text.addcommand(showcommand); text.setcommandlistener(this); } public void startapp() { display.setcurrent(text); } public void pauseapp() { } public void destroyapp(boolean unconditional) { } public void commandaction(command command, displayable displayable) { if(command==showcommand){ /** * 创建新的线程完成联网操作 */ (new networkthread(text)).start(); } } } |

图2
多线程与拍照
同样,在移动多媒体api(jsr 135)的使用过程中也需要应用到多线程。利用手机摄像头拍照的操作也会引起的堵塞,因此当在commandaction()中调用拍照操作时,若未开辟新线程来处理,同样也会造成程序没有响应。让我们通过一个例子来观察一下吧。
这是一个很简单的摄像头程序,首先程序将会调用摄像头取景,然后当用户按下“拍照”命令后就捕捉当前的画面并显示给用户。图3是程序的uml类图。

图3
malcameramidlet和cameramidlet分别是错误和正确的midlet,它们都创建一个cameraview(用于显示摄像头画面)的对象,唯一的不同在于前者创建malcamera的对象,后者创建camera的对象(malcamera和camera都是cameraview的子类)。snapshot则是将摄像头捕捉到的图像显示给用户的类。
首先,我们还是来看一下未使用多线程,而造成程序没有响应的情况。如下是malcameramidlet类的代码,其中保存着一个指向cameraview的引用,并在startapp()中创建了malcamera对象。
| 以下是引用片段: /** * malcameramidlet.java * */ package nju.hysteria.thread.camera; import javax.microedition.lcdui.display; import javax.microedition.midlet.midlet; /** * this midlet create the mal camera view, so the program * will block after calling capture command. * @author magic * */ public class malcameramidlet extends midlet { protected display display; private cameraview camera; public malcameramidlet() { super(); display = display.getdisplay(this); } protected void startapp() { camera = new malcamera(this); display.setcurrent(camera); } /** * show current camera. */ public void showcamera(){ display.setcurrent(camera); } protected void pauseapp() { } protected void destroyapp(boolean arg0) { } } |
| 以下是引用片段: /** * cameraview.java */ package nju.hysteria.thread.camera; import java.io.ioexception; import javax.microedition.lcdui.command; import javax.microedition.lcdui.commandlistener; import javax.microedition.lcdui.displayable; import javax.microedition.lcdui.form; import javax.microedition.lcdui.item; import javax.microedition.media.manager; import javax.microedition.media.mediaexception; import javax.microedition.media.player; import javax.microedition.media.control.videocontrol; import javax.microedition.midlet.midlet; /** * this is an abstract class, which display camera to user. * @author magic * */ public abstract class cameraview extends form implements commandlistener { protected midlet midlet; protected player player; protected videocontrol vc; protected command exitcommand; protected command capturecommand; protected cameraview(midlet midlet){ super("照相"); this.midlet = midlet; exitcommand = new command("退出",command.exit,0); capturecommand = new command("拍照",command.screen,1); addcommand(exitcommand); addcommand(capturecommand); setcommandlistener(this); /** * create camera player and control. */ try { player = manager.createplayer("capture://video"); player.realize(); vc = (videocontrol)player.getcontrol("videocontrol"); append((item)vc.initdisplaymode(videocontrol.use_gui_primitive,null)); player.start(); } catch (ioexception e) { e.printstacktrace(); } catch (mediaexception e) { e.printstacktrace(); } } public abstract void commandaction(command cmd,displayable displayable); } |
[nextpage]
malcamera和camera都继承了cameraview类,并分别实现了commandaction()方法。先来看一下malcamera:
| 以下是引用片段: /** * malcamera.java */ package nju.hysteria.thread.camera; import javax.microedition.lcdui.command; import javax.microedition.lcdui.displayable; import javax.microedition.media.mediaexception; import javax.microedition.midlet.midlet; /** * this class display the mal camera. in commandaction(), * for capture command, just get the data without create a * new thread, and thus cause program blocked. * @author magic * */ public class malcamera extends cameraview { public malcamera(midlet midlet){ super(midlet); } public void commandaction(command cmd, displayable displayable) { if(cmd==exitcommand){ try { player.stop(); } catch (mediaexception e) { e.printstacktrace(); } player.close(); ((malcameramidlet)midlet).destroyapp(false); midlet.notifydestroyed(); }else if(cmd==capturecommand){ // do not handle in a new thread. try { byte[] data = vc.getsnapshot(null); new snapshot(midlet,data); } catch (mediaexception e) { e.printstacktrace(); } } } } |
现在运行malcameramidlet,按下“拍照”命令后,出现询问是否记录图像的提示,但是,当你按下yes后程序就再也没有响应了,如图4所示。

图4
查看控制台窗口,得到如下提示:
| 以下是引用片段: warning: to avoid potential deadlock, operations that may block, such as networking, should be performed in a different thread than the commandaction() handler. |
同样,还是由于没有创建新的线程进行处理的原因。下面,我们就把处理的代码放到新的线程中来完成,看看情况如何。如下是修改过的malcamera代码,camera.java:
| 以下是引用片段: /** * camera.java */ package nju.hysteria.thread.camera; import javax.microedition.lcdui.command; import javax.microedition.lcdui.displayable; import javax.microedition.media.mediaexception; import javax.microedition.midlet.midlet; /** * this class displays camera. and do the right thing * for capture command, putting code in a new thread. * @author magic * */ public class camera extends cameraview { public camera(midlet midlet){ super(midlet); } public void commandaction(command cmd, displayable displayable) { if(cmd==exitcommand){ try { player.stop(); } catch (mediaexception e) { e.printstacktrace(); } player.close(); ((cameramidlet)midlet).destroyapp(false); midlet.notifydestroyed(); }else if(cmd==capturecommand){ // handle in a new thread. new thread(){ public void run(){ try { byte[] data = vc.getsnapshot(null); new snapshot(midlet,data); } catch (mediaexception e) { e.printstacktrace(); } } }.start(); } } } |
| 以下是引用片段: /** * cameramidlet.java */ package nju.hysteria.thread.camera; import javax.microedition.lcdui.display; import javax.microedition.midlet.midlet; /** * the correct midlet. * @author magic * */ public class cameramidlet extends midlet { protected display display; private cameraview camera; public cameramidlet() { super(); display = display.getdisplay(this); } protected void startapp() { camera = new camera(this); display.setcurrent(camera); } /** * show current camera. */ public void showcamera(){ display.setcurrent(camera); } protected void pauseapp() { } protected void destroyapp(boolean arg0) { } } |

图5
[nextpage]
timer与timertask
联网和拍照这两种情况都需要程序员创建新的线程来完成任务,并且这种做法对于程序员来说是显式的,即通过直接使用thread类或runnable接口来直接创建新线程。在midp的api中同样提供了隐式的方式来创建新线程,以方便程序员的编程。这就是timertask类,它实现了runnable接口,用户只需创建一个继承它的类,并且实现run()方法,以此来创建新线程,而无需显示的继承thread或runnable。
当然,timertask的优点不仅于此,从它的名字来看,可以认为它是一个在特定时间执行的任务。run()方法中代码就是这任务,那么怎么控制其在特定时间执行呢?这就需要timer这个类的帮助了。顾名思义,timer是一个定时器,通过调用它的多个schedule(...)方法中的一个,可以控制在特定的时间,或每隔一定时间执行timertask。具体的方法介绍请看jdk文档。
timertask和timer经常一起使用,比如在显示时间,倒计时和显示欢迎界面时会经常用到。下面,就通过一个实例来介绍这两个的用法。
这是一个计时的程序,程序从0秒开始,用户可以随时暂停或继续计时。程序是通过timer和timertask来完成的,包含三个类:clockmidlet,clockcanvas和clock。首先来看一下本程序最主要的类clockcanvas:
| 以下是引用片段: /** * clockcanvas.java */ package nju.hysteria.thread.clock; import java.util.timer; import javax.microedition.lcdui.canvas; import javax.microedition.lcdui.command; import javax.microedition.lcdui.commandlistener; import javax.microedition.lcdui.displayable; import javax.microedition.lcdui.font; import javax.microedition.lcdui.graphics; /** * this class display time to user, and also start the timer * to update time each second. * @author magic * */ public class clockcanvas extends canvas implements commandlistener { private clockmidlet midlet; private command exitcommand; private command stopcommand; private command resumecommand; private long second; private int x; private int y; // timer private timer timer; public clockcanvas(clockmidlet midlet){ this.midlet = midlet; exitcommand = new command("退出",command.exit,0); stopcommand = new command("停止",command.screen,1); resumecommand = new command("继续",command.screen,1); addcommand(exitcommand); addcommand(stopcommand); setcommandlistener(this); second = 0; x = getwidth()/2 - 10; y = getheight()/2 - 10; // create timer and start it. timer = new timer(); timer.schedule(new clock(this),0,1000); } /** * add one second. * */ public void addsecond(){ second++; } protected void paint(graphics g) { g.setcolor(0,0,0); g.fillrect(0,0,getwidth(),getheight()); g.setcolor(255,0,0); g.setfont(font.getfont(font.face_system,font.style_plain,font.size_large)); // draw second. g.drawstring(string.valueof(second),x,y,graphics.left|graphics.top); } public void commandaction(command cmd, displayable displayable) { if(cmd==exitcommand){ timer.cancel(); midlet.destroyapp(false); midlet.notifydestroyed(); }else if (cmd==stopcommand){ timer.cancel(); removecommand(stopcommand); addcommand(resumecommand); }else if (cmd==resumecommand){ timer = null; timer = new timer(); timer.schedule(new clock(this),0,1000); removecommand(resumecommand); addcommand(stopcommand); } } } |
注意commandaction()方法,在处理“停止”命令时,需要停止计时,此处调用timer.cancle()来取消计时器,所以界面将停止更新;当按下“继续”命令后,又需要继续开始计时,所以我们重新创建了timer,因为原来的已经取消了,是不可用的了。
接着就来看看clock是如何来工作的,代码如下:
| 以下是引用片段: /** * clock.java */ package nju.hysteria.thread.clock; import java.util.timertask; /** * update the time. * @author magic * */ public class clock extends timertask { private clockcanvas canvas; public clock(clockcanvas canvas){ this.canvas = canvas; } public void run() { canvas.addsecond(); canvas.repaint(); } } |
非常简单,在run()方法中就是调用了clockcanvas类中addsencond()方法来增加时间,同时调用repaint()来更新界面。从表面上看几乎没有多线程的痕迹,其实创建timer的时候就相当于在后台创建了一个新的线程,它控制着timertask的执行,因此对于秒数的增加是在另一个线程的完成的,而主线程只负责更新显示。
在加上下面的clockmidlet就可以运行程序了。
| 以下是引用片段: /** * clockmidlet.java */ package nju.hysteria.thread.clock; import javax.microedition.lcdui.canvas; import javax.microedition.lcdui.display; import javax.microedition.midlet.midlet; /** * clock midlet. * @author magic * */ public class clockmidlet extends midlet { private display display; private canvas clockcanvas; public clockmidlet() { super(); display = display.getdisplay(this); } protected void startapp(){ clockcanvas = new clockcanvas(this); display.setcurrent(clockcanvas); } protected void pauseapp() { } protected void destroyapp(boolean arg0) { } } |

图6
总 结
以上介绍了多线程技术在联网,拍照中的应用,以及midp自带的timertask和timer的使用方法。在实际编程过程中会遇到更多的使用多线程的情况,比如播放背景音乐等,但基本方法都是如此,这里不再赘述了。
由于笔者水平有限,以上论述只是蜻蜓点水,未能深入讨论多线程的使用,特别是防止死锁,以及wait()/notify()的使用。
闽公网安备 35060202000074号