简体   繁体   中英

App Crashes on Android 4.0, but not 2.3 — Strange Exception

I'm developing an android game on 2.3 and have started testing on more devices so that I can release it.

On some, not all 4.0 devices my game crashes when minimized. You can lock the phone and unlock it, and the game resumes and pauses just fine. The crash occurs when you try to return to the home screen.

The error logs are shown below:

07-18 14:33:44.839 E/AndroidRuntime(15542)FATAL EXCEPTION: Thread-662
07-18 14:33:44.839 E/AndroidRuntime(15542)java.lang.NullPointerException
07-18 14:33:44.839 E/AndroidRuntime(15542)at com.petronicarts.stormthecastle.MainThread.run(MainThread.java:55)
07-18 14:33:44.846 W/IInputConnectionWrapper(15542)showStatusIcon on inactive InputConnection
07-18 14:33:45.081 I/ActivityManager(178)No longer want com.android.packageinstaller (pid 15351): hidden #16
07-18 14:33:45.143 W/InputDispatcher(178)channel '41bb4d28 com.android.packageinstaller/com.android.packageinstaller.InstallAppProgress (server)' ~ Consumer closed input channel or an error occurred.  events=0x8
07-18 14:33:45.143 E/InputDispatcher(178)channel '41bb4d28 com.android.packageinstaller/com.android.packageinstaller.InstallAppProgress (server)' ~ Channel is unrecoverably broken and will be disposed!
07-18 14:33:45.190 W/InputDispatcher(178)Attempted to unregister already unregistered input channel '41bb4d28 com.android.packageinstaller/com.android.packageinstaller.InstallAppProgress (server)'
07-18 14:33:45.190 I/WindowManager(178)WIN DEATH: Window{41bb4d28 com.android.packageinstaller/com.android.packageinstaller.InstallAppProgress paused=false}
07-18 14:33:45.198 I/WindowManager(178)WINDOW DIED Window{41bb4d28 com.android.packageinstaller/com.android.packageinstaller.InstallAppProgress paused=false}
07-18 14:33:47.268 D/dalvikvm(178)GC_EXPLICIT freed 338K, 20% free 22816K/28487K, paused 6ms+5ms
07-18 14:34:15.464 I/power   (178)*** set_screen_state 0
07-18 14:34:15.471 D/SurfaceFlinger(115)About to give-up screen, flinger = 0x1822918
07-18 14:34:15.596 D/NfcService(403)NFC-C OFF, disconnect

I believe the important line is this:

com.petronicarts.stormthecastle.MainThread.run(MainThread.java:55)

Looking at line 55, it is this:

canvas.setMatrix(matrix);

within my run thread:

    @Override
public void run() 
{
    boolean ScaleGame = true;
    //boolean SkipFrame = false;
    Bitmap screen = Bitmap.createBitmap(960, 540, Config.RGB_565);
    Canvas canvas;
    Canvas canvas2 = new Canvas(screen);
    Paint paint = new Paint();
    Matrix matrix = new Matrix();
    matrix.preScale(gamePanel.getScaleX(), gamePanel.getScaleY());
    if (gamePanel.getScaleX() == 1 && gamePanel.getScaleY() == 1)
        ScaleGame = false;
    long startTime, elapsedTime;
    startTime = System.currentTimeMillis();
    elapsedTime = System.currentTimeMillis() - startTime;

    this.gamePanel.setScreenBitmap(screen);

    while (running) {
        if(!pleaseWait) {
            canvas = null;
            // try locking the canvas for exclusive pixel editing on the surface
            try {
                canvas = this.surfaceHolder.lockCanvas();
                if (ScaleGame)
                    canvas.setMatrix(matrix);
                synchronized (surfaceHolder) {
                    startTime = System.currentTimeMillis();
                    this.gamePanel.update((float)elapsedTime);

                    canvas.drawBitmap(screen, 0, 0, paint);
                    elapsedTime = System.currentTimeMillis() - startTime;

                }
            } finally {
                if (canvas != null) {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }            
        }
        else {
            synchronized (this) {
                try {
                    wait();
                } catch (Exception e) { }
            }
        }
    }
}

I have no idea why I would be unable to set the matrix. It seems like the code should work just fine. I assume that that means I am handling something in a way that only works on 2.3.

My pause, resume, and destroy events are shown here:

public void pause() {
    justPause = true;
    pauseGame = true;


    SharedPreferences fileStore = this.getContext().getSharedPreferences("userData", 0);
    SharedPreferences.Editor editor = fileStore.edit();
    editor.putInt("highscore", highscore);
    editor.commit();

    AudioService.StopMusic();

}

public void resume(Context context) {
    if (gameState == 1)
        AudioService.StartMusic();
    //gold += 1000;
}

public void destroy() {
    thread.setRunning(false);


    if (thread != null)
    {
        Thread killThread = thread;
        thread = null;
        killThread.interrupt();
    }   
}

If you have any ideas, they would be very welcome. Thanks.

One issue I see is after calling "canvas = this.surfaceHolder.lockCanvas();" you only check for null in the finally block. I would change it to look like this:

if(!pleaseWait) {
    canvas = this.surfaceHolder.lockCanvas();

    if (canvas != null) {
        // try locking the canvas for exclusive pixel editing on the surface
        try {
            if (ScaleGame)
                canvas.setMatrix(matrix);
            synchronized (surfaceHolder) {
                startTime = System.currentTimeMillis();
                this.gamePanel.update((float)elapsedTime);
                canvas.drawBitmap(screen, 0, 0, paint);
                elapsedTime = System.currentTimeMillis() - startTime;
        }
        finally {
            surfaceHolder.unlockCanvasAndPost(canvas);
        }            
    }
}

I would also ask if the synchronized block should run if the canvas matrix is not set (when ScaleGame is false). I don't have enough insight into the problem or Canvas in order to answer that. If the answer is no then the synchronized block should be encased in the if statement.

You should never hold a lock on the UI thread, as it can block the UI thread and prevent the user from interacting smoothly with your app. The documentation for lockCanvas suggests to synchronize with a separate drawing thread instead:

If null is not returned, lockCanvas() internally holds a lock until the corresponding unlockCanvasAndPost(Canvas) call, preventing SurfaceView from creating, destroying, or modifying the surface while it is being drawn. This can be more convenient than accessing the Surface directly, as you do not need to do special synchronization with a drawing thread in Callback.surfaceDestroyed .

Android 3.0+ introduced StrictMode checks to ensure developers didn't abuse the UI thread, so it is no surprise that your app works on Android 2.3, but not on Android 4.0.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM