当应用程序关闭并重新打开时,Android崩溃了。

12

我有一个非常简单的安卓应用程序,只显示一个空白的白屏。当我按HOME按钮关闭应用程序,然后再次尝试打开应用程序时,它会崩溃并出现“强制关闭”按钮。在Eclipse中,我收到了这个错误,“ActivityManager:警告:由于当前活动正在为用户保留,因此未启动活动。”。我该如何解决这个崩溃问题?

public class HelloAndroid extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    getWindow().setFlags(   WindowManager.LayoutParams.FLAG_FULLSCREEN,   
                            WindowManager.LayoutParams.FLAG_FULLSCREEN); 

    setContentView(new Panel(this));
}

class Panel extends SurfaceView implements SurfaceHolder.Callback {

    private TutorialThread _thread;

    public Panel(Context context) {
        super(context);

        // register our interest in hearing about changes to our surface
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
        _thread = new TutorialThread(holder, this);

        setFocusable(true);
    }

    @Override
    public void onDraw(Canvas canvas) {

        // Clear the background
        canvas.drawColor(Color.WHITE);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // resize canvas here
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        _thread.setRunning(true);
        _thread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // simply copied from sample application LunarLander:
        // we have to tell thread to shut down & wait for it to finish, or else
        // it might touch the Surface after we return and explode
        boolean retry = true;
        _thread.setRunning(false);
        while (retry) {
            try {
                _thread.join();
                retry = false;
            } catch (InterruptedException e) {
                // we will try it again and again...
            }
        }
    }
}

class TutorialThread extends Thread {
    private SurfaceHolder _surfaceHolder;
    private Panel _panel;
    private boolean _run = false;

    public TutorialThread(SurfaceHolder surfaceHolder, Panel panel) {
        _surfaceHolder = surfaceHolder;
        _panel = panel;
    }

    public void setRunning(boolean run) {
        _run = run;
    }

    @Override
    public void run() {
        Canvas c;
        while (_run) {
            c = null;
            try {
                c = _surfaceHolder.lockCanvas(null);
                synchronized (_surfaceHolder) {
                    _panel.onDraw(c);
                }
            } finally {
                // do this in a finally so that if an exception is thrown
                // during the above, we don't leave the Surface in an
                // inconsistent state
                if (c != null) {
                    _surfaceHolder.unlockCanvasAndPost(c);
                }
            }
        }
    }
}

添加 LogCat

03-15 15:36:05.579: INFO/AndroidRuntime(4441): NOTE: attach of thread 'Binder Thread #2' failed
03-15 15:36:05.719: DEBUG/AndroidRuntime(4449): >>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<
03-15 15:36:05.719: DEBUG/AndroidRuntime(4449): CheckJNI is OFF
03-15 15:36:05.719: DEBUG/dalvikvm(4449): creating instr width table
03-15 15:36:05.759: DEBUG/AndroidRuntime(4449): --- registering native functions ---
03-15 15:36:05.969: INFO/ActivityManager(1294): Starting activity: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.helloandroid/.HelloAndroid }
03-15 15:36:05.979: DEBUG/Launcher(1371): onPause+
03-15 15:36:05.979: DEBUG/Launcher.DragController(1371): +endDrag: false
03-15 15:36:05.979: DEBUG/Launcher.DragController(1371): mDragging == false
03-15 15:36:05.979: DEBUG/Launcher.DragController(1371): -endDrag: false
03-15 15:36:05.979: DEBUG/Launcher(1371): onPause-
03-15 15:36:05.999: DEBUG/AndroidRuntime(4428): Shutting down VM
03-15 15:36:05.999: DEBUG/AndroidRuntime(4449): Shutting down VM
03-15 15:36:05.999: WARN/dalvikvm(4428): threadid=1: thread exiting with uncaught exception (group=0x4001d7e0)
03-15 15:36:06.009: DEBUG/dalvikvm(4449): Debugger has detached; object registry had 1 entries
03-15 15:36:06.009: INFO/AndroidRuntime(4449): NOTE: attach of thread 'Binder Thread #3' failed
03-15 15:36:06.029: ERROR/AndroidRuntime(4428): FATAL EXCEPTION: main
03-15 15:36:06.029: ERROR/AndroidRuntime(4428): java.lang.IllegalThreadStateException: Thread already started.
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at java.lang.Thread.start(Thread.java:1322)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at com.example.helloandroid.HelloAndroid$Panel.surfaceCreated(HelloAndroid.java:55)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at android.view.SurfaceView.updateWindow(SurfaceView.java:538)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at android.view.SurfaceView.onWindowVisibilityChanged(SurfaceView.java:206)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at android.view.View.dispatchWindowVisibilityChanged(View.java:3888)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:725)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:725)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at android.view.ViewRoot.performTraversals(ViewRoot.java:748)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1737)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at android.os.Handler.dispatchMessage(Handler.java:99)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at android.os.Looper.loop(Looper.java:123)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at android.app.ActivityThread.main(ActivityThread.java:4627)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at java.lang.reflect.Method.invokeNative(Native Method)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at java.lang.reflect.Method.invoke(Method.java:521)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
03-15 15:36:06.029: ERROR/AndroidRuntime(4428):     at dalvik.system.NativeStart.main(Native Method)
03-15 15:36:06.039: WARN/ActivityManager(1294):   Force finishing activity com.example.helloandroid/.HelloAndroid
03-15 15:36:06.541: WARN/ActivityManager(1294): Activity pause timeout for HistoryRecord{450300c0 com.example.helloandroid/.HelloAndroid}
03-15 15:36:06.549: DEBUG/Launcher(1371): onResume+
03-15 15:36:06.549: DEBUG/Launcher.DragController(1371): +endDrag: false
03-15 15:36:06.549: DEBUG/Launcher.DragController(1371): mDragging == false
03-15 15:36:06.549: DEBUG/Launcher.DragController(1371): -endDrag: false
03-15 15:36:06.549: DEBUG/Launcher(1371): onResume-
03-15 15:36:08.645: ERROR/KINETO(1370): KLOG0C3- xmk_QueryOSQueue SDL Queue empty : WAIT_FOREVER 

是的,错误是由你的“线程”引起的。请看下面我的答案。 - Wroclai
1
LunarLander和JetBoy在被HOME键打断并重新启动后都会崩溃! - Lumis
1
这里讨论了SurfaceView / Activity生命周期:https://source.android.com/devices/graphics/architecture.html#activity。Grafika中的工作代码示例链接如下:https://dev59.com/O3zaa4cB1Zd3GeqPLyDU#21684399 - fadden
4个回答

20

我曾经在这里回答过类似的问题

你得到的错误可能是由你的Thread引起的(但没有看到完整的Logcat很难确定)。你每次创建surface时都会启动它,这将导致你的应用程序崩溃,因为你不能调用Thread.start()两次。查看上面链接中更详细的说明以及如何解决该问题。

既然我的解释还不够清楚,我将发布整个解决方案:

在你的Runnable / Thread内部:

private Object mPauseLock = new Object();  
private boolean mPaused;

// Constructor stuff.      

// This should be after your drawing/update code inside your thread's run() code.
synchronized (mPauseLock) {
    while (mPaused) {
        try {
            mPauseLock.wait();
        } catch (InterruptedException e) {
        }
    }
}

// Two methods for your Runnable/Thread class to manage the thread properly.
public void onPause() {
    synchronized (mPauseLock) {
        mPaused = true;
    }
}

public void onResume() {
    synchronized (mPauseLock) {
        mPaused = false;
        mPauseLock.notifyAll();
    }
}

在你的SurfaceView类中:

private boolean mGameIsRunning;

@Override
public void surfaceCreated(SurfaceHolder holder) {
    // Your own start method.
    start();
}

public void start() {
    if (!mGameIsRunning) {
        thread.start();
        mGameIsRunning = true;
    } else {
        thread.onResume();
    }
}

Viktor,我无法让你的代码与问题中的线程一起工作。在surfaceDestroyed中调用了线程的关闭,并且每次用户离开此活动时都会运行,包括按Home按钮时。 - Lumis
@Lumis:你不应该在surfaceDestroyed中关闭它,因为这是不必要的(我们已经在onPause中暂停了它)。你应该在onResumeonPauseonDestroy中处理Thread - Wroclai
在制作类似于Fraps的屏幕叠加显示动态数据时,使用了这个代码片段,效果非常好。非常感谢@Wroclai :) - Sourav Banerjee

3
下面的解决方案已经测试过。代码检查线程状态,如果线程终止,则创建一个新线程。没有崩溃,唯一的问题是游戏状态没有保存,因此从HOME键返回时,游戏将重新开始。 附注:记得从Lunarview视图传递上下文并设置为mContextLunarView。 希望这可以帮助你。这些论坛很棒,继续保持。
public void surfaceCreated(SurfaceHolder holder) {
    // start the thread here so that we don't busy-wait in run()
    // waiting for the surface to be created    

    if(thread.getState() == Thread.State.TERMINATED) {
        //LunarView Thread state TERMINATED..make new...under CheckCreateThread

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

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

2

这里有一个简单的解决方案,它可能在某些情况下是可接受的,例如背景动画屏幕和不需要恢复状态的活动-表面视图活动需要在暂停时终止。

protected void onPause()  { 
 super.onPause(); 
 finish();
} 

更好的解决方案是将线程的创建从构造函数移动到onSurfaceCreated中,代码如下:
@Override
public void surfaceCreated(SurfaceHolder holder) {
  _thread = new TutorialThread(holder, this);
  _thread.setRunning(true);
  _thread.start(); 
}

然后在线程循环中创建一个暂停标志:
if(!pause){
  _panel.onDraw(c); 
}

最后,在 onPause 和 onRestore 中为活动设置暂停标志:

   protected void onResume() {
            super.onResume();
            pause = false;    
        }

        protected void onPause()  {   
            super.onPause();   
            pause = true;   
        } 

当用户点击Home按钮时,将调用surfaceDestroyed方法来关闭当前线程"_thread"。当用户返回应用程序时,surfaceCreated方法将会把引用"_thread"分配给一个新的线程,而旧的线程对象将被垃圾回收器移除。


2
这不是一个正确的处理方式。 - Wroclai
1
如果你知道怎么做,为什么不编辑问题中的代码以使其正常工作呢?否则这都是理论...当我想出一个可行的代码时,我会发布它。 - Lumis
@Lumis:请查看我的回答和其中的链接。那里有一个解决方案,你必须正确地管理Thread,使用synchronized和锁。 - Wroclai
我认为,由于问题中提供的示例来自Android SDK游戏MoonLander和JetBoy,因此将被许多人使用,我们应该编写一个可行的代码/解决方案并在此处发布,以便其他人不必重复造轮子,并可以从一个好的示例中学习。 - Lumis
@Lumis:那就等等吧,我会发布整个解决方案。 - Wroclai
@Lumis:现在看看我的答案,里面有完整的解决方案。 - Wroclai

0

当您想通过点击返回按钮退出时,请使用以下方法...

boolean doubleBackToExitPressedOnce = false;
@Override
public void onBackPressed() {
    if (doubleBackToExitPressedOnce) {

        //**completely exit the app like this**//
      
        System.exit(0);

        return;
    }

    this.doubleBackToExitPressedOnce = true;
    Toast.makeText(this, "Tap again to exit", Toast.LENGTH_SHORT).show();

    new Handler().postDelayed(() -> doubleBackToExitPressedOnce=false, 2000);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接