在本系列前一篇文章讲到MIDlet 生命周期时, 一直没有示范过notifyPaused()与 resumeRequest()的使用方式。当我们学会了Timer 与TimerTask 之后,就具备了了解notifyPaused()与 resumeRequest()的能力。
范例如下:
//LifeCycleTask.java
import javax.microedition.midlet.*;
import java.util.*;
public class LifeCycleTask extends TimerTask
{
//要唤醒的MIDlet
MIDlet call ;
public LifeCycleTask(MIDlet call)
{
this.call = call ;
}
public void run()
{
//使用resumeRequest()请求让MIDlet 重新进入Active 状态
call.resumeRequest() ;
}
}
//LifeCycleTest.java
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.util.*;
public class LifeCycleTest extends MIDlet
{
Timer timer ;
LifeCycleTask lct ;
public LifeCycleTest()
{
timer = new Timer() ;
}
public void startApp()
{
System.out.println("startApp Executes") ;
Form f = new Form("Test") ;
Display.getDisplay(this).setCurrent(f) ;
try
{
Thread.sleep(2000);
}catch(Exception e){}
System.out.println("Ready to paused") ;
lct = null ;
lct = new LifeCycleTask(this) ;
timer.schedule(lct,4000);
notifyPaused() ;
}
public void pauseApp()
{
}
public void destroyApp(boolean unconditional)
{
timer.cancel() ;
}
}
执行结果:
startApp Executes
Ready to paused
startApp Executes
Ready to paused
startApp Executes
Ready to paused
……………
此范例让MIDlet 不断地在启动状态与停止状态之间转换。我们也会看到画面上不断地在程序主画面与最初的MIDlet 选择画面切换。从执行结果中我们可以发现,每次从停止状态回复到启动状态时,startApp()都会被叫用。
请回忆在前一章提到的:「startApp()很可能不光只有被呼叫一次而已,而是每次从停止状态重新回到运作状态的时候都会被应用程序管理员叫用。所以只需要被初始化一次的动作就不适合放在startApp()之中,请改用建构式做初始化动作。」
因此在此范例中,Timer 被放到建构式中,只会被叫用一次,但是TimerTask 却每次都在startApp()之中重新产生,这是因为除非Timer 的cancel()被叫用,否则TimerTask 只能排程一次。
思考
有没有可以重复使用TimerTask 的方法,而且不必每次重新产生呢?
由于Timer 并没有提供任何参数让我们得知它本身的状态,因此有些人很可能会将Timer 的参考传入LifeCycleTask 之中,然后在run()里叫用其cancel()。可是,这样会造成有时成功,有时失败(startApp()却丢出Exception,使得MIDlet 立刻进入毁灭状态)。这是为什么呢?
解答
根据规格,如果叫用cancel()时,run()仍在执行,那么run()会继续完成(使用TimerTask 的cancel()方法时也会有一样的情形),待最后一个run()执行完毕之后,Timer 与TimerTask 之间的关系才被解除,TimerTask 才能重新排程。但是,此时cancel()与startApp()会发生竞速情形(race condition),使得startApp()执行到schedule()时,cancel 未完成,这时就会产生Exception。而上述的范例程序由于使用了Thread.sleep(),所以减少了错误的发生机率,这代表这是不安全的写法,应该尽量避免。