trotz invalidate() wird onDraw() nicht aufgerufen

phobos

Mitglied
Ich versteh absolut nicht warum invalidate() auf einmal meine onDraw() Methode in meiner Panel Klasse nicht mehr aufruft. Es hatte schon mal funktioniert aber jetzt wird onDraw nur noch einmal und dann nie wieder aufgerufen. obwohl invalidate() ständig aufgerufen wird.

Java:
package org.phobos.kompass;

import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.Window;
import android.widget.TextView;

public class KompassActivity extends Activity implements SensorEventListener {

	private SensorManager mSensorManager;
	private Sensor mOrientation;
	private Panel mPanel;
	TextView textFenster;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.main);
		AttributeSet attrs = null;
		mPanel = new Panel(this, attrs);
		mPanel.setRoll_angle(0);
		mPanel.setAzimuth_angle(0);
		textFenster = (TextView) findViewById(R.id.textView1);
		mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
		mOrientation = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

	}

	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// Do something here if sensor accuracy changes.
		// You must implement this callback in your code.
	}

	@Override
	protected void onResume() {
		super.onResume();
		mSensorManager.registerListener(this, mOrientation,
				SensorManager.SENSOR_DELAY_NORMAL);
	}

	@Override
	protected void onPause() {
		super.onPause();
		mSensorManager.unregisterListener(this);
	}

	public void onSensorChanged(SensorEvent event) {

		float azimuth_angle = event.values[0];
		float pitch_angle = event.values[1];
		float roll_angle = event.values[2];

		//  Text ausgeben
		// "Azimuth:" + String.format("%.2f", azimuth_angle) + "°", 0, 50,
		// "x-Neigung:" + String.format("%.2f", pitch_angle) + "°", 0,
		// 100, mPaint);
		// "y-Neigung:" + String.format("%.2f", roll_angle) + "°",
		// 0, 150, mPaint);
		textFenster.setText("Azimuth: " + String.format("%.2f", azimuth_angle)
				+ "°");
		
		mPanel.setRoll_angle(roll_angle);
		mPanel.setPitch_angle(pitch_angle);
		mPanel.setAzimuth_angle(azimuth_angle);
		mPanel.invalidate();
	}

}


Java:
package org.phobos.kompass;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;

// Diese Klasse ist für das Zeichnen auf dem Display zuständig

public class Panel extends View {

	// Variablen deklarieren
	private int xpos; // Position der
	private int ypos; // Kugel
	private float pitch_angle; // Sensordaten
	private float roll_angle;
	private float azimuth_angle;

	// Konstruktor
	public Panel(Context context, AttributeSet attrs) {
		super(context, attrs);

	}

	// In der onDraw Methode wird auf den Bildschirm gezeichnet
	@Override
	public void onDraw(Canvas canvas) {

		// // kleine Pause zum Akku sparen
		// try {
		// Thread.sleep(33);
		// } catch (InterruptedException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// }

		// Hintergrund zeichnen
		Bitmap background = BitmapFactory.decodeResource(getResources(),
				R.drawable.background);
		Rect dst = new Rect();
		dst.set(0, 0, canvas.getWidth(), canvas.getHeight());
		canvas.drawBitmap(background, null, dst, null);

		// Kompass drehen und zeichnen
		Bitmap komp = BitmapFactory.decodeResource(getResources(),
				R.drawable.kom_blatt);
		Matrix mat = new Matrix();
		mat.postRotate(360 - azimuth_angle);
		Bitmap komp_gedr = Bitmap.createBitmap(komp, 0, 0, komp.getWidth(),
				komp.getHeight(), mat, true);
		int xversatz = (komp_gedr.getWidth() - komp.getWidth()) / 2;
		int yversatz = (komp_gedr.getHeight() - komp.getHeight()) / 2;
		canvas.drawBitmap(komp_gedr, canvas.getWidth() / 2 - komp.getWidth()
				/ 2 - xversatz, canvas.getHeight() / 2 - komp.getHeight() / 2
				- yversatz, null);

		// Kugelposition berechnen und zeichnen
		Bitmap kugel = BitmapFactory.decodeResource(getResources(),
				R.drawable.fadenkreuz);
		xpos = (canvas.getWidth() / 2)
				+ Math.round((roll_angle / 180) * canvas.getWidth())
				- kugel.getWidth() / 2;
		ypos = (canvas.getHeight() / 2)
				+ Math.round((pitch_angle / 180) * canvas.getHeight())
				- kugel.getHeight() / 2;
		canvas.drawBitmap(kugel, xpos, ypos, null);

	}

	// Getter und Setter

	public float getPitch_angle() {
		return pitch_angle;
	}

	public void setPitch_angle(float pitch_angle) {
		this.pitch_angle = pitch_angle;
	}

	public float getRoll_angle() {
		return roll_angle;
	}

	public void setRoll_angle(float roll_angle) {
		this.roll_angle = roll_angle;
	}

	public int getXpos() {
		return xpos;
	}

	public void setXpos(int xpos) {
		this.xpos = xpos;
	}

	public int getYpos() {
		return ypos;
	}

	public void setYpos(int ypos) {
		this.ypos = ypos;
	}

	public float getAzimuth_angle() {
		return azimuth_angle;
	}

	public void setAzimuth_angle(float azimuth_angle) {
		this.azimuth_angle = azimuth_angle;
	}

}

[XML]<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android😱rientation="vertical">

<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Testtext" />

<org.phobos.kompass.Panel
android:id="@+id/panel1"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />


</LinearLayout>[/XML]
 
Der Code sieht so aus als müsste er funktionieren. Wie's so ist mit Code tut er das aber meistens nicht oder nicht so wie wir das wollen.


Also bleibt dir wohl nichts anderes übrig als dich mal reinzudebuggen wenn die View am Bildschirm sichtbar ist aber sich nicht mehr verändert.
1. Wird onSensorChange aufgerufen? (Das ist ja leicht anhand der Text-Ausgabe nachzuvollziehen.)
2. Wird onDraw aufgerufen? Log.d-Ausgabe einbauen und checken. Wenn ja: Ändern sich die Parameter? Wenn nein: Funktioniert es mit einer noch einfacheren View?

Ein paar Anmerkungen hab ich so noch:
- Im onDraw schlafen gehen ist eine sehr schlechte Idee. Siehe auch meine Antwort hier.
- In Java herrscht der CamelCase also sollten Methodennamen nicht eine Mischform aus der üblichen _-Benennung aus C++ und dem CamelCase von Java sein. getPitchAngle statt getPitch_angle etc. sollte also verwendet werden. (Tut aber hier nichts zur Sache.)
 
Erst mal vielen Dank für deine Tips. Leider bin ich trotzdem noch nicht weiter gekommen.
Bei Programmstart wird onDraw() einmal aufgerufen. Danach nur noch onSensorChanged() und invalidate() abwechselnd. Hab herausgefunden dass während invalidate() eine Methode namens skipInvalidate() aufgerufen wird. Leider weiss ich nicht warum. Gibts ne Möglichkeit sich den Code von invalidate() irgendwo anzusehen? Ich werd jetzt nochmal ne einfache View schreiben und testen obs damit funktioniert. Aber falls ja, wie bringt mich das dann weiter?
 
skipInvalidate, entgegengesetzt ihres Namens, prüft nur ob überhaupt gezeichnet werden soll. Die Methode wird also immer aufgerufen, siehe auch hier (Zeile 10122)

Allerdings habe ich auf Stackoverflow einen Post mit einem ähnlichen Problem gefunden. Vielleicht probierst du das einmal:
1. HW-Acceleration deaktivieren im Manifest und probieren ob es das ist.
2. Eine zweite View auch per invalidate anstoßen (z.B. die TextView)
 
Habs mit dieser View getestet, wird auch nicht aufgerufen. Wird wieder invalidateSkip() aufgerufen.
Java:
package org.phobos.kompass;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;

public class TestPanel extends View {

	public TestPanel(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}
	
	public void onDraw(Canvas canvas) {
		canvas.drawColor(Color.BLUE);
		
	}

}

Edit: Hab deinen neuen Beitrag noch nicht gelesen. Werd das mal ausprobieren. Danke
 
Wenn ich HW-Beschleunigung ausschalten will bekomm ich den Fehler:
Code:
error: No resource identifier found for attribute 'hardwareAccelerated' in package 'android'	AndroidManifest.xml	/kompass	line 9	Android AAPT Problem
Liegt wohl an
[XML] <uses-sdk android:minSdkVersion="8" />[/XML]
dh aber doch das dies nicht das Problem sein sollte oder? Hab am Telefon nochmal nachgesehen da is GPU-Rendering erzwingen auch auf aus.

Edit: Wenn ich
Code:
textFenster.invalidate();
einfüge wird onDraw() gar nicht aufgerufen. Nichtmal bei Programmstart. Ich verstehs einfach nicht 🙁
edit2: Ka was grade los war, jetzt sind wir jedenfalls wieder beim alten Stand. Die TextView wird aktualisiert, die andere nur einmal bei Programmstart, trotz des zweiten invalidate()
 
Zuletzt bearbeitet:
Kann es sein dass ich irgendwie 2 verschiedene Instanzen der Panel Klasse erzeuge und einfach auf die falsche zugegriffen wird? Ist denn das im main.xml angegebene Panel welches in
Java:
setContentView(R.layout.main);
gesetzt wird und das durch
Java:
mPanel = new Panel(this, attrs);
erzeugte das selbe Objekt? Irgendwie versteh ich die zusammenhänge da nicht ganz. Was passiert denn genau während setContentView()?
Wenn ich allerdings sowas wie
Java:
mPanel= (Panel) findViewById(R.id.panel1);
versuche ändert sich auch nichts. Wie komm ich denn an die durch setContentView erzeugte Instanz? Oder versteh ich da was komplett falsch?
 
Mit findViewById. Wenn du new verwendest erzeugst du eine neue Referenz.

Das habe ich ganz überlesen. Du musst auf jeden Fall findViewById verwenden, ansonsten ist die View nicht sichtbar und das Zeichnen wird wirklich immer übersprungen.
 
Daaaanke :toll:

Endlich funktionierts wieder.
Nochmal kurz zu dem sleep, soll ich das dann unter onSensorChanged() machen oder soll ichs einfach ganz weg lassen? Richtig wärs wohl eine eigene Klasse/Thread zu machen die die Sensor Events entgegennimmt und da drin zu pausieren, oder? Aber von Threads hab ich noch gar keine Ahnung.
 
@Sleep

Überlege dir kurz einmal die Architektur: Das sleep benötigst du für polling-Umgebungen also wo du aktiv nachfragen musst ob der Sensor etwas neues für dich hat. Dort macht es Sinn, dass du nicht ständig an den Systemressourcen saugst obwohl ein Update im Abstand von 5ms sinnlos ist. Dort würdest du das ganze in einen Thread auslagern und den schlafen lassen.

In deinem Fall passiert aber etwas anderes: Das lauern auf Updates erledigt das System. Du bekommst nur Benachrichtigungen wenn sich etwas geändert hat. Du reagierst sofort darauf und zeigst es an. Warum hier etwas verzögern?

Lass das ganze Schlafen weg.
 
ok, danke. Dh wenn ich Akku sparen will sollte ich das System dazu bewegen weniger oft zu prüfen ob sich die Sensoren geändert haben. Ich hab rausgefunden das ich dort:
Java:
		mSensorManager.registerListener(this, mOrientation,
				SensorManager.SENSOR_DELAY_NORMAL);
beim letzten Parameter auch eine Verzögerung in ms angeben kann, damit onSensorChanged() nicht dauernd aufgerufen wird.
 
Darüber würde ich mir bei der App keine Sorgen machen. Wenn du zeitnah reagieren willst, belass es so. Wenn dir der Akku Sorgen macht, bau ein, dass die Activity per finish() beendet wird wenn der Benutzer die back-Taste drückt.

Denn wenn man die Activity gerade anzeigt wird es ja interessant sein, dass sich das verändert. Nur wenn's im Hintergrund ist, ist's ein "Problem". (Auch nicht wirklich)
 
Wo füge ich das finish() ein wenn ich möchte das die Activity beendet wird sobald sie nicht mehr im Vordergrund ist also sowohl bei drücken der Home Taste als auch der Back Taste? Bei onPause()?
 
Eher bei onStop.

Ich würde aber eher gleich in onStop den Sensor wieder verwerfen, dann kannst dir das mit dem finish ganz sparen 😉
 
Ich hab jetzt nochmal ein wenig über den Activity Lifecycle nachgelesen. Ist es nicht so dass eigentlich vor onStop immer onPause aufgerufen wird. Dh der Sensor Listener sowieso wieder freigegeben wird. Oder fehlt da noch was? Muss ich dem SensorManager auch noch sagen dass ich ihn nicht mehr brauche?
Wenn ja, wie mache ich das?
 

Zurück
Oben