Android Handler läuft zu schnell

Endymion

Bekanntes Mitglied
Hallo, ich habe folgendes Problem: Ich habe ein kleines Android-Spiel, das in runden unterteilt ist. Jede runde dauert 60 Sekunden. In der ersten Runde dauert die Runde auch wirklich 60 Sekunden. Die zweite Runde läuft aber doppelt so schnell, obwohl sie auch nur 60 Sekunden dauern sollte. Das Zeitintervall ist in einer final-Variable gespeichert, kann also nicht verändert werden. Der Code sieht folgendermaßen aus:
Java:
import java.util.Date;
import java.util.Random;

import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.os.Handler;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

public class GameActivity extends Activity implements Runnable, OnClickListener {
	private static final long INTERVAL = 100L;
	private static final int MAX_TIME = 600;
	private static final long MOSQUITO_EXISTING_TIME = 2000;
	private static final String ELEPHANT = "elephant";
	private Random random = new Random();
	private Handler handler;
	private FrameLayout gameSpace;
	private boolean running;
	private int gameSpaceWidth;
	private int gameSpaceHeight;
	private int round;
	private int score;
	private int mosquitosNeeded;
	private int mosquitosCatched;
	private int mosquitosLeft;
	private int timeLeft;
	private float scale;
	private TextView tvScore;
	private TextView tvRound;
	private TextView tvHits;
	private TextView tvTime;
	private FrameLayout flHits;
	private LayoutParams lpHits;
	private FrameLayout flTime;
	private LayoutParams lpTime;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.game);
		handler = new Handler();
		gameSpace = (FrameLayout) findViewById(R.id.gameSpace);
		scale = getResources().getDisplayMetrics().density;
		tvScore = (TextView) findViewById(R.id.score);
		tvRound = (TextView) findViewById(R.id.round);
		tvHits = (TextView) findViewById(R.id.hits);
		tvTime = (TextView) findViewById(R.id.time);
		flHits = (FrameLayout) findViewById(R.id.bar_hits);
		lpHits = flHits.getLayoutParams();
		flTime = (FrameLayout) findViewById(R.id.bar_time);
		lpTime = flTime.getLayoutParams();
		startGame();
	}

	private void startGame() {
		round = 0;
		score = 0;
		running = true;
		startRound();
	}

	private void startRound() {
		round += 1;
		mosquitosNeeded = 20 + round * 2;
		mosquitosLeft = (int) Math.round(mosquitosNeeded * 1.5);
		mosquitosCatched = 0;
		mosquitosCatched = 0;
		timeLeft = MAX_TIME;
		int id = getResources().getIdentifier(
				"hintergrund" + Integer.toString(round), "drawable",
				this.getPackageName());
		if (id > 0) {
			LinearLayout l = (LinearLayout) findViewById(R.id.background);
			l.setBackgroundResource(id);
		}
		updateScreen();
		handler.postDelayed(this, INTERVAL);
	}

	private boolean testRoundEnd() {
		if (timeLeft == 0) {
			startRound();
			return true;
		}
		return false;
	}

	private boolean testGameEnd() {
		if (timeLeft == 0 && mosquitosCatched < mosquitosNeeded) {
			gameOver();
			return true;
		}
		return false;
	}

	private void gameOver() {
		Dialog dialog = new Dialog(this,
				android.R.style.Theme_Translucent_NoTitleBar_Fullscreen);
		dialog.setContentView(R.layout.gameover);
		dialog.show();
	}

	@Override
	public void run() {
		if (running) {
			mosquitosCatched++;
			try {
				handler.postDelayed(this, INTERVAL);
			} catch (Throwable t) {

			}
			timeLeft--;
			float randomNumber = random.nextFloat();
			double probability = mosquitosLeft * 1.5 / timeLeft;
			while (probability > 1) {
				showMosquito();
			}
			if (randomNumber < probability) {
				showMosquito();
			}
			deleteMosquito();
			updateScreen();
			if (!testGameEnd()) {
				testRoundEnd();
			}
		}
	}

	private void updateScreen() {
		tvScore.setText(Integer.toString(score));
		tvRound.setText(Integer.toString(round));
		tvHits.setText(Integer.toString(mosquitosCatched));
		tvTime.setText(Integer.toString(timeLeft));
		lpHits.width = Math
				.round(scale * 300
						* Math.min(mosquitosCatched, mosquitosNeeded)
						/ mosquitosNeeded);
		lpTime.width = Math.round(scale * timeLeft * 300 / MAX_TIME);
	}

	private void showMosquito() {
		gameSpaceWidth = gameSpace.getWidth();
		gameSpaceHeight = gameSpace.getHeight();
		ImageView mosquito = new ImageView(this);
		if (random.nextFloat() < 0.05) {
			mosquito.setImageResource(R.drawable.elephant);
			mosquito.setTag(R.id.animal, "elephant");
		} else {
			mosquito.setImageResource(R.drawable.mosquito);
			mosquito.setTag(R.id.animal, "mosquito");
			mosquitosLeft--;
		}
		mosquito.setTag(R.id.geburtsdatum, new Date());
		mosquito.setOnClickListener(this);
		int mosquitoWidth = (int) Math.round(scale * 50);
		int mosquitoHeight = (int) Math.round(scale * 42);
		int links = random.nextInt(gameSpaceWidth - mosquitoWidth);
		int oben = random.nextInt(gameSpaceHeight - mosquitoHeight);
		FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
				mosquitoWidth, mosquitoHeight);
		params.leftMargin = links;
		params.topMargin = oben;
		params.gravity = Gravity.TOP + Gravity.LEFT;
		gameSpace.addView(mosquito, params);
	}

	private void deleteMosquito() {
		gameSpace = (FrameLayout) findViewById(R.id.gameSpace);
		int i = 0;
		while (i < gameSpace.getChildCount()) {
			ImageView muecke = (ImageView) gameSpace.getChildAt(i);
			Date geburtsdatum = (Date) muecke.getTag(R.id.geburtsdatum);
			long alter = (new Date()).getTime() - geburtsdatum.getTime();
			if (alter > MOSQUITO_EXISTING_TIME) {
				gameSpace.removeView(muecke);
			} else {
				i++;
			}
		}
	}

	@Override
	public void onClick(View mosquito) {
		if (mosquito.getTag(R.id.animal) == ELEPHANT) {
			score -= 1000;
		} else {
			mosquitosCatched++;
			score += 100;
		}
		updateScreen();
		gameSpace.removeView(mosquito);
	}
}
Warum wird die run()-Methode nun also in der zweiten runde doppelt so schnell aufgerufen?
 

ice-breaker

Top Contributor
weil du nicht einfach sagen kannst, dass ein Durchlauf in run() genau 1 sekunde benötigt, das ist von der CPU abhängig und und und.
Es gibt hier im Forum ein Tutorial von Quaxli zu Spieleprogrammierung, das solltest du mal lesen, dein Ansatz funktioniert vorne und hinten nicht.
 

schlingel

Gesperrter Benutzer
Wie ice-breaker schon sagte, solche Zeiten werden vom Framework nicht garantiert.

Bau einfach in deiner Handler-Methode noch Code ein der misst wie lange es her ist, das er das letzte Mal aufgerufen wurde. Z.B. System.currentTimeMillis() bietet sich dafür an.
 

Endymion

Bekanntes Mitglied
Das Tutorial habe ich bereits durchgelesen. Der Timer für den nächsten Durchlauf wird doch schon am Anfang der Methode gesetzt, also müsste die eine Sekunde doch passen. Ansonsten wäre das trotzdem keine Erklärung dafür, dass ich Runde 1 noch alles glatt läuft, und der Handler in Runde 2 doppelt so schnell tickt.
 

schlingel

Gesperrter Benutzer
Niemand verspricht dir, dass der Timer tatsächlich immer eine Sekunde braucht. Das sind nur "ungefähr"-Werte. Du wirst also nicht um's selber messen herum kommen.
 

Neue Themen


Oben