Keyboard Handler Up Up Down Down Left Right

The KeyboardHandler has to fulfill a couple of tasks. First it must hook up with the View from which keyboard events are to be received. Next it must store the current state of each key for polling. It must also keep a list of KeyEvent instances, which we designed in Chapter 3 for event-based input handling. Finally it must properly synchronize all this, as it will receive events on the UI thread while being polled from our main game loop, which is executed on a different thread. Quite a lot of work. As a little refresher, let me show you the KeyEvent class again, which we defined in Chapter 3 as part of the Input interface:

public static class KeyEvent {

public static final int KEY_DOWN = 0; public static final int KEY_UP = 1;

public int type; public int keyCode; public char keyChar;

It simply defines two constants encoding the key event type along with three members, holding the type, key code, and Unicode character of the event. With this we can implement our handler.

Listing 5-7 shows the implementation of the handler with the Android APIs discussed earlier and our new Pool class.

Listing 5-7. KeyboardHandler.java: Handling Keys Since 2010

package com.badlogic.androidgames.framework.impl;

import java.util.ArrayList; import java.util.List;

import android.view.View;

import android.view.View.OnKeyListener;

import com.badlogic.androidgames.framework.Input.KeyEvent;

import com.badlogic.androidgames.framework.Pool;

import com.badlogic.androidgames.framework.Pool.PoolObjectFactory;

public class KeyboardHandler implements OnKeyListener { boolean[] pressedKeys = new boolean[128];

Pool<KeyEvent> keyEventPool;

List<KeyEvent> keyEventsBuffer = new ArrayList<KeyEvent>(); List<KeyEvent> keyEvents = new ArrayList<KeyEvent>();

The KeyboardHandler class implements the OnKeyListener interface so that it can receive key events from a View. Next up are the members.

The first member is an array holding 128 booleans. We'll store the current state (pressed or not) of each key in this array. It is indexed by the key code of a key. Luckily for us, the android.view.KeyEvent.KEYCODE_XXX constants (which encode the key codes) are all in the range between 0 and 127, so we can store them in this garbage collector-friendly form. Note that by an unlucky accident our KeyEvent class shares its name with the Android KeyEvent class, instances of which get passed to our OnKeyEventListener.onKeyEvent() method. This slight confusion is limited to this handler code only. As there's hardly a better name for a key event than KeyEvent, we chose to live with this short-lived confusion.

The next member is a Pool that holds instances of our KeyEvent class. We don't want to make the garbage collector angry, so we recycle all the KeyEvent objects we create.

The third member stores the KeyEvents that have not yet been consumed by our game. Each time we get a new key event on the UI thread we'll add it to this list.

The last member stores the KeyEvents we'll return upon a call to KeyboardHandler.getKeyEvents(). We'll see why we have to double-buffer the key events in a minute.

public KeyboardHandler(View view) {

PoolObjectFactory<KeyEvent> factory = new PoolObjectFactory<KeyEvent>() { @Override public KeyEvent createObject() {

return new KeyEvent();

keyEventPool = new Pool<KeyEvent>(factory, 100); view.setOnKeyListener(this); view.setFocusableInTouchMode(true); view.requestFocus();

The constructor has a single parameter consisting of the View we want to receive key events from. We create the Pool instance with a proper PoolObjectFactory, register the handler as an OnKeyListener with the View, and finally make sure that the View will receive key events by making it the focused View.

@Override public boolean onKey(View v, int keyCode, android.view.KeyEvent event) { if (event.getAction() == android.view.KeyEvent. ACTION_MULTIPLE) return false;

synchronized (this) {

KeyEvent keyEvent = keyEventPool.newObject(); keyEvent.keyCode = keyCode;

keyEvent.keyChar = (char) event.getUnicodeChar(); if (event.getAction() == android.view.KeyEvent.ACTION_DOWN) { keyEvent.type = KeyEvent.KEY_DOWN; if(keyCode > 0 && keyCode < 127) pressedKeys[keyCode] = true;

if (event.getAction() == android.view.KeyEvent.ACTION_UP) { keyEvent.type = KeyEvent. KEY_UP; if(keyCode > 0 && keyCode < 127) pressedKeys[keyCode] = false;

keyEventsBuffer.add(keyEvent);

return false;

Next up is our implementation of the OnKeyListener.onKey() interface method, which gets called each time the View receives a new key event. We start by ignoring any (Android) key events that encode a KeyEvent.ACTION_MULTIPLE event. These are not relevant in our context. We follow that up with a tasty synchronized block. Remember that the events are received on the UI thread and read on the main loop thread, so we have to make sure none of our members are accessed in parallel.

Within the synchronized block we first fetch a KeyEvent instance (of our KeyEvent implementation) from the Pool. This will either get us a recycled instance or a brand-new one, depending on the state of the Pool. Next we set the KeyEvent's keyCode and keyChar members based on the contents of the Android KeyEvent that got passed to the method. We then decode the type of the Android KeyEvent and set the type of our KeyEvent as well as the element in the pressedKey array accordingly. Finally we add our KeyEvent to the keyEventBuffer list we defined earlier.

public boolean isKeyPressed(int keyCode) { if (keyCode < 0 || keyCode > 127)

return false; return pressedKeys[keyCode];

Next we have the isKeyPressed() method, which basically implements the semantics of Input.isKeyPressed(). We pass in an integer specifying the key code (one of the Android KeyEvent.KEYCODE_XXX constants) and return whether that key is pressed or not. We do so by looking up the state of the key in the pressedKey array after some range checking. Remember that we set the elements of this array in the previous method, which gets called on the UI thread. As we are again working with primitive types, there's no need for synchronization.

public List<KeyEvent> getKeyEvents() { synchronized (this) {

int len = keyEvents.size(); for (int i = 0; i < len; i++)

keyEventPool.free(keyEvents.get(i)); keyEvents.clear(); keyEvents.addAll(keyEventsBuffer); keyEventsBuffer.clear(); return keyEvents;

The last method of our handler is called getKeyEvents(), and implements the semantics of the Input.getKeyEvents() method. We start off with a juicy synchronized block again, remembering that this method will be called from a different thread.

Next we do something very mysterious. We loop through the keyEvents array and insert all the KeyEvents stored in it into our Pool. Remember that we fetch instances from the Pool in the onKey() method on the UI thread. Here we reinsert them into the Pool. But isn't the keyEvents list empty? Yes, but only the first time we invoke that method. To understand why that is, you have to grasp the rest of the method first.

After our mysterious Pool insertion loop, we clear the keyEvents list and fill it with the events in our keyEventsBuffer list. Finally we clear the keyEventsBuffer list and return the newly filled keyEvents list to the caller. What is happening here?

Let me illustrate it by giving you a simple example. We'll examine what happens to the keyEvents and keyEventsBuffer lists, as well as our Pool each time a new event arrives on the UI thread or the game is fetching the events in the main thread:

keyEvents = { }, keyEventsBuffer = {KeyEvent1}, pool = { } Main thread: getKeyEvents() ->

keyEvents = {KeyEvent1}, keyEventsBuffer = { }, pool { } UI thread: onKey() ->

keyEvents = {KeyEvent1}, keyEventsBuffer = {KeyEvent2}, pool { } Main thread: getKeyEvents() ->

keyEvents = {KeyEvent2}, keyEventsBuffer = { }, pool = {KeyEvent1} UI thread: onKey() ->

keyEvents = {KeyEvent2}, keyEventsBuffer = {KeyEventl}, pool = { }

1. First we get a new event in the UI thread. There's nothing in the Pool yet, so a new KeyEvent instance (KeyEventl) is created and inserted into the keyEventsBuffer list.

2. Next we call getKeyEvents() on the main thread. It takes KeyEventl from the keyEventsBuffer list and puts it into the keyEvents list it returns to the caller.

3. We get another event on the UI thread. We still have nothing in the Pool, so a new KeyEvent instance (KeyEvent2) is created and inserted into the keyEventsBuffer list.

4. The main thread calls getKeyEvents() again. Now something interesting happens. Upon entry into the method, the keyEvents list still holds KeyEventl. The mysterious insertion loop will place that event into our Pool. It then clears the keyEvents list and inserts any KeyEvent into the keyEventsBuffer, in this case KeyEvent2. We just recycled a key event.

5. Finally another key event arrives on the UI thread. This time we have a free KeyEvent in our Pool, which we'll happily reuse. Look mom, no garbage collection!

This mechanism comes with one caveat, though: we have to call

KeyboardHandler.getKeyEvents() frequently or else the keyEvents list will fill up quickly, and no objects will be returned to the Pool. As long as we remember this, all is well.

0 0

Post a comment