在J2ME的UI体系中,UI操作是在一个独立的线程中运行的。往往在API中要求程序员对接口方法立即返回。也就是说非阻塞的。你必须开启一个独立的线程来完成你自定义的复杂的工作,比如联网等可能发生阻塞的io操作。新的线程如果不和用户交流,告诉用户线程正在工作的话,将会显现的非常不友好。用户可能执行别的操作而扰乱程序的正常运行。一个简单的方法是提供一个进度条,这样用户就会愿意等待上一会,直到程序运行出结果。为了将程序员从前台进度条与后台线程的通信中解脱出来,专心于后台线程的开发,有必要设计一个进度条线程模型。
应该注意到进度条有多种的形式: 动画形式进度条,仅表示程序正在运行(自维护的)
可交互增量形式的进度条,后台线程通过调用进度条的相应方法在程序运行中不断的改变进度条的状态
进度条的表现形式应该灵活,不要固定其实现
进度条对象要重复利用
进度调和后台线程的交流也有好几种情况:
仅仅将进度条绘画在屏幕上,并等后台任务完成后,由后台线程跳转到成功画面。
对于可取消的任务,用户可以通过点击进度条的按钮来试图cancel任务,后台任务应该尽快取消,并跳转到失败的画面。
对于不可跳转的任务,用户只有耐心等待。
如果背景线程运行失败,应自行跳转到失败的屏幕。
进度条的设计(前台)为了实现进度条的表现的多样性,首先抽象一个接口:
ProgressObserver.java
package com.favo.ui;
import javax.microedition.lcdui.Display;
/**
* @author Favo
*
* 这是仿照Smart Ticket制作的进度条观察者,这个模型的优点是
* 1,低耦合度。你可以通过Form,Canvas等来实现这个接口
* 2,支持可中断的任务,因为背景线程是无法强制性中断的,
* 所以就 没有了在观察者中回调背景线程相应方法的必要,
* 如果支持可中断的话,可以让背景线程来查询观察者的isStopped()
* 3,可以说进度条仅仅将自己绘画在屏幕上,他对后台线程毫不关心
*/
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();
public void setStoppable(boolean stoppable);
public boolean isStopped();
public void setStopped(boolean stopped);
public void setTitle(String title);
public void setPrompt(String prompt);
}
每个方法都很一幕了然,我解释两点: 1)“2,支持可中断的任务,因为背景线程是无法强制性中断的,所以就没有了在观察者中回调背景线程相应方法的必要, 如果支持可中断的话,可以让背景线程来查询观察者的isStopped()”
如果要支持可中断线程的话,想当然的,我们希望用户按下按钮后回调后台线程的某个方法来停止线程,并且这个方法要立即返回(前面提过UI的用户响应不能够阻塞)。但是细细想想,线程是无法被强制停止的,而且即使能够被强制停止也很不安全。所以这个方法也只能够是通过设置某个flag,然后立即返回。这样的话线程就和前台的UI紧密的耦合在一起了。与其这样,倒不如让后台线程去查询UI的状态。这样UI并不关心到底是谁在后台维护他状态。
2)如果要实现一个不交互动画UI,那么显然这个UI是自维护的(也就是说UI单独有自己的绘画线程)。为了能够实现这种情况,可以在show中开启线程,在exit中结束线程。对于交互UI,可以简单的忽略exit方法。
下面给一个利用Form和Gauge实现的交互式UI(非自维护的),读者可以看看其中的细节,参照他可以设计自己的用Canvas实现的,或者自维护的等等不同的实现。
ProgressGaugeUI.java
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
* 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;
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;
}
public void reset() {
current=0;
gauge.setValue(0);
stopped=false;
setStoppable(false);
setTitle("");
setPrompt("");
}
public void updateProgress(Object param1)
{
//这里的参数设计为提示语
current=(current+1)%GAUGE_LEVELS;
gauge.setValue(current * GAUGE_MAX/GAUGE_LEVELS);
if(param1!=null && param1 instanceof String){
setPrompt((String)param1);
}
}
public boolean isStoppable() {
return stoppable;
}
public void setStoppable(boolean stoppable) {
this.stoppable = stoppable;
if(stoppable){
f.addCommand(stopCMD);
}else{
f.removeCommand(stopCMD);
}
}
public boolean isStopped() {
return stopped;
}
public void setStopped(boolean stopped) {
this.stopped=stopped;
}
public void setTitle(String title) {
f.setTitle(title);
}
public void setPrompt(String prompt) {
gauge.setLabel(prompt);
}
public void commandAction(Command arg0, Displayable arg1)
{
if(arg0==stopCMD){
if(isStoppable())
stopped=true;
else{
setPrompt("can't stop!");
}
}
}
public void show(Display display) {
display.setCurrent(f);
}
public void exit() {
// 忽略
}
public void setMax() {
gauge.setValue(GAUGE_MAX);
}
}
后台线程的设计 后台线程替我们作以下的内容:
1)执行我们的任务runTask()
2)如果用户中断线程,那么runTask()运行完后,将会跳转到我们指定的失败屏幕
3)在最后替我们调用UI.exit()
我们需要做的:
1)提供一个前台的UI,提供失败后跳转的画面,提供Display的实例
2)在runTask()中,如果任务完成,手工跳转失败画面
3)在runTask()中,如果任务失败,手工跳转失败画面
4)在runTask()中改变进度栏的状态。
5)在runTask()中查询用户是否取消,如果用户取消,应该尽快退出runTask()
这种模型职责清晰,便于使用。但也有一个缺点:如果用户取消了任务,但是此时任务接近完成,或者已经完成。后台线程依然会显示用户取消了任务,并将会跳转到我们指定的失败屏幕。这时候会产生不一致的情况。为了解决整个问题,程序员可以在runTask()中调用taskComplete()来强制完成任务。这样即使用户取消了任务,依然回显示任务成功。当然你也可以不掉用taskComplete()遵循默认的行为特点。
BackgroundTask.java
package com.favo.ui;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Alert;