程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> J2ME >> 再議j2me進度條與線程化模型

再議j2me進度條與線程化模型

編輯:J2ME
作者:FavoYang Email:[email protected] 歡迎交流
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();
}
}

}
/////////////////////////////////////////////////////////////////
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved