|
|
作者:magic003
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();
}
}
}
}
其中SnapShot是显示捕捉到的图像的界面,详细请看文后的源代码,这里不再赘述。
现在运行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();
}
}
}
同样,我们也需要在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) {
}
}
运行CameraMidlet,按下“拍照”命令,这次程序没有堵塞,我们可以得到捕捉到的图片。如图5所示:
图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) {
}
}
运行程序,将以秒为单位进行计时,如图6所示:
图6
总 结
以上介绍了多线程技术在联网,拍照中的应用,以及MIDP自带的TimerTask和Timer的使用方法。在实际编程过程中会遇到更多的使用多线程的情况,比如播放背景音乐等,但基本方法都是如此,这里不再赘述了。
由于笔者水平有限,以上论述只是蜻蜓点水,未能深入讨论多线程的使用,特别是防止死锁,以及wait()/notify()的使用。
|
|