可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a surfaceView setup and running, but when I resume it I get an error that the thread has already been started. What's the proper way to handle when the app goes to the background and then back to the foreground? I've tinkered around and managed to get the app to come back without crashing... but the surfaceView doesn't draw anything anymore. My code:
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.e("sys","surfaceCreated was called.");
if(systemState==BACKGROUND){
thread.setRunning(true);
}
else {
thread.setRunning(true);
thread.start();
Log.e("sys","started thread");
systemState=READY;
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.e("sys","surfaceDestroyed was called.");
thread.setRunning(false);
systemState=BACKGROUND;
}
回答1:
The easy solution is to simply kill and restart the thread. Create methods resume() - creates thread object and starts it - and pause() - kills thread (see Lunarlander example) - in your SurfaceView class and call these from surfaceCreated and surfaceDestroyed to start and stop the thread.
Now in the Activity that runs the SurfaceView, you will also need to call the resume() and pause() methods in the SurfaceView from the Activity's (or fragment's) onResume() and onPause(). It's not an elegant solution, but it will work.
回答2:
This bug appears to relate to the lunar lander bug, which is quite famous (do a Google search on it). After all this time, and after several android version releases, the bug still exists and
no one has bothered to update it. i have found this to work with the least code clutter:
public void surfaceCreated(SurfaceHolder holder) {
if (thread.getState==Thread.State.TERMINATED) {
thread = new MainThread(getHolder(),this);
}
thread.setRunning(true);
thread.start();
}
回答3:
public void surfaceCreated(SurfaceHolder holder) {
if (!_thread.isAlive()) {
_thread = new MyThread(this, contxt);
}
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
_thread.setRunning(false);
while (retry) {
try {
_thread.join();
retry = false;
} catch (InterruptedException e) {
// we will try it again and again...
}
}
}
回答4:
The best way I have found is to override the onResume method of the activity controlling the surface view so that with the method it re-instantiates the SurfaceView and then sets it with setContentView. The problem with this approach is that you need to reload any state that your SurfaceView was taking care of.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyCustomSurfaceView(this));
}
@Override
protected void onResume() {
super.onResume();
setContentView(new MyCustomSurfaceView(this));
}
回答5:
Tried to comment on the accepted answer above but couldn't, new to this. I don't think you should be calling your start/stop thread methods from both your SurfaceView and Activity. This will result in starting/stopping the thread doubly, and you can't start a thread more than once. Just call your methods from the Activity's onPause and onResume. They're called when exiting and re-entering the app so this will make sure your states are handled properly. surfaceDestroyed isn't always called, which messed me up for a while.
If you use this method make sure to check for a valid surface in your run code before working with your canvas, because the Activity will start the thread in onResume before the surface is available.
while (_run) {
if (_surfaceHolder.getSurface().isValid()) {
...
}
} //end _run
回答6:
This is what I have used. The app does not crashes now.
View Class:
holder.addCallback(new Callback() {
public void surfaceDestroyed(SurfaceHolder holder) {
gameLoopThread.setRunning(false);
gameLoopThread.stop();
}
public void surfaceCreated(SurfaceHolder holder) {
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
In the GameLoopThread :
private boolean running = false;
public void setRunning(boolean run) {
running = run;
}
@Override
public void run() {
long ticksPs=1000/FPS;
long startTime;
long sleepTime;
while(running){
Canvas c = null;
startTime=System.currentTimeMillis();
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
sleepTime=ticksPs-(System.currentTimeMillis()-startTime);
try{
if(sleepTime>0){
sleep(sleepTime);
}
else
sleep(10);
} catch(Exception e){}
}
}
I hope it will help.
回答7:
You should use the Activities onPause() and onResume() methods.
First, in surfaceCreated(), start the thread. Also, in onResume(), make sure the thread isn't already started (keep a variable inside the thread or something). Then if it is not running, set it as running again. in onPause(), pause the thread. In surfaceDestroyed, pause the thread again.
回答8:
Another solution for this good-known problem. Sadly, I don't understand why it works -- it came out accidentally. But it works good for me and it's easy to implement: no overriding of Activity
's onPause()
, onResume()
, onStart()
, onStop()
, nor writing of special thread methods (like resume()
, pause()
) are required.
Special requirement is to put all changing variables in something other than rendering thread class.
Main points to add to render-thread class:
class RefresherThread extends Thread {
static SurfaceHolder threadSurfaceHolder;
static YourAppViewClass threadView;
static boolean running;
public void run (){
while(running){
//your amazing draw/logic cycle goes here
}
}
}
Now, important things about YourAppViewClass
:
class YourAppViewClass extends SurfaceView implements SurfaceHolder.Callback {
static RefresherThread surfaceThread;
public YourAppViewClass(Activity inpParentActivity) {
getHolder().addCallback(this);
RefresherThread.threadSurfaceHolder = getHolder();
RefresherThread.threadView = this;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
surfaceThread = new RefresherThread();
surfaceThread.running=true;
surfaceThread.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
surfaceThread.running=false;
try {
surfaceThread.join();
} catch (InterruptedException e) {
}
}
}
Two code blocks above are not full-written classes, but mere notion of which commands in which methods are needed. Also note that each return to app invokes surfaceChanged()
.
Sorry for such space-consuming answer. I hope it will work properly and will help.