登录 用户中心() [退出] 后台管理 注册
   
您的位置: 首页 >> 程序员学前班[不再更新,只读] >> 主题: [转]多线程在JAVA ME应用程序中的使用     [回主站]     [分站链接]
标题
[转]多线程在JAVA ME应用程序中的使用
clq
浏览(0) + 2008-06-19 13:55:22 发表 编辑

关键字:

http://www.j2medev.com/Article/Class1/Class11/200607/2853.html



作者:magic003 文章来源:本站原创 点击数:3459 更新时间:2006-7-30


多线程技术是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);
}
}
}

clq
2008-6-19 13:56:00 发表 编辑

[图片]
(图1)

当用户按下“问候”命令时,就会向服务器发送“Hello World”的消息,然后再得到服务器返回的消息,并显示在TextBox中。
运行程序,如图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.


这就是因为没有使用多线程造成的。下面,就来看看如何使用多线程来解决此问题。
新建类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);
}

}

同时,修改原先的MIDlet,得到新的MIDlet:
以下是引用片段:
/*
* 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();
}
}
}

clq
2008-6-19 13:57:25 发表 编辑

[图片]
图2



此时,在commandAction()中,我们创建了新的线程来完成消息的发送和接受。运行程序,可以成功接受到返回的消息,如图2所示。

clq
2008-6-19 13:58:00 发表 编辑

[图片]
图3



多线程与拍照

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



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是一个抽象类,是两个显示Camera的类的父类。它负责显示Camera,并将commandAction()方法留给子类实现,代码如下:
以下是引用片段:
/**
* 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);
}

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();
}
}
}
}

clq
2008-6-19 13:58:43 发表 编辑

[图片]

图4



其中SnapShot是显示捕捉到的图像的界面,详细请看文后的源代码,这里不再赘述。
现在运行MalCameraMidlet,按下“拍照”命令后,出现询问是否记录图像的提示,但是,当你按下Yes后程序就再也没有响应了,如图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();
}
}
}

同样,我们也需要在MIDlet中创建Camera的对象,而不是MalCamera。CameraMidlet的代码如下:
以下是引用片段:
/**
* 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) {
}
}

clq
2008-6-19 13:59:10 发表 编辑

[图片]
图5



运行CameraMidlet,按下“拍照”命令,这次程序没有堵塞,我们可以得到捕捉到的图片。如图5所示:



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);
}
}
}

ClockCanvas继承了Canvas,用来向用户显示当前的秒数。注意构造函数中黑体的代码,创建了Timer,并且调用schedule()方法来设定运行任务的时间,第一个参数是TimerTask的对象,这里是Clock,它继承了TimerTask,我们将稍后讨论;第二个参数是运行的延迟,这里为0,也就是立刻执行;第三个参数是连续运行的时间间隔,这里为1000毫秒,也就是每隔1秒钟更新界面。
注意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) {
}
}

clq
2008-6-19 13:59:49 发表 编辑

[图片]

图6


运行程序,将以秒为单位进行计时,如图6所示:


总 结

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

资 源

程序源码
“J2ME中多线程网络连接编程的分析 ”
“在J2ME中使用MMAPI开发摄像头程序” by efei
“使用Timer和TimerTask实现欢迎界面” by magic003


总数:6 页次:1/1 首页 尾页  
总数:6 页次:1/1 首页 尾页  


所在合集/目录



发表评论:
文本/html模式切换 插入图片 文本/html模式切换


附件:



NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.

Copyright © 2005-2020 clq, All Rights Reserved
版权所有
桂ICP备15002303号-1