再议j2me进度条与线程化模型

再议j2me进度条与线程化模型

作者: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();
       }
    }
 
}
/
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值