IllegalThreadStateException in LunarLander

在實驗LunarLander 的時後,如果在run LunarLander的途中,按下Home跳出
而不是按下return的話,此時在進入LunarLander,就會跑出 IllegalThreadStateException 這樣的例外


稍微研究了一下這個原因


首先看一下這張圖,綠色框起來是Return,藍色框起來是Home
比較這兩鍵按下去返回主選單在回去遊戲的不同

Return
生命週期變化

onCreate->onStart->onResume->按下Retuen ->onPause->onStop->onDestroy
->回到 LunarLander ->onCreate ->onStart->onResume



Home
生命週期變化

onCreate->onStart->onResume->按下Home ->onPause->onStop
->回到 LunarLander ->onRestart ->onStart->onResume


上面的比較可以發現,按下Return的時候,整個Activity會依照正常程序Destroy在Create
但是按下Home回到主選單的時候Activity不會Destroy掉,他的資料仍然會存在記憶體,之後只是Restart他

而LunarLander遊戲設計是假設SurfaceView的生命週期跟Activity一致,在Activity的Create跟Destroy這段期間,只會發生一次的surfaceCreate跟surfaceDestroy

來觀察一下按下Home的時候,SurfaceView的生命週期

onCreate->onStart->onResume
onWindowFocusChanged ->surfaceCreated ->surfaceChanged -> 按下Home ->onPause->surfaceDestroyed ->onWindowFocusChanged ->onStop
->回到 LunarLander ->onRestart ->onStart->onResume->surfaceCreated
->
surfaceChanged -> onWindowFocusChanged


就像上面所示,因為Activity沒有照正常程序被Destroy掉
而在Activity的create跟destroy之間,SurfaceView的create跟Destroy執行了超過一次以上,這是沒有在當出遊戲設計者的預料之中,所以會發生IllegalThreadStateException 這個例外而不能執行的Bug

IllegalThreadStateException 是怎麼發生的呢?
先來看他的定義

Thrown when an operation is attempted which is not possible given the state that the executing thread is in.

也就是試圖從相同的程序中start相同的Thread
舉個例子

Thread t=new Thread(new Runnable(){

@Override
public void run() {
// TODO Auto-generated method stub

}});
t.start();
t.start();//start兩次,拋出IllegalThreadStateException

而要避開這例外,必須使用新的Thread

Thread t=new Thread(new Runnable(){

@Override
public void run() {
// TODO Auto-generated method stub

}});
t.start();
t=new Thread(..........);
t.start();



而在Lunar,IllegalThreadStateException 是從LunarView的surfaceCreated 中拋出來的

/*
* Callback invoked when the Surface has been created and is ready to be
* used.
*/
public void surfaceCreated(SurfaceHolder holder) {
....
thread.start();//由此函式拋出IllegalThreadStateException
}

如上面所示,因為surfaceCreated在Activity執行了兩次以上的話,相同的thread就會start兩次以上,thread的產生是在Activity的onCreate呼叫建構式LunarView時誕生,所以只會被產生一次

public LunarView(Context context, AttributeSet attrs) {
....
thread = new LunarThread(holder, _context, new Handler() {
@Override
public void handleMessage(Message m) {
mStatusText.setVisibility(m.getData().getInt("viz"));
mStatusText.setText(m.getData().getString("text"));
}
});

....
}

在此我提出一個暴力法的解決方案解決這Bug,就是在surfaceCreated 的時候就產生一個新的LunarThread,這樣可以避免同樣的thread被start兩次以上,不過我沒做State的Retore,所以他就跟按下Return一樣是從新開始一個遊戲

我的作法就是讓LunarThread的生命週期跟Surface一致

  • surfaceCreated:產生LunarThread
  • surfaceDestroyed:消滅LunarThread



修改方法如下,粗體是我新增的程式碼
surfaceCreated

public void surfaceCreated(SurfaceHolder holder) {
/*判斷thread是否為null,是的話產生新的Thread*/
if (thread == null) {
/*
把建構式創造thread的程式碼copy過來,記得建構式保持原樣就好,不須要更改
*/

thread = new LunarThread(holder, _context, new Handler() {
@Override
public void handleMessage(Message m) {
mStatusText.setVisibility(m.getData().getInt("viz"));
mStatusText.setText(m.getData().getString("text"));
}
});
setFocusable(true);

/*這邊是跟建構式不同的地方,因為按下Home的時候,會改變thread的狀態
在這邊要手動把狀態設置為ready來讓keyEvent能正常動作
*/

thread.setState(LunarThread.STATE_READY);
}


thread.setRunning(true);
thread.start();
}


surfaceDestroyed

public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
/*在此把thread設為null消滅現有的thread
這邊是為了配合surfaceCreated的if(thread==null)
*/

thread=null;
}


onWindowFocusChanged

public void onWindowFocusChanged(boolean hasWindowFocus) {

if (!hasWindowFocus)
{
/*surfaceDestroy發生後會在發生 onWindowFocusChanged事件
因為thread已經被設為null了所以不能在執行函式動作
這也是為什麼surfaceCreated要手動設置Ready
*/

if(thread!=null)
thread.pause();
}

}


以上,改完就大功告成了
--------------------
更好的解決方案
在建構式ListView的時候創造跟啟動執行緒(相較於在surfaceCreate啟動執行緒)
創造一個lock的物件
而在surfaceCreate的時候使用notify跟restore
在surfaceDestroy的時候使用wait跟相關儲存動作
創造一個函式destroyGame()真正跳出整個遊戲,只有在Activity的onDestroy去呼叫他(相較於在surfaceDestroy結束執行緒)

不過我沒有去真正實作上面的方案,只是構想,不過因該是八九不離十

更多相关文章

  1. [Android面试题-1] Activity的生命周期
  2. Android非正常结束时的生命周期
  3. Jetpack组件之Lifecycle用途及原理
  4. android 入门学习笔记 LayoutInflater的使用
  5. Android(安卓)横屏切换竖屏Activity的生命周期
  6. Android:Activity生命周期深入详解
  7. Android四大组件生命周期,组件类的继承与实现,Context
  8. android基础入门生命周期(1)
  9. Android官方架构组件LifeCycle详解

随机推荐

  1. Redux-saga停留在收益率调用上
  2. 如何在JavaScript中按大写和小写分割字符
  3. 如何为select中的选项创建?
  4. 当尝试安装节点时,会得到一个“DLL”错误
  5. 如何通过ajax将javascript数组传递给YII
  6. 什么库或模式减少了这个cb到1 cb的样板?
  7. 使用yui将新项添加到组合框中
  8. js获取毫秒值以及转换成年月日时分秒等
  9. 使用“使用中值排序基数法”实现树状结构
  10. JQuery 1.4.4和Datatable 1.7不工作