Андроид; Тайм-аут отправки ключа истек

У меня есть действие меню и действие игры, которое запускается из меню. Некоторые (большинство) раз я запускаю игровую активность; весь ввод зависает на несколько (до 10-ти) секунд, а затем воспроизводится на гиперскорости, пока я получаю это в 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}}}

Активность меню:

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;

}

Игровая деятельность:

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;
}

}

Что может быть причиной этого? Любые идеи/догадки приветствуются. И да, я знаю, что это довольно большая стена кода, через которую нужно просеять.


person Southgrove    schedule 20.11.2010    source источник
comment
Глупый вопрос: вы начинаете в режиме отладки, потому что я заметил, что в Android иногда это может быть очень МЕДЛЕННО?   -  person Sephy    schedule 20.11.2010


Ответы (1)


Одной из распространенных причин «превышение времени ожидания диспетчеризации ключей», с которой я сталкивался довольно часто, пока не копнул немного глубже, является удержание потока пользовательского интерфейса, который обрабатывает события пользовательского интерфейса, в отладчике более короткого промежутка времени (подробности ниже) . Например, если вы хотите отладить код в обработчике событий, это может стать проблемой.

Например, если вы установите точку останова в onTouchEvent() действия

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

... и вы держитесь за эту ветку (UI):

Через 5 секунд вы получите это предупреждение: Истекло время отправки ключа на com.hos/com.hos.MyActivity... null to Window...

Через 20 секунд вы получите: Время отправки ключа истекло, отправив его на com.hos/com.hos.MyActivity... null to Window... Продолжая ждать отправки ключа

Через 35 секунд вы получите: Время отправки ключа истекло, отправив его на com.hos/com.hos.MyActivity... ноль в окно... истекло время ожидания процесса следующего ключа и найти новую цель

На данный момент зависает не только приложение, но и телефон. Довольно часто мне нужно дождаться ANR, а иногда и жестко перезагрузить телефон.

Таким образом, один простой ответ: не держитесь за поток пользовательского интерфейса, будь то с отладчиком или с затратным по времени кодом.

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

Что касается синхронизации, это очень похожая проблема. В этом примере onTouchEvent() может ожидать заполнения общего ресурса, не поддерживающего многопоточность. В этом случае может истечь время ожидания, если во время события касания происходит заполнение.

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(?);
        }
    }
}

Лично я предпочитаю не синхронизировать и не использовать какую-либо функцию ожидания в потоке пользовательского интерфейса. Или, если вам нужно, убедитесь, что это быстро. Это состояние гонки ждет своего часа. Особенно, если это влияет не только на ваше приложение, но и на ваш телефон.

то есть я мог бы выбрать следующее решение и синхронизировать каждое добавление.

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