再议j2me进度条与线程化模型作者:FavoYang Email:
[email protected] 欢迎交流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.javapackage 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(); } } }/////////////////////////////////////////////////////////////////
本文地址:http://com.8s8s.com/it/it11046.htm