Zdarzenie asynchroniczne AsyncTask
Czasami dochodzi do sytuacji, kiedy musimy wykonać pewną operację zajmującą więcej czasu. Można oczywiście wykonać to "zwyczajnie" w kodzie, lecz użytkownik może odnieść wtedy wrażenie, że aplikacja się zawiesiła. Jest to oczywiście zdarzenie bardzo niepożądane. Na szczęście z pomocą przychodzi AsyncTask.
AsyncTask jest definiowany przez 3 typy generczne(z tego co widzę aktuanie nie mamy jeszcze artykułu o typach generycznych, ale postaram się w najbliższym czasie takowy dodać i tutaj podlinkować): Parametry, Progres i Wynik. Jest wykonywany w 4 krokach:
- onPreExecute
- doInBackgroud
- onProgressUpdate
- onPostExecute
Aby wykorzystać AsyncTask należy dziedziczyć (extends; więcej o dziedziczeniu tutaj i tutaj) po jej klasie.
import android.os.AsyncTask;
public class Obliczenia extends AsyncTask<Void, Void, Void>{
@Override
protected Void doInBackground(Void... arg0) {
// TODO Auto-generated method stub
return null;
}
}
Spróbujmy napisać aplikację, która imituje zadanie wykonywane w tle.
MainActivity.java
package com.example.asynctaskdemo;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
public static final int PLEASE_WAIT_DIALOG = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void buttonGo(View view) {
new Obliczenia(this).execute();
}
@Override
public Dialog onCreateDialog(int dialogId) {
switch (dialogId) {
case PLEASE_WAIT_DIALOG:
ProgressDialog dialog = new ProgressDialog(this);
dialog.setTitle("Obliczanie");
dialog.setMessage("Proszę czekać....");
dialog.setCancelable(true);
return dialog;
default:
break;
}
return null;
}
}
Warto zwrócić uwagę na obsługę przycisku. Nie używam tutaj metody findElementById() i nie przypisuję mu listenera, tylko korzystam z możliwości dodania do przycisku atrybutu onClick="nazwaMetody". Następnie wystarczy napisać publiczną metodę przyjmującą w parametrze View(referencję do przycisku który tą metodę wywołał).
Zauważcie, że nie tworzę sobie typu Obliczenia, tylko od razu po stworzeniu wywołuję na nim metodę run(). Jest to spowodowane m.in. tym, że AsyncTask możemy wykonać tylko raz. Aby wykonać zadanie ponownie należy stworzyć kolejną instancję.
Kolejną ciekawostką jest użycie metody onCreateDialog(int dialogID). Dlaczego tak, zamiast po prostu dialog.show()? Otóż dialog.show() może doprowadzić do wycieków pamięci w przypadku, gdy Activity w tle zostanie niespodziewanie zamknięte. W ten sposób całą odpowiedzialność za ewentualne wycieki pamięci przejmuje za nas Android, a my mamy jedną rzecz mniej o którą możemy się martwić. Niestety według dokumentacji od API 13 jest to metoda przedawniona i zaleca się teraz stosowanie kalsy DialogFragment z FragmentManager. Jeszcze się z tym nie zapoznałem. Jak zobaczę o co chodzi, to napiszę odpowiedni artykuł i podlinkuję tutaj.
Obliczenia:
package com.example.asynctaskdemo;
import android.app.Activity;
import android.os.AsyncTask;
import android.widget.Toast;
public class Obliczenia extends AsyncTask<Void, Void, Void> {
Activity wywolujaceActivity;
public Obliczenia(Activity wywolujaceActivity) {
this.wywolujaceActivity = wywolujaceActivity;
}
@Override
protected void onPreExecute() {
wywolujaceActivity.showDialog(MainActivity.PLEASE_WAIT_DIALOG);
}
@Override
protected Void doInBackground(Void... arg0) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void result) {
wywolujaceActivity.removeDialog(MainActivity.PLEASE_WAIT_DIALOG);
Toast.makeText(wywolujaceActivity, "Obliczono!", Toast.LENGTH_SHORT).show();
}
}
Stworzyłem AsyncTask, który stosuje trzykrotnie typ Void - czyli nie przyjmuje żadnych parametrów, nie zwraca żadnych informacji o stanie i nie zwraca żadnego wyniku.
Dodałem swój konstruktor umożliwiający otrzymanie referencji do Activity która go wywołała, przez co mam możliwość wyświetlenia dialogu, oraz Toastu.
Same Obliczenia nic nie robią oprócz 5 sekund uśpienia, co dla celów demonstracyjnych dobrze się sprawdza.
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:onClick="buttonGo"
android:text="Go!" />
</RelativeLayout>
Tutaj jedynką ciekawostką o której już wcześniej wspomniałem jest android:onClick="buttonGo", które umożliwia wygodniejsze i "czystsze" przechwycenie akcji guzika.
Możliwe, że w przyszłości rozwinę artykuł. Zależy głównie od zainteresowania tematem :)
Na razie tyle.
Po więcej informacji zapraszam do dokumentacji :)
Dyskusja i komentarze
Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.
Poniżej znajdziesz archiwalne wpisy z czasów, gdy strona była jeszcze hobbystycznym blogiem.
Mateusz
A jakbyśmy chcieli umieścić na jednym ekranie jakiś sekundnik, oraz np. TextView? Mógłbyś opisać problem?
Grzegorz
Tak jak kolega. Jak zmienić TextView w asynctask?
Fiddle
Obliczenia w klasie "Obliczenia" dopiszcie sobie pustą metode którą co sekundę wywołuje doInBackground następpnie tam gdzie tworzycie instancje "Obliczenia" nadpiszcie stworzoną metodę tak by edytowała co chcecie, będzie miała w tedy dostep do zmiennych lokalnych
Tolk
Czy jeden AsyncTask może wywoływać inny AsyncTask?
Marcin Kunert
Podejrzewam, że nie powinno być z tym problemu, ale jeszcze to sprawdzę.
Michał
"Zauważcie, że nie tworzę sobie typu Obliczenia, tylko od razu po stworzeniu wywołuję na nim metodę run()." Nie run() tylko execute()
gosciu
"z tego co widzę aktuanie nie mamy jeszcze artykułu o typach generycznych, ale postaram się w najbliższym czasie takowy dodać i tutaj podlinkować" Minęło 10miesięcy i nadal takowego artykułu nie ma...
Maciek
Czyli nie ma różnicy wyświetlania informacji o ładowaniu danych w sposób opisany wyżej czy w artykule "Ekran ładowania – Splash Screen"?