Reputation: 167
This problem seemingly appeared from nowhere, and it's been turning me insane for the past two and half hours. I've been all around the internet, looking for a solution, but my quest was in vain.
I've therefore decided to ask for help.
My Android game compiles fine, and it used to run fine, but now it doesn't. It occurred at about the time when I started adding in a few more images for use in the game.
The logged message:
01-28 20:42:34.510: WARN/dalvikvm(3857): threadid=10: thread exiting with uncaught exception (group=0x40015560)
01-28 20:42:34.510: ERROR/AndroidRuntime(3857): FATAL EXCEPTION: Thread-11
01-28 20:42:34.510: ERROR/AndroidRuntime(3857): java.lang.NullPointerException
01-28 20:42:34.510: ERROR/AndroidRuntime(3857): at com.ballbounce.bounce.MainGamePanel.update(MainGamePanel.java:153)
01-28 20:42:34.510: ERROR/AndroidRuntime(3857): at com.ballbounce.bounce.MainThread.run(MainThread.java:91)
01-28 20:42:34.510: WARN/ActivityManager(105): Force finishing activity com.ballbounce.bounce/.BallBounceActivity
I think the NullPointerException happens as a result of the thread exiting, not the actual cause of the thread closing, though.
I've tried 'cleaning' the project in Eclipse, restarting Eclipse, restarting the phone... all of the usual stuff. My 'undo' function doesn't let me go back far enough to get the game to a stable state.
Relevant code (I've tried to trim as much of it down as possible):
BallBounceActivity.java
public class BallBounceActivity extends Activity {
// Trimmed down for ease-of-viewing
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0);
mainGamePanel = new MainGamePanel(this);
setContentView(mainGamePanel);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "BallBounceActivity");
}
protected void onPause() {
// Trimmed down for ease-of-viewing
}
protected void onResume() {
// Trimmed down for ease-of-viewing
}
protected void onStop() {
// Trimmed down for ease-of-viewing
}
private SensorEventListener accelerationListener = new SensorEventListener() {
public void onAccuracyChanged(Sensor sensor, int acc) {
}
@Override
public void onSensorChanged(SensorEvent event) {
// Trimmed down for ease-of-viewing
}
};
}
MainGamePanel.java
public class MainGamePanel extends SurfaceView implements SurfaceHolder.Callback {
// Trimmed down for ease-of-viewing
private MainThread thread;
// The Fps to be displayed
private String avgFps;
public void setAvgFps(String avgFps) {
this.avgFps = avgFps;
}
public MainGamePanel(Context context) {
super(context);
getHolder().addCallback(this);
setFocusable(true);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new MainThread(getHolder(), this);
thread.setRunning(true);
thread.start();
random = new Random();
bmpFactoryOptions = new BitmapFactory.Options();
bmpFactoryOptions.inScaled = false;
powerups = new Bitmap[12];
// Copy and paste time!
powerups[0] = BitmapFactory.decodeResource(getResources(), R.drawable.coin, bmpFactoryOptions);
powerups[1] = BitmapFactory.decodeResource(getResources(), R.drawable.powerup_8, bmpFactoryOptions);
powerups[2] = BitmapFactory.decodeResource(getResources(), R.drawable.powerup_9, bmpFactoryOptions);
// Trimmed down for ease-of-viewing
ballImages = new Bitmap[8];
// Copy and paste time!
ballImages[0] = BitmapFactory.decodeResource(getResources(), R.drawable.ball_0, bmpFactoryOptions);
ballImages[1] = BitmapFactory.decodeResource(getResources(), R.drawable.ball_1, bmpFactoryOptions);
ballImages[2] = BitmapFactory.decodeResource(getResources(), R.drawable.ball_2, bmpFactoryOptions);
// Trimmed down for ease-of-viewing
items = new ArrayList<Item>();
theBall = new Ball(this.getWidth(), this.getHeight(), 24.0f, items, ballImages);
platform = new Platform(this.getWidth(), this.getHeight(), 180.0f);
player = new Player(theBall, platform);
theBall.setPlayer(player);
theBall.setPlatform(platform);
x = y = 0.0f;
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// Trimmed down for ease-of-viewing
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Trimmed down for ease-of-viewing
}
public void render(Canvas canvas) {
// Trimmed down for ease-of-viewing
}
public void update() {
// Trimmed down for ease-of-viewing
theBall.update(x, y);
platform.update();
}
public void updateAccelerometerValues(float x, float y, float z) {
// Trimmed down for ease-of-viewing
}
public void displayScore(Canvas canvas) {
// Trimmed down for ease-of-viewing
}
public void displayFps(Canvas canvas, String fps) {
// Trimmed down for ease-of-viewing
}
}
MainThread.java
public class MainThread extends Thread {
// Trimmed down for ease-of-viewing
public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
@Override
public void run() {
Canvas canvas;
// Trimmed down for ease-of-viewing
sleepTime = 0;
while (running) {
canvas = null;
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
beginTime = System.currentTimeMillis();
framesSkipped = 0;
this.gamePanel.update();
this.gamePanel.render(canvas);
timeDiff = System.currentTimeMillis() - beginTime;
sleepTime = (int)(FRAME_PERIOD - timeDiff);
if (sleepTime > 0) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {}
}
while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
this.gamePanel.update();
sleepTime += FRAME_PERIOD;
framesSkipped++;
}
// Trimmed down for ease-of-viewing
}
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
Log.d(TAG, "Thread exited");
}
}
R.java
public final class R {
public static final class attr {
}
public static final class drawable {
public static final int ball_0=0x7f020000;
public static final int ball_1=0x7f020001;
public static final int ball_2=0x7f020002;
public static final int ball_3=0x7f020003;
public static final int ball_4=0x7f020004;
public static final int ball_5=0x7f020005;
public static final int ball_6=0x7f020006;
public static final int ball_7=0x7f020007;
public static final int coin=0x7f020008;
public static final int ic_launcher=0x7f020009;
public static final int powerup_8=0x7f02000a;
public static final int powerup_9=0x7f02000b;
public static final int powerup_9_save=0x7f02000c;
}
public static final class layout {
public static final int main=0x7f030000;
}
public static final class string {
public static final int app_name=0x7f040001;
public static final int hello=0x7f040000;
}
}
Any ideas as to how to fix this problem, or even any suggestions as for how this happened, would be greatly appreciated.
Many thanks in advance, Will.
Upvotes: 0
Views: 6547
Reputation: 167
I came up with a solution:
The gamePanel.update()
function was called by Thread-11 before the entire surfaceCreated(SurfaceHolder holder)
function had completed. This meant that Thread-11 was trying to use objects before they were initialised, which of course resulted in the NullPointerException.
My fix was to add:
while(gamePanel.player == null) {
try {
Thread.sleep(10);
}
catch (InterruptedException e) {}
}
Just before the while (running) ...
bit in MainThread's run()
function. (The choice of 'player' was because 'player' is the last object to be initialised).
Quite an interesting problem, and an interesting solution.
I hope this solution solves a problem for some people in the future!
Upvotes: 1