我有一个 SurfaceView 设置并正在运行,但是当我恢复它时,我收到一个错误,表明线程已经启动。当应用程序进入后台然后返回前台时,正确的处理方法是什么?我已经进行了修改并设法让应用程序返回而不会崩溃......但是 SurfaceView 不再绘制任何内容。我的代码:
@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;
}
简单的解决方案是简单地终止并重新启动线程。创建方法resume() - 创建线程对象并启动它 - 和pause() - 杀死线程(参见Lunarlander示例) - 在SurfaceView类中,并从surfaceCreated和surfaceDestroyed调用这些方法来启动和停止线程。
现在在运行SurfaceView的Activity中,您还需要从Activity(或fragment)的onResume()和onPause()调用SurfaceView中的resume()和pause()方法。这不是一个优雅的解决方案,但它会起作用。
这个错误似乎与非常有名的月球着陆器错误有关(用谷歌搜索一下)。过了这么久,几个android版本发布之后,这个bug仍然存在, 没有人费心去更新它。我发现这可以使代码混乱最少:
public void surfaceCreated(SurfaceHolder holder) {
if (thread.getState==Thread.State.TERMINATED) {
thread = new MainThread(getHolder(),this);
}
thread.setRunning(true);
thread.start();
}
我发现的最好方法是重写控制 Surface View 的 Activity 的 onResume 方法,以便使用该方法重新实例化 SurfaceView,然后使用 setContentView 设置它。这种方法的问题是您需要重新加载 SurfaceView 正在处理的任何状态。
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyCustomSurfaceView(this));
}
@Override
protected void onResume() {
super.onResume();
setContentView(new MyCustomSurfaceView(this));
}
尝试对上面已接受的答案发表评论,但不能,这是新的。我认为您不应该从 SurfaceView 和 Activity 调用启动/停止线程方法。这将导致双重启动/停止线程,并且您不能多次启动线程。只需从 Activity 的 onPause 和 onResume 调用您的方法即可。它们在退出和重新进入应用程序时被调用,因此这将确保您的状态得到正确处理。 surfaceDestroyed 并不总是被调用,这让我有一段时间很困惑。
如果您使用此方法,请确保在使用画布之前检查运行代码中的有效表面,因为活动将在表面可用之前在 onResume 中启动线程。
while (_run) {
if (_surfaceHolder.getSurface().isValid()) {
...
}
} //end _run
这是我用过的。该应用程序现在不会崩溃。
查看班级:
holder.addCallback(new Callback() {
public void surfaceDestroyed(SurfaceHolder holder) {
gameLoopThread.setRunning(false);
gameLoopThread.stop();
}
public void surfaceCreated(SurfaceHolder holder) {
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
在 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){}
}
}
希望对你有帮助。
您应该使用 Activity 的 onPause() 和 onResume() 方法。
首先,在surfaceCreated()中,启动线程。另外,在 onResume() 中,确保线程尚未启动(在线程内保留一个变量或其他内容)。然后,如果它没有运行,请再次将其设置为运行。在 onPause() 中,暂停线程。在surfaceDestroyed中,再次暂停线程。
这个众所周知的问题的另一个解决方案。可悲的是,我不明白它为什么起作用——它是意外出现的。但它对我来说效果很好,而且很容易实现:不需要重写
Activity
的 onPause()
、onResume()
、onStart()
、onStop()
,也不编写特殊的线程方法(如 resume()
、pause()
)
)是必需的。
特殊要求是将所有变化的变量放在渲染线程类之外的其他地方。
渲染线程类添加要点:
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
}
}
}
现在,关于
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) {
}
}
}
上面的两个代码块不是完整编写的类,而只是需要哪些命令、哪些方法的概念。另请注意,每次返回应用程序都会调用
surfaceChanged()
。
很抱歉回答这么耗时。我希望它能正常工作并有所帮助。
你真的不需要做任何事情,真的。 Surface 只是想抱怨它已经在运行,并且它通过抛出错误来做到这一点。将其包装在 try catch 中并忽略错误。
private suspend fun SurfaceView.prepareSurface(): Surface = suspendCoroutine { cont ->
val callback = object : SurfaceHolder.Callback {
override fun surfaceDestroyed(holder: SurfaceHolder) {
}
override fun surfaceChanged(
holder: SurfaceHolder,
format: Int,
width: Int,
height: Int
) = onSurface(holder)
override fun surfaceCreated(holder: SurfaceHolder) = onSurface(holder)
private fun onSurface(holder: SurfaceHolder) {
post {
try {
if (holder.surface.isValid) cont.resume(holder.surface)
} catch (e: IllegalStateException) {
// Ignore error to persist UI config changes
}
}
}
}
holder.addCallback(callback)
}