Android; "Key dispatching timed out ..."

Southgrove picture Southgrove · Nov 20, 2010 · Viewed 22.6k times · Source

I've got a menu activity and a game activity which is launched from the menu. Some (most) of the times I launch the game activity; all input hangs for a few (up to 10-ish) seconds and then plays out in hyperspeed while i get this in logcat:

11-20 18:24:27.873: WARN/WindowManager(2473): Key dispatching timed out sending to southgrove.game/southgrove.game.Game
11-20 18:24:27.873: WARN/WindowManager(2473): Previous dispatch state: {{KeyEvent{action=1 code=4 repeat=0 meta=0 scancode=28 mFlags=8} to Window{4866c7a0 southgrove.game/southgrove.game.Game paused=false} @ 1290273811209 lw=Window{4866c7a0 southgrove.game/southgrove.game.Game paused=false} lb=android.os.BinderProxy@484e8a58 fin=false gfw=true ed=true tts=0 wf=false fp=false mcf=Window{4866c7a0 southgrove.game/southgrove.game.Game paused=false}}}
11-20 18:24:27.873: WARN/WindowManager(2473): Current dispatch state: {{null to Window{4833d500 southgrove.game/southgrove.game.Game paused=false} @ 1290273867876 lw=Window{4833d500 southgrove.game/southgrove.game.Game paused=false} lb=android.os.BinderProxy@485487b0 fin=false gfw=true ed=true tts=0 wf=false fp=false mcf=Window{4833d500 southgrove.game/southgrove.game.Game paused=false}}}

The menu activity:

package southgrove.game;

import southgrove.game.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;

public class Menu extends Activity
{

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    setContentView(R.layout.menu);

    View playButton = findViewById(R.id.play);
    playButton.setOnClickListener(new OnClickListener()
    {
        public void onClick(View view)
        {
            startActivityForResult(new Intent(Menu.this, Game.class), 0);
        }
    });

    View testButton = findViewById(R.id.test);
    testButton.setOnClickListener(new OnClickListener()
    {
        public void onClick(View view)
        {
            startActivityForResult(new Intent(Menu.this, Test.class), 0);
        }
    });

    View closeButton = findViewById(R.id.close);
    closeButton.setOnClickListener(new OnClickListener()
    {
        public void onClick(View view)
        {
            showDialog(QUIT_DIALOG);
        }
    });

}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
    if (keyCode == KeyEvent.KEYCODE_BACK)
    {
        showDialog(QUIT_DIALOG);
    }

    return super.onKeyDown(keyCode, event);
}

@Override
protected Dialog onCreateDialog(int id)
{
    Dialog dialog;

    switch (id)
    {
        case QUIT_DIALOG:
            AlertDialog.Builder quitDialogBuilder = new AlertDialog.Builder(this);
            quitDialogBuilder.setMessage("Exit the game?")
                    .setCancelable(false)
                    .setPositiveButton("Yes", new DialogInterface.OnClickListener()
                    {
                        public void onClick(DialogInterface dialog, int id)
                        {
                            Menu.this.finish();
                        }
                    })
                    .setNegativeButton("No", new DialogInterface.OnClickListener()
                    {
                        public void onClick(DialogInterface dialog, int id)
                        {
                            dialog.cancel();
                        }
                    });
            dialog = quitDialogBuilder.create();
        break;

        default:
            dialog = null;
    }

    return dialog;
}

private final int   QUIT_DIALOG = 0;

}

The game activity:

package southgrove.game;

import southgrove.droidgl.DroidGL;
import southgrove.droidgl.core.Camera;
import southgrove.droidgl.core.Node;
import southgrove.droidgl.core.RootNode;
import southgrove.game.R;
import southgrove.game.board.BoardBase;
import southgrove.game.board.core.*;
import southgrove.game.cameras.StupidCamera;
import southgrove.input.OnTouchFilter;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.widget.TextView;

public class Game extends Activity
{

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    setContentView(R.layout.game);

    // Get fpsTextView reference
    fpsTextView = (TextView) findViewById(R.id.fpsTextView);

    // Build meshes
    TetrominoMesh.buildMeshes();

    // Setup the DroidGL surface
    droidgl = (DroidGL) findViewById(R.id.droidGL);
    droidgl.setLongClickable(true);
    droidgl.setOnTouchListener(new GameSurfaceOnTouchFilter(false));

    // Create and add camera
    final Camera camera = new StupidCamera();
    camera.move(0, 0, 14);
    droidgl.registerCamera(camera);
    DroidGL.setActiveCamera(camera);

    // Create and add root node
    final Node rootNode = new RootNode();
    droidgl.setRootNode(rootNode);

    // Create and add game board
    gameBoard = new GameBoard(droidgl, 32, 32, 8);
    rootNode.addChild(gameBoard);

    // start up updateHandler
    updateHandler = new UpdateHandler();
    updateHandler.sleep(1);

    // get vibrator service
    vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
}

@Override
public void onBackPressed()
{
    showDialog(QUIT_DIALOG);
}

@Override
protected Dialog onCreateDialog(int id)
{
    Dialog dialog;

    switch (id)
    {
        case QUIT_DIALOG:
            AlertDialog.Builder quitDialogBuilder = new AlertDialog.Builder(this);
            quitDialogBuilder.setMessage("Really quit?")
                    .setCancelable(false)
                    .setPositiveButton("Yup!", new DialogInterface.OnClickListener()
                    {
                        public void onClick(DialogInterface dialog, int id)
                        {
                            Game.this.finish();
                        }
                    })
                    .setNegativeButton("Nope!", new DialogInterface.OnClickListener()
                    {
                        public void onClick(DialogInterface dialog, int id)
                        {
                            dialog.cancel();
                        }
                    });
            dialog = quitDialogBuilder.create();
        break;

        default:
            dialog = null;
    }

    return dialog;
}

@Override
protected void onPause()
{
    super.onPause();
    droidgl.onPause();
}

protected void onUpdate()
{
    fpsTextView.setText("fps: " + String.valueOf(droidgl.getFps()));
    updateHandler.sleep(500);
}

@Override
protected void onResume()
{
    super.onResume();
    droidgl.onResume();
}

private DroidGL         droidgl;
private GameBoard       gameBoard;
private TextView        fpsTextView;
private Vibrator        vibrator;

private UpdateHandler   updateHandler;

private final int       QUIT_DIALOG = 0;

private class UpdateHandler extends Handler
{
    @Override
    public void handleMessage(Message msg)
    {
        Game.this.onUpdate();
    }

    public void sleep(long delayMillis)
    {
        this.removeMessages(0);
        if (!Game.this.isFinishing())
            sendMessageDelayed(obtainMessage(0), delayMillis);
    }
}

private class GameSurfaceOnTouchFilter extends OnTouchFilter
{
    public GameSurfaceOnTouchFilter(Boolean consumeEvent)
    {
        super(consumeEvent);
    }

    private float   flipDeltaX;
    private float   flipDeltaY;

    protected void doubleTap(float x, float y)
    {
        super.doubleTap(x, y);

        synchronized (gameBoard)
        {
            if (gameBoard.dropCursorTetromino())
            {
                gameBoard.resetReactorTimer();
                gameBoard.setCursorTetromino((int) (Tetromino.NUM_TYPES * Math.random() - 1), 0);
                if (gameBoard.removeConnectedTetrominoes(3))
                {
                    gameBoard.startShockWave(0, 0, 10f, 0.35f);
                    vibrator.vibrate(300);
                } else
                {
                    vibrator.vibrate(50);
                }
            }
        }
    }

    protected void down(int pointer, float x, float y)
    {
        super.down(pointer, x, y);

        if (pointer == 0)
        {
        }

        if (pointer == 1)
        {
            flipDeltaX = 0;
            flipDeltaY = 0;
        }
    }

    protected void up(int pointer, float x, float y)
    {
        super.up(pointer, x, y);

        synchronized (gameBoard)
        {
        }
    }

    protected void move(int pointer, float x, float y, float dx, float dy)
    {
        super.move(pointer, x, y, dx, dy);

        synchronized (gameBoard)
        {
            if (pointer == 0)
            {
                gameBoard.addInertia(-dx * 0.0045f, dy * 0.0045f);
            }

            if (pointer == 1)
            {
                flipDeltaX -= dx;
                flipDeltaY += dy;

                if (Math.abs(flipDeltaX) > 45 || Math.abs(flipDeltaY) > 45)
                {
                    vibrator.vibrate(50);

                    if (Math.abs(flipDeltaX) > Math.abs(flipDeltaY))
                    {
                        if (flipDeltaX > 0)
                        {
                            gameBoard.rotateCursorTetromino(1);
                        } else
                        {
                            gameBoard.rotateCursorTetromino(-1);
                        }
                    } else
                    {
                        if (flipDeltaY > 0)
                        {
                            gameBoard.rotateCursorTetromino(1);
                        } else
                        {
                            gameBoard.rotateCursorTetromino(-1);
                        }
                    }
                    flipDeltaX = 0;
                    flipDeltaY = 0;
                }
            }
        }
    }

}

private class GameBoard extends BoardBase
{

    public GameBoard(DroidGL droidGL, int width, int height, int depth)
    {
        super(droidGL, width, height, depth);
    }

    public void resetReactorTimer()
    {
        synchronized (this)
        {
            reactorTimer = 0;
        }
    }

    public void startShockWave(int gridPosX, int gridPosY, float length, float magnitude)
    {
        synchronized (this)
        {
            for (int i = 0; i < tetrominoes.size(); i++)
            {
                tetrominoes.get(i).bounce(
                        (float) (Math.random() * 0.12f),
                        (float) (Math.random() * magnitude)
                        );
            }
        }
    }

    @Override
    protected void onSetup()
    {
        synchronized (this)
        {
            setCursorTetromino((int) (Tetromino.NUM_TYPES * Math.random()), 0);

            for (int i = 0; i < 1024; i++)
            {
                addTetromino(
                        (int) (Math.random() * Tetromino.NUM_TYPES),
                        (int) (Math.random() * width / 2) * 2,
                        (int) (Math.random() * height / 2) * 2,
                        (int) (Math.random() * 2),
                        (int) (Math.random() * 4));
            }

            removeConnectedTetrominoes(3);
        }
    }

    @Override
    protected void onLogic(float timeFactor)
    {
        synchronized (this)
        {
            if (shiftTimer > shiftTime || tetrominoes.size() < 15)
            {
                shiftTimer = 0;
                shiftTime -= shiftTime / 5;

                shiftUp();

                for (int i = 0; i < 256; i++)
                {
                    addTetromino(
                            (int) (Math.random() * Tetromino.NUM_TYPES),
                            (int) (Math.random() * width / 2) * 2,
                            (int) (Math.random() * height / 2) * 2,
                            0,// (int) (Math.random() * (depth - 1)),
                            (int) (Math.random() * 4));
                }
            }

            if (reactorTimer > 1f)
            {
                reactorTimer = 0;

                drop();
                removeConnectedTetrominoes(3);
            }

            reactorTimer += timeFactor;
            shiftTimer += timeFactor;
        }
    }

    private float   shiftTime   = 60 * 5;
    private float   reactorTimer;
    private float   shiftTimer;
}

}

What could be causing this? Any ideas/speculations are welcome. And yes, I know that's a pretty massive wall of code to sift through.

Answer

paiego picture paiego · Feb 19, 2011

One common cause of the "Key dispatching timed out", which I experienced quite often until digging a little deeper, is holding onto the UI thread - which handles UI events - in the debugger for more than a short amount of time (details below). For instance, if you want to debug code in your event handler, this is potential problem.

For example, if you set a breakpoint in onTouchEvent() of an Activity

class MyActivity extends Activity
{
    public boolean onTouchEvent(MotionEvent me)
    {
        // ** Breakpoint ** 
        // Code you wish to debug
    }
}

... and you hold on to this thread (UI):

After 5 seconds you will get this warning: Key dispatching timed out sending to com.hos/com.hos.MyActivity ... null to Window ...

After 20 seconds you will get: Key dispatching timed out sending to com.hos/com.hos.MyActivity ... null to Window ... Continuing to wait for key to be dispatched

After 35 seconds you will get: Key dispatching timed out sending to com.hos/com.hos.MyActivity ... null to Window ... timed out expired process next Key & find new target

At this point, not only is the application frozen but so is the phone. Quite often I need to wait for the ANR and sometimes hard restart the phone.

So one simple answer is don't hold on to the UI thread, whether it be with the debugger or with time-expensive code.

=============

Regarding synchronization, this is a very similar problem. In this example, an onTouchEvent() may have to wait for the population of a non-thread-safe shared resource. In which case it may timeout if population is occurring during the touch event.

class MyActivity extends Activity
{
    private static ArrayList<Object> m_alShared = new ArrayList<Object>();

    public boolean onTouchEvent(MotionEvent me)
    {
        synchronized(this)
        {
            // accessed shared resource.
            m_alShared.get(?);
        }
    }

    public void methodCalledByBackgroundThread()
    {
        synchronized(this)
        {
            // populate shared resource for more than 35 seconds
            while (/* time < 35 seconds */)
                m_alShared.add(?);
        }
    }
}

Personally, I choose to not synchronize or use any "wait" function on the UI thread. Or if you need to, make sure it's quick. It's a race condition waiting to happen. Especially if it affects not only your app, but your phone.

i.e. I might opt for the following solution and synchronize each add.

    public void methodCalledByBackgroundThread()
    {
        while (/* time < 35 seconds */)
        {
            synchronized(this)
            {
                // populate shared resource for more than 35 seconds
                m_alShared.add(?);
            }
        }
    }