作者:FavoYang Email:favoyang@yahoo.com 歡迎交流
Keyworld:線程化模型 j2me UI設計
內容提要:
本文研究如何建立一個方便使用的線程化模型,這個線程化模型由前臺的進度條UI 和后臺的背景線程組成。
版權聲明:
本文同時發表在www.j2medev.com和我的Blog(http://blog.csdn.net/alikeboy)上,如果需要轉載,有三個途徑:1)聯系我并經我同意;2)和www.j2medev.com有轉載文章合作協議的 3)通過rss聚合我的Blog。另外轉載需要全文轉發(包括文章的頭部),不要斷章取義。
正文:
在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-->
-->
-->
完成
希望這個模型可以加快你的開發速度。如果你有更好的解決辦法,能夠更清晰的解決問題或是問題的細節,歡迎討論。
(出處:http://www.survivalescaperooms.com)
新聞熱點
疑難解答