0 komentarzy

Przechowywanie stanu Aktywności podczas zmiany orientacji ekranu

Lipiec 25, 2011 Activity Tutoriale

Problem ze znikającymi danymi podczas rotacji ekranu jest chyba najbardziej powszechnym problemem dla wszystkich początkujących programistów Androida. O tym skąd się on bierze i jak się przed nim zabezpieczyć przeczytacie w tym wpisie.

Dlaczego?

Problem jest oczywiście bardziej ogólny. Zacznijmy jednak od wytłumaczenia dlaczego najczęściej mamy z nim do czynienia podczas rotacji.
Podczas zmiany orientacji ekranu nasza Aktywność ulega zniszczeniu. Innymi słowy wywoływane są kolejno poniższe metody z jej cyklu życia (opis poszczególnych metod cyklu przedstawiony jest tutaj):

  • onPause()
  • onStop()
  • onDestroy()

po czym nasza Aktywność znika z pamięci.
Następnie system tworzy nową instancję wyświetlanej Aktywności, tyle, że tym razem w wersji odwróconej. Wywoływane są metody:

  • onCreate()
  • onStart()
  • onResume()

i naszym oczom ukazuje się nowa (w pełnym tego słowa znaczeniu) Aktywność.
W związku z tym, że jest to nowa instancja klasy Activity, normalnym jest, że informacje w niej zawarte nie są lub nie muszą być takie same jak w zniszczonej przed chwilą poprzedniej instancji klasy Aktywności.
Taka sytuacja może mieć miejsce również w innych przypadkach. Pomijając zamierzone zamknięcie i otwarcie aplikacji, czasami może się zdarzyć, że nasza Aktywność, która jest niewidoczna, zostanie na chwilę „ubita” w celu odzyskania zasobów. Kiedy zatem będziemy chcieli do niej wrócić, może się okazać, że i tu straciliśmy niektóre dane, ponieważ Aktywność, którą przywołaliśmy nie jest tym samym obiektem, który chwilę wcześniej oglądaliśmy.

Rozwiązanie

Na szczęście aby zapobiec przypadkom utraty danych Aktywności twórcy wprowadzili mechanizm służący do ich chwilowego przechowywania. Co ciekawe działa on domyślnie w każdym okienku, czego efektem jest zachowywanie stanu niektórych elementów UI.

Mała uwaga – należy zatem pamiętać, że nie jest błędem to, że tracimy niektóre dane podczas tworzenia Aktywności na nowo. Raczej usprawnieniem jest to, że niektórych nie tracimy. :)

Działanie mechanizmu opiera się na dwóch metodach:

  • onSaveInstanceState(Bundle outState) – służy do zapisywania stanu i jest wywoływana przed metodą onStop() w cyklu życia Aktywności. Należy jednak pamiętać, że sama nie należy do tego cyklu gdyż wywoływana jest tylko wtedy gdy Aktywność jest niszczona „bez wiedzy” użytkownika (jeżeli użytkownik sam wyłącza okno/aplikację, metoda ta nie jest wołana).
  • onRestoreInstanceState(Bundle savedInstanceState) – służy do odzyskiwania stanu i jest wywoływana zaraz po metodzie onStart() Aktywności. Co ciekawe jako jej argument otrzymujemy ten sam obiekt, co metoda onCreate(Bundle), dlatego też większość implementacji przenosi odzyskiwanie stanu właśnie tam.

Należy pamiętać, że mechanizm ten służy tylko i wyłącznie do chwilowego przechowywania stanu. Wszelkie dane są niszczone po zamknięciu aplikacji, dlatego też nie nadaje się on do innych zastosowań.

Poniżej przedstawię bardzo prosty przykład prezentujący wykorzystanie powyższych metod.

Przykładowa aplikacja

Działanie naszej aplikacji ma polegać na dodawaniu nowych elementów do pola ListView, których treść będziemy wpisywać w polu tekstowym.

Przygotowania

Oto plik main.xml opisujący wygląd naszej aplikacji:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<EditText
		android:layout_height="wrap_content"
		android:id="@+id/etText"
		android:layout_width="fill_parent"
		android:hint="Type text here..." />
	<Button
		android:layout_height="wrap_content"
		android:id="@+id/btnAdd"
		android:text="Add"
		android:layout_width="100dp" />
	<ListView
		android:id="@+id/listView"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content" />
</LinearLayout>

Oraz jej kod źródłowy:

package pl.froger.statesavingexample;

import java.util.ArrayList;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;

public class MainActivity extends Activity {
	private EditText etText;
	private Button btnAdd;
	private ListView listView;
	private ArrayList<String> itemList;
	private ArrayAdapter<String> adapter;

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

	private void initUI() {
		etText = (EditText) findViewById(R.id.etText);
		listView = (ListView) findViewById(R.id.listView);
		btnAdd = (Button) findViewById(R.id.btnAdd);
		initButtonClick();
		initList();
	}

	private void initButtonClick() {
		btnAdd.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				itemList.add(etText.getText().toString());
				adapter.notifyDataSetChanged();
			}
		});
	}

	private void initList() {
		itemList = new ArrayList<String>();
		adapter = new ArrayAdapter<String>(getApplicationContext(),
				android.R.layout.simple_list_item_1, itemList);
		listView.setAdapter(adapter);
	}
}

Oto jak zachowuje się nasza aplikacja przy rotacji:

Zapis i odczyt stanu Aktywności

Aby zapobiec temu co stało się powyżej, powinniśmy nadpisać dwie metody klasy Activity:

	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		super.onRestoreInstanceState(savedInstanceState);
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
	}

Dodatkowo polecam pozostawienie wywołań tych metod z klasy bazowej (słowo super), ponieważ dzięki temu zapisywany jest stan niektórych elementów interfejsu, o czym pisałem powyżej.

Dla ciekawskich polecam zakomentowanie tych wywołań. Wtedy nie dość, że zostanie usunięta zawartość ListView, to zniknie też wpisany przez nas tekst w polu EditText, który dotychczas był „przekazywany” pomiędzy starą a nową wersją Aktywności.

Do zapisywania stanu korzystamy z metod rozpoczynających się od put…, które wywołujemy na obiekcie Bundle (w naszym przypadku outState):

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putStringArrayList("listItems", itemList);
	}

W tym wypadku do paczki dopisujemy cały ArrayList, w którym przechowywana jest lista dodanych przez nas obiektów String.

Do odczytu, analogicznie stosujemy metody o początku get…:

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
	ArrayList items = savedInstanceState.getStringArrayList("listItems");
	for (String item : items) {
		itemList.add(item);
		super.onRestoreInstanceState(savedInstanceState);
}

Po kompletną listę metod do zapisywania/odczytu danych w paczce Bundle odsyłam do dokumentacji.

Tak nasza aplikacja będzie działała dzięki powyższym zabiegom:

Komentarze (0) Subskrybuj

Prześlij komentarz

Zaloguj się lub skorzystaj z profilu:

[rpxlogin redirect="http://www.android4devs.pl" prompt="" style="large"]

Możesz również zostawić komentarz bez rejestracji, korzystając z poniższego formularza:

Musisz być zalogowany aby móc pisać komentarze.