再议j2me进度条与线程化模型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();        }     }   } /////////////////////////////////////////////////////////////////    
 
  |