热门文章
阿标在线 动力3.62HTML生成3.62网站文件说明
动力3.62整合动网7.0 SP2插
MDAC2.8 下载!
动力3.62版 防止垃圾留言
动力3.6全方位改动方法
让3.62不同频道实现不同风
把3.62首页登陆为横向代码
动易3.6首页随机FLASH修改
362首页和文章频道页图文幻
个性化修改3.6宝典
3.62轻易实现网摘功能
如何正确统计中文字数?
弹出JAVASCRIPT语法错误对
后台使“网站顶部LOGO地址
最新图片文章横向移动的修
html 生成艺术字
3.6 Sp2 Logo和Banner及广
日期值的计算
汉字转拼音
首页“图片更新”图片滚动
简体中文转换为繁体中文的
如何在css中定义链接的下划
再议j2me进度条与线程化模型
[ 录入:阿标 | 点击数: | 更新时间:2005-3-12 9:17:00]
作者:FavoYang Email:favoyang@yahoo.com 欢迎交流
Keywords:线程化模型 j2me UI设计
内容提要:
本文是《j2me进度条与线程化模型》一文的续(以后简称原文,没看过的建议看一下)。
讨论了原文中使用的线程模型的不足,并针对她的缺点提出了新的改进办法并给出了改进后的实现。因原文中UI部分有灵活的扩展性,未作更改。
版权声明:
本文同时发表在www.j2medev.com和我的Blog(blog.csdn.net/alikeboy)上,如果需要转载,有三个途径:1)联系我并经我同意;2)和www.j2medev.com有转载文章合作协议的 3)通过Rss聚合我的Blog。另外转载需要全文转发(包括文章的头部),不要断章取义。
正文:
前台UI如何和后台线程交互
原文中模型,是一个前台的ProgressGaugeUI与后台线程无关的模型。这样设计的时候最大程度上的化简了通信的复杂性,实际上是一种单方向的模型(由BackgroundTask 向 PGUI通信)。按照这种模式的要求,程序员在Override BackgroundTask 的runTask()方法时,有义务定期的去查训前台的PGUI的运行情况,并根据这种情况做出反映。这样这种模式完全相信后台线程,将是否响应用户cancel命令的权利交给了后台线程,如果后台线程陷入麻烦没有响应了(比如访问一个很昂贵的网络连接),此时用户试图cancel也没有用,程序将会暂时的死锁,直到后台线程有时间去检查前台的状态。并且在实际情况中,到底什么时候去查询,多大的频率都是问题。在代码段中过多的此类代码,会影响对正常的流程的理解。
从下面的这个顺序图,可以看到这个具体流程:
我们需要一个方法,让我们能够强制的结束Task。这个方法由背景线程自己提供,取名叫做cancel()。当然没有任何一个方法可以强迫线程立即结束(曾经有,因为安全性问题而被取消)。所以cancel()方法往往通过关闭的资源(一个连接,一个流等)来迫使runTask发生异常被中断,runTask有义务根据自己的约定捕捉此类异常并立即退出。一图胜千言,让我们看看这种方法的流程。
很显然的,关键在于前台的线程对后台的线程进行了回调,这样就可以解决问题了。但是新的问题来了,这样做迫使我们将前台与后台线程紧密的耦合在了一起(因为要回调嘛)。能不能既实现回调又避免前台UI与后台线程的紧密耦合呢?
通过Cancelable接口降低耦合度
幸好,我门可以利用接口来实现这一点。
先前的模型是这样的:
为了降低耦合,我们建立一个接口
public interface Cancelable {
/**
* 本方法非阻塞,应该立即返回(如有必要开启新的线程)
* 此外应避免对此方法的重复调用
*/
public void cancel();
}
接下来在ProgressObserver加入对这个方法的支持
public interface ProgressObserver {
……
……
/**
* 设置取消Task时回调的函数对象
* @param co
*/
public void setCancelalbeObject(Cancelable co);
}
这样,就可以在用户按下取消按钮的时候,就可以进行对Cancelable.cancel()的回调。这样灵活性大大增强了。
新代码
更新后的代码如下,除了改用以上的模型外,还对部分的BUG进行了更正,更改的地方会用不同的颜色表示。详细的用法可参见注释
/////////////////////////////////////////////////////////////////
Cancelable.java
package com.favo.ui;
/**
* @author Favo
*
* TODO To change the template for this generated type comment go to
* Window - Preferences - Java - Code Style - Code Templates
*/
public interface Cancelable {
/**
* 此方法非阻塞,应该立即返回(如果有必要开启新的线程)
* 此外应避免对此方法的重复调用
*/
public void cancel();
}
ProgressObserver.java
/*
* Created on 2005-2-26
*
* TODO To change the template for this generated file go to
* Window - Preferences - Java - Code Style - Code Templates
*/
package com.favo.ui;
import javax.microedition.lcdui.Display;
/**
* @author Favo
*
* 这是仿照Smart Ticket制作的进度条观察者,这个模型的优点是
* 1,低耦合度。你可以通过Form,Canvas等来实现这个接口
* 2,可中断任务的支持。是通过在内部设置flag并回调cancelObject的cancel()来实现的。后台线程可以通过查询这个flag从而知道用户是否中断过Task。
*/
public interface ProgressObserver {
/**
* 将进度条复位,主要为了重复利用进度条
*/
public void reset();
/**
* 将进度条的值为设置最大
*/
public void setMax();
/**
* 将自己绘制在屏幕上,如果进度条要开启自身的线程用于自动更新画面,
* 也在这里构造并开启绘画线程(常用于动画滚动条)
*/
public void show(Display display);
/**
* 如果进度条曾经开启自身的线程用于自动更新画面,(常用于动画滚动条),在这里关闭动画线程
* 如果没有请忽略此方法
*/
public void exit();
/**
* 更新进度条,参数任意
*/
public void updateProgress(Object param1);
/**
* 查询进度条是否可以暂停
*/
public boolean isStoppable();
/**
* 设置进度条是否可以暂停
* @param stoppable
*/
public void setStoppable(boolean stoppable);
/**
* 查询用户是否暂停了任务
* @return
*/
public boolean isStopped();
/**
* 设置任务暂停标记
*/
public void setStopped(boolean stopped);
/**
* 设置标题
*/
public void setTitle(String title);
/**
* 设置提示
*/
public void setPrompt(String prompt);
/**
* 设置是否取消Task时回调的函数对象
* @param co
*/
public void setCancelalbeObject(Cancelable co);
}
ProgressGaugeUI.java
/*
* Created on 2005-2-26
* Window - Preferences - Java - Code Style - Code Templates
*/
package com.favo.ui;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Gauge;
/**
* @author Favo
* 新版本的pgUI,主要是增加了cancel task的能力,通过回调CancelableObject的
* cancel方法实现。
* Preferences - Java - Code Style - Code Templates
*/
public class ProgressGaugeUI implements ProgressObserver, CommandListener {
private static final int GAUGE_MAX = 8;
private static final int GAUGE_LEVELS = 4;
private static ProgressGaugeUI pgUI;
private Form f;
private Gauge gauge;
private Command stopCMD;
boolean stopped;
boolean stoppable;
int current;
Cancelable cancelableObject;
protected ProgressGaugeUI() {
f = new Form("");
gauge = new Gauge("", false, GAUGE_MAX, 0);
stopCMD = new Command("Cancel", Command.STOP, 10);
f.append(gauge);
f.setCommandListener(this);
}
public static ProgressGaugeUI getInstance() {
if (pgUI == null) {
return new ProgressGaugeUI();
}
return pgUI;
}
/*
* (non-Javadoc)
*
* @see com.favo.ui.ProgressObserver#reset(java.lang.Object)
*/
public void reset() {
current=0;
gauge.setValue(0);
stopped=false;
setStoppable(false);
setTitle("");
setPrompt("");
cancelableObject=null;
}
/*
* (non-Javadoc)
*
* @see com.favo.ui.ProgressObserver#updateProgress(java.lang.Object)
*/
public void updateProgress(Object param1) {
// TODO Auto-generated method stub
current=(current+1)%GAUGE_LEVELS;
gauge.setValue(current * GAUGE_MAX/GAUGE_LEVELS);
if(param1!=null && param1 instanceof String){
setPrompt((String)param1);
}
}
/*
* (non-Javadoc)
*
* @see com.favo.ui.ProgressObserver#isStoppable()
*/
public boolean isStoppable() {
return stoppable;
}
/*
* (non-Javadoc)
*
* @see com.favo.ui.ProgressObserver#setStoppable(boolean)
*/
public void setStoppable(boolean stoppable) {
this.stoppable = stoppable;
if(stoppable){
f.addCommand(stopCMD);
}else{
f.removeCommand(stopCMD);
}
}
/*
* (non-Javadoc)
*
* @see com.favo.ui.ProgressObserver#isStopped()
*/
public boolean isStopped() {
// TODO Auto-generated method stub
return stopped;
}
/*
* (non-Javadoc)
*
* @see com.favo.ui.ProgressObserver#setTitle(java.lang.String)
*/
public void setTitle(String title) {
// TODO Auto-generated method stub
f.setTitle(title);
}
/*
* (non-Javadoc)
*
* @see com.favo.ui.ProgressObserver#setPrompt(java.lang.String)
*/
public void setPrompt(String prompt) {
gauge.setLabel(prompt);
}
/*
* (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==stopCMD){
if(isStoppable())
if(!isStopped()){//保证仅被调用一次
setStopped(true);
if(cancelableObject!=null)
cancelableObject.cancel();
}
else{
setPrompt("can't stop!");
}
}
}
/* (non-Javadoc)
* @see com.favo.ui.ProgressObserver#show(javax.microedition.lcdui.Display)
*/
public void show(Display display) {
display.setCurrent(f);
}
/* (non-Javadoc)
* @see com.favo.ui.ProgressObserver#exit()
*/
public void exit() {
cancelableObject=null;
}
/* (non-Javadoc)
* @see com.favo.ui.ProgressObserver#setMax()
*/
public void setMax() {
gauge.setValue(GAUGE_MAX);
}
/* (non-Javadoc)
* @see com.favo.ui.ProgressObserver#setStopped(boolean)
*/
public void setStopped(boolean stopped) {
this.stopped=stopped;
}
public void setCancelalbeObject(Cancelable co){
this.cancelableObject=co;
}
}
BackgroundTask.java
/*
* Created on 2005-2-26
*
* TODO To change the template for this generated file go to
* Window - Preferences - Java - Code Style - Code Templates
*/
package com.favo.ui;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Alert;
/**
* @author Favo
*
* TODO To change the template for this generated type comment go to Window -
* Preferences - Java - Code Style - Code Templates
*/
public abstract class BackgroundTask extends Thread implements Cancelable {
ProgressObserver poUI;
protected Displayable preScreen;
protected boolean needAlert;
protected Alert alertScreen;
private Display display;
/*
*
*/
public BackgroundTask(ProgressObserver poUI, Displayable pre,
Display display) {
this.poUI = poUI;
this.preScreen = pre;
this.display = display;
this.needAlert = false;
}
/*
* (non-Javadoc)
*
* @see java.lang.Thread#run()
*/
public void run() {
boolean taskComplete=false;
try {
taskComplete=runTask();
} catch (Exception e) {
Alert al = new Alert("undefine exception", e.getMessage(), null,
AlertType.ALARM);
al.setTimeout(Alert.FOREVER);
display.setCurrent(al);
} finally {
if (!taskComplete&&poUI.isStoppable()) {
if (poUI.isStopped()) {//如果用户中断了程序
if (needAlert) {
display.setCurrent(alertScreen, preScreen);
} else {
display.setCurrent(preScreen);
}
}
}
poUI.exit();
}
}
/**
* 须由用户定义的任务
* 注意!!!
* 任务如果成功的运行,应该由此方法内部负责跳转至成功画面,并返回true.
* 若任务运行失败,请设置needAlert(是否需要警报),AlertScreen(警报画面),preScreen(跳转回的前一个具体屏幕)
* 手动更新进度栏,请调用pgUI.updateProgress().
* 请确保当cancel()调用时,此方法会立即退出,并返回false(如果因为异常跳出此函数是可以接受的行为).
*/
public abstract boolean runTask();
/**
* 这是一个偷懒的办法,当你构造好BackgroundTask对象后,直接调用这个方法, 可以帮助你初始化进度UI,并显示出来。之后启动你的任务线程
*/
public static void runWithProgressGauge(BackgroundTask btask, String title,
String prompt, boolean stoppable, Display display) {
ProgressObserver po = btask.getProgressObserver();
po.reset();
po.setStoppable(stoppable);
if(stoppable){
po.setCancelalbeObject(btask);
}
po.setTitle(title);
po.setPrompt(prompt);
po.show(display);
btask.start();
}
public ProgressObserver getProgressObserver() {
return poUI;
}
//取消了taskComplete方法,因为runTask已经有了返回值
//
// public void taskComplete(){
// getProgressObserver().setStopped(false);
// }
}
TestProgressGauge.java
/*
* Created on 2005-2-26
*
* TODO To change the template for this generated file go to
* Window - Preferences - Java - Code Style - Code Templates
*/
package com.favo.ui;
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.Form;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
/**
* @author Favo
*
* TODO To change the template for this generated type comment go to Window -
* Preferences - Java - Code Style - Code Templates
*/
public class TestProgressGauge extends MIDlet implements CommandListener {
/**
*
*/
Display display;
Command workCmd;
Command exitCmd;
Form f;
public TestProgressGauge() {
super();
// TODO Auto-generated constructor stub
display = Display.getDisplay(this);
workCmd = new Command("compute", Command.OK, 10);
exitCmd = new Command("exit", Command.EXIT, 10);
f = new Form("Test");
f.setCommandListener(this);
f.addCommand(workCmd);
f.addCommand(exitCmd);
}
/*
* (non-Javadoc)
*
* @see javax.microedition.midlet.MIDlet#startApp()
*/
protected void startApp() throws MIDletStateChangeException {
// TODO Auto-generated method stub
display.setCurrent(f);
}
/*
* (non-Javadoc)
*
* @see javax.microedition.midlet.MIDlet#pauseApp()
*/
protected void pauseApp() {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
*
* @see javax.microedition.midlet.MIDlet#destroyApp(boolean)
*/
protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
*
* @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command,
* javax.microedition.lcdui.Displayable)
*/
public void commandAction(Command arg0, Displayable arg1) {
// TODO Auto-generated method stub
if (arg0 == workCmd) {
ProgressObserver poUI = ProgressGaugeUI.getInstance();
BackgroundTask bkTask = new BackgroundTask(poUI, arg1, display) {
public boolean runTask() {
System.out.println("task start!");
alertScreen = new Alert(
"user cancel",
"you press the cancel button and the screen will jump to the main Form",
null, AlertType.ERROR);
alertScreen.setTimeout(Alert.FOREVER);
needAlert = true;
//do something first
getProgressObserver().updateProgress(null);//手动更新
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
return false;
}
getProgressObserver().updateProgress("sleepd 3s...");//手动更新
//取消了此处的手动查询点
// if (getProgressObserver().isStopped())
// return;
getProgressObserver().updateProgress(null);//手动更新
//do something again
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
return false;
}
getProgressObserver().setMax();//手动更新
display.setCurrent(new Form("complete"));//跳转成功画面
return true;
}
public void cancel() {
this.interrupt();
}
};
BackgroundTask.runWithProgressGauge(bkTask, "Sleep 6s",
"Sleep now...", true, display);
}else if(arg0==exitCmd){
try {
destroyApp(false);
} catch (MIDletStateChangeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
notifyDestroyed();
}
}
}
/////////////////////////////////////////////////////////////////