在實驗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結束執行緒)
不過我沒有去真正實作上面的方案,只是構想,不過因該是八九不離十
分享到:
相关推荐
- IllegalThreadStateException - NumberFormatException - Error 在异常处理中,有以下两种主要方法: 1. 使用`try...catch...finally`结构: 这种方式允许你在可能抛出异常的代码块前放置`try`关键字,然后...
如果一个线程已经启动,再次调用 `start()` 会抛出`IllegalThreadStateException`。 - **run()**:`run()` 方法包含了线程要执行的具体任务。当`start()`被调用时,`run()` 会在新线程上下文中执行。如果你直接调用...
每个`Thread`对象只能启动一次,否则会抛出`IllegalThreadStateException`。 - 实现`Runnable`接口:创建一个实现了`Runnable`接口的类,重写`run()`方法,然后将该类的实例传入`Thread`类的构造函数中。这种方式...
- 线程启动只能调用一次start(),否则抛出异常`IllegalThreadStateException`。 - 如果没有重写run(),创建的Thread对象不会执行任何操作。 - 可以使用匿名内部类快速创建线程。 通过理解和熟练掌握Java线程的这些...
每个线程只能启动一次,多次调用start()会抛出`IllegalThreadStateException`。 - **run()**:线程执行的主要代码段,由Java虚拟机调用。 - **Thread.yield()**:让当前线程暂停,让其他线程有机会执行,但并不保证...
尝试启动已启动的线程会导致`IllegalThreadStateException`。 - **run()方法**:线程执行的主要逻辑,只是一个普通方法,可以直接调用,但不会创建新线程,而是直接在当前线程中执行。 5. **线程控制**: - **...
10. **IllegalThreadStateException** - 发生场景:线程处于不适当的状态时触发。 - 解决方案:确保线程状态符合预期的操作要求。 11. **IndexOutOfBoundsException** - 发生场景:索引超出合法范围时抛出。 - ...
项目中碰到的,已解决,写个文档记录一下
如果尝试对已启动的线程调用`start()`,系统会抛出`IllegalThreadStateException`。因此,中断线程后,若需要重新启动,必须创建一个新的`Thread`实例: ```java public void restartThread() { mThread = new ...
这个方法必须在 **start()** 方法调用之前调用,否则会抛出 `IllegalThreadStateException` 异常。 - 守护线程的主要用途是在后台提供服务,如垃圾回收等任务。 ### 3. JDBC 中的异常处理 - 在使用 JDBC 连接...
- 注意:不能对已经启动的线程再次调用`start()`方法,否则会抛出`IllegalThreadStateException`异常。 2. **就绪状态**: - 线程对象被创建并调用`start()`方法之后,线程进入就绪状态。 - 就绪状态的线程已...
- 必须在调用`start()`方法之前设置线程为守护线程,否则会抛出`IllegalThreadStateException`异常。 - 在守护线程中创建的新线程也会默认成为守护线程。 - 守护线程不应该直接操作资源,比如文件或数据库,因为...
- **线程进入死亡状态后,调用它的start()方法仍然可以重新启动**:线程一旦进入死亡状态,再次调用start()方法会导致`IllegalThreadStateException`异常。 #### 11. 自动滚动条的触发机制 - **知识点解析**:此...
- **IllegalThreadStateException**: 线程状态不允许请求的操作。 - **IndexOutOfBoundsException**: 索引超出允许范围。 - **InstantiationException**: 尝试创建抽象类或接口的实例。 - **InterruptedException**:...
- `IllegalThreadStateException`:非法线程状态异常。 - `IndexOutOfBoundsException`:索引越界异常。 - `InstantiationException`:实例化异常。 - `InterruptedException`:中断异常。 - `...
- `setDaemon(true)`必须在`start()`之前设置,否则会抛出`IllegalThreadStateException`异常。 - 在守护线程中产生的新线程也是守护线程。 - 守护线程应该避免直接访问重要的资源,如文件、数据库等,因为这些...
- 死亡状态的线程无法复生,即使尝试再次调用 `start()` 方法也会抛出 `IllegalThreadStateException` 异常。 ### 附加知识点 #### 线程的状态转换图 线程状态之间的转换非常灵活,可以从一种状态转换到另一种...
值得注意的是,该方法必须在`start()`方法之前调用,否则将会抛出`IllegalThreadStateException`异常。这是因为一旦线程开始运行,其状态就无法改变,包括将其设置为守护线程。 ### 2. MySQL日期格式 - **知识点...