Beiträge mit dem Tag ‘onPause’

Threads in Android Teil 2 (Aktualisierung der UI)

Werden Threads in Android überhaupt benötigt? Ja, besonders bei Operationen mit langer Laufzeit wie z.B. beim Zugriff auf das Netzwerk. Solche Operationen können den Haupt-Thread, durch den die Benutzeroberfläche (UI) ausgeführt wird, blockieren. Als Folge dessen ist die UI nicht in der Lage Benutzereingaben entgegenzunehmen.
Das folgende Beispiel soll das Problem veranschaulichen:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    textView = (TextView) findViewById(R.id.textView);
    textView.setText("hello World");

    // simulate long running operation
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        Log.e(TAG, "Thread.sleep", e);
    }
}

Gekapselte Prozesse brauchen in Threads länger, besonders dann wenn der Prozess in den Lebenszyklus der Aktivität gestartet wird (onCreate, onResume, onPause).
Wenn eine Anwendung nicht reagiert, liefert das Android-System normalerweise die Meldung "Anwendung reagiert nicht. Sofort schließen / Warten" zurück (ANR Dialog – Application Not Responding).

Um dies zu umgehen kann ein Thread erstellt werden der am Ende eine Meldung ausgibt:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    textView = (TextView) findViewById(R.id.textView);
    textView.setText("hello World");

    thread = new Thread() {
        public void run() {
            try {
                // simulate long running operation
                Thread.sleep(5000);
                textView.setText("new value...");
            } catch (InterruptedException e) {
                Log.e(TAG, "run in thread", e);
            }
        }
    };
    thread.start();
}

War es das schon? Nein, leider nicht. Nachfolgend können Sie sehen wie sich die Anwendung mit folgender Meldung verabschiedet:
CalledFromWrongThreadException Only the original thread that created a view hierarchy can touch its views.

CalledFromWrongThreadException

Genau wie in Java oder C# ein UI-Objekt (in unserem Fall ein textView) kann dieses nur durch den Thread verändert werden, durch den es auch erzeugt wurde. In diesem Beispiel ist dies der Main-UI-Thread (nicht der neue Thread).

Schauen Sie sich dazu auch den Artikel Painless Threading auf der Android-Entwickler-Seite an. Ein weiteres Tutorial dort zeigt wie mittels des Handler die UI eines Threads aktualisiert werden kann.

Eine Handler-Instanz wird an den Main-UI-Thread gebunden und ist so in der Lage das UI-Objekt zu aktualisieren, wenn Benachrichtigungen von anderen Threads empfangen werden.

Als nächstes wird eine Unterklasse der Handler-Klasse erzeugt und die handleMessage Methode überschrieben. msg.obj enthält die Nachricht aus dem anderen Thread.

private Handler uiHandler = new UIHandler();

class UIHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        // a message is received; update UI text view
        textView.setText(msg.obj.toString());
        super.handleMessage(msg);
    }
}

Erstellen Sie in dem länger laufenden Thread das Message-Objekt und übergeben Sie es an den Handler. Verwenden Sie Message.obtain() um eine Nachricht aus dem Pool wieder verwendbarer Objekte zu übernehmen.

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    textView = (TextView) findViewById(R.id.textView);
    textView.setText("hello World");

    thread = new Thread() {
        public void run() {
            try {
                // simulate long running operation
                Thread.sleep(5000);

                // create message which will be send to handler
                Message msg = Message.obtain(uiHandler);
                msg.obj = "new value...";
                uiHandler.sendMessage(msg);

            } catch (InterruptedException e) {
                Log.e(TAG, "run in thread", e);
            }
        }
    };
    thread.start();
}

Das uiHandler-Objekt erhält die Nachricht und gibt den Inhalt im UI-Objekt aus.

Falls Sie unterschiedliche Threads haben und kontrollieren möchten woher die Nachricht kommt, können Sie msg.what verwenden um die Nachricht zu identifizieren:

class UIHandler extends Handler {
    private static final int ID_0 = 0;
    private static final int ID_1 = 1;

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case ID_0:
            // a message is received; update UI text view
            if (msg.obj != null)
                textView.setText(msg.obj.toString());
            break;

        default:
            break;
        }
        super.handleMessage(msg);
    }
}

Ändern Sie den Thread um eine Nachricht zu senden (benutzerdefinierter Code):

// create message which will be send to handler
Message msg = Message.obtain(uiHandler, UIHandler.ID_0);
msg.obj = "new value...";
uiHandler.sendMessage(msg);

Threads in Android Teil 1 (Endlosschleife)

Diese Serie zeigt Tipps und Tricks über die Verwendung von Threads in der Android-Entwicklung. Teil 1 beschreibt, wie man einen Thread mit einer Endlosschleife erzeugt. Da der Thread nur gebraucht wird wenn die Anwendung läuft, muss zunächst sichergestellt werden, dass der Thread stoppt, sobald die Anwendung nicht mehr aktiv ist.

In diesem Beispiel haben wir einen Thread erstellen, der alle n Sekunden ausführen wird.
Schauen Sie sich den nachfolgenden Quelltext an. Die onCreate Methode der Activity wird überschrieben und in einem separaten Thread wird die Aufgabe abgearbeitet. Dieser Thread wird nach erledigter arbeit „Schlafen“ geschickt.

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    thread = new Thread() {
        public void run() {
            while (true) {
                try {
                    // do something here
                    Log.d(TAG, "local Thread sleeping");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Log.e(TAG, "local Thread error", e);
                }
            }
        }
    };
    thread.start();
}

Was passiert, wenn die Anwendung pausiert, z.B. wenn eine andere Anwendung in den Vordergrund gebracht wird?
Der nachfolgende ScreenShot vom Debugger zeigt, dass der Thread weiterhin ausgeführt wird wenn die Anwendung nicht aktiv ist. Debugger: Der Thread läuft noch

Es ist also erforderlich den Thread zu kontrollieren und ihn in der onPause-Methode zu stoppen.

Die Lösung dazu stammt aus dem Google Artikel "Updating the UI from a Timer" (http://developer.android.com/resources/articles/timed-ui-updates.html). Wir instanziieren einen Handler der es uns erlaubt ablauffähige-Objekte (Runnable Objects) verarbeiten können.

Der Handler ist an den Main-UI-Thread der Activity gebunden.
Der neue Thread wird durch den Aufruf handler.postDelayed(this, 1000); zur "Message-Queue" des Main-UI-Thread hinzugefügt und nach 1 Sekunde ausgeführt.

private Thread thread;
private Handler handler = new Handler();

@Override
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);
	
	thread = new Thread() {
		public void run() {
			// do something here
			Log.d(TAG, "local Thread sleeping");
			handler.postDelayed(this, 1000);
		}
	};
}

Der Rest ist einfach. Wir beginnen den Thread in onResume, welches nach onCreate aufgerufen wird, sobald die Activity durch den Aufruf von handler.postDelayed(thread, 0); in den Vordergrund kommt (Parameter 0 zeigt, dass es keine Verzögerung gibt).

Bevor der Thread zum Handler hinzugefügt wird, muss er entfernt werden um sicherzustellen, dass er nicht schon an den Handler gebunden ist. Das kann mit onPause erledigt werden:

@Override
protected void onResume() {
	super.onResume();
	
	handler.removeCallbacks(thread);
	handler.postDelayed(thread, 0);
	Log.d(TAG, "onResume");
}

@Override
protected void onPause() {
	super.onPause();

	handler.removeCallbacks(thread);
	Log.d(TAG, "onPause");
}

Wenn man sich die Debugger anschaut, merkt man, dass der Thread gestoppt wird wenn die Activity pausiert. Threads in Android - onResume  onPause