解決的問題
在j2me的UI體系中,UI操作是在一個獨立的線程中運行的。往往在api doc中要求程序員對接口方法立 即返回。也就是說非阻塞的。你必須開啟一個獨立的線程來完成你自定義的復雜的工作,比如聯網等可能 發生阻塞的io操作。新的線程如果不和用戶交流,告訴用戶線程正在工作的話,將會顯現的非常不友好。 用戶可能執行別的操作而擾亂程序的正常運行。一個簡單的方法是提供一個進度條,這樣用戶就會願意等 待上一會,直到程序運行出結果。為了將程序員從前台進度條與後台線程的通信中解脫出來,專心於後台 線程的開發,有必要設計一個進度條線程模型。
應該注意到進度條有多種的形式:
A, 動畫形式進度條,僅表示程序正在運行(自維護的)
B, 可交互增量形式的進度條,後台線程通過調用進度條的相應方法在程序運行中不斷的改變進度條 的狀態
C, 進度條的表現形式應該靈活,不要固定其實現
D, 進度條對象要重復利用
進度調和後台線程的交流也有好幾種情況:
A, 僅僅將進度條繪畫在屏幕上,並等後台任務完成後,由後台線程跳轉到成功畫面。
B, 對於可取消的任務,用戶可以通過點擊進度條的按鈕來試圖cancel任務,後台任務應該盡快取消 ,並跳轉到失敗的畫面
C, 對於不可跳轉的任務,用戶只有耐心等待
D, 如果背景線程運行失敗,應自行跳轉到失敗的屏幕
進度條的設計(前台)
為了實現進度條的表現的多樣性,首先抽象一個接口:
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;
/**
* @author Favo
* Preferences - Java - Code Style - Code Templates
*/
public abstract class BackgroundTask extends Thread {
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;
}
public void run() {
try {
runTask();
} catch (Exception e) {
Alert al = new Alert("undefine exception",
e.getMessage(), null,
AlertType.ALARM);
al.setTimeout(Alert.FOREVER);
display.setCurrent(al);
} finally {
if (poUI.isStoppable()) {
if (poUI.isStopped()) {//如果用戶中斷了程序
if (needAlert) {
display.setCurrent(alertScreen, preScreen);
} else {
display.setCurrent(preScreen);
}
}
}
poUI.exit();
}
}
/*
* 如果任務可中斷,查看pgUI.isStopped().並盡快退出此方法;
* 如果任務需要更新進度欄,調用pgUI.updateProgress(“進度提示”).
* 習慣上此方法的最後手動調用taskComplete()以防止用戶在任務接近
* 完成時取消
*/
public abstract void 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);
po.setTitle(title);
po.setPrompt(prompt);
po.show(display);
btask.start();
}
public ProgressObserver getProgressObserver() {
return poUI;
}
public void taskComplete(){
getProgressObserver().setStopped(false);
}
}
如何使用
1)產生一個ProgressObserver 對象poUI
如果用默認的,通過調用ProgressGaugeUI.getInstance();
2)構造BackgroundTask對象bkTask,一般可以用匿名類來實現。
3)初始化poUI設置後字段顯示你的poUI開啟bkTask線程。
第三步可以用一步完成,通過調用靜態方法
BackgroundTask.runWithProgressGauge(bkTask, "標題",
"提示", 是否可以暫停, display);
下面一個例子,看看你是否理解了,並且會使用了。
TestProgressGauge.java
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
* 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);
}
protected void startApp() throws MIDletStateChangeException {
// TODO Auto-generated method stub
display.setCurrent(f);
}
protected void pauseApp() {
// TODO Auto-generated method stub
}
protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
}
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 void runTask() {
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();
}
getProgressObserver().updateProgress("sleepd 3s...");
if (getProgressObserver().isStopped())
return;
getProgressObserver().updateProgress(null);
//do something second
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
getProgressObserver().setMax();
display.setCurrent(new Form("complete"));
taskComplete();
}
};
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();
}
}
}
運行流程畫面
按下compute 用戶取消
回到前一屏幕 按下compute
完成
希望這個模型可以加快你的開發速度。如果你有更好的解決辦法,能夠更清晰的解決問題或是問題的 細節,歡迎討論。