Skip to content

Commit 6f56395

Browse files
committed
async task post added
1 parent 1ff9520 commit 6f56395

File tree

4 files changed

+142
-0
lines changed

4 files changed

+142
-0
lines changed

_drafts/2019-06-24-asynctask.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
---
2+
layout: post
3+
title: "AsyncTask"
4+
date: 2019-06-24
5+
categories: ["Background"]
6+
image: background/asynctask
7+
github: background/tree/master/asynctask
8+
description: "Background threading"
9+
keywords: "asynctask, async, task, background, threading, doinbackground, onpreexecute, onprogressupdate, onpostexecuted, execute, weakreference, android, programowanie, programming"
10+
---
11+
12+
## Wstęp
13+
`AsyncTask` umożliwia wykonanie zadania w tle i publikowanie stanu pracy w interfejsie użytkownika bez konieczności manipulowania wątkami. Jest zaprojektowany jako klasa pomocnicza wokół klasy `Thread` i `Handler`. Wykorzystywany przede wszystkim do krótkich jednorazowych operacji powiązanych z bieżącym ekranem, które wymagają przetworzenia rezultatu na wątku głównym aplikacji. Dostarcza prosty w obsłudze interfejs co zwalnia programistę z obowiązku zajmowania się obsługą wątków (roboczego i głównego).
14+
15+
## Implementacja
16+
Aby stworzyć zadanie asynchroniczne należy rozszerzyć klasę `AsyncTask` oraz nadpisać przynajmniej metodę `doInBackground` odpowiedzialną za wykonywanie zadania na wątku roboczym. Pozostałe metody `onPreExecute`, `onProgressUpdate`, `onPostExecute`, `onCancelled` są wykonywane na głównym wątku dla różnych stanów zadania. Częstą praktyką jest implementacja klasy `AsyncTask` jako klasy wewnętrznej (`inner class`) w kontekście wywołania. Rozpoczęcie wykonywania zadania odbywa się przy pomocy metody `execute`.
17+
18+
{% highlight kotlin %}
19+
//first of generic types are params, second progress and third is final result
20+
private class CustomAsyncTask : AsyncTask<String, Int, String>() {
21+
22+
override fun onPreExecute() {
23+
//prepare before running background job like show progress dialog
24+
}
25+
26+
override fun doInBackground(vararg params: String): String {
27+
//some background job with passed params like URLs
28+
var total = ""
29+
for((counter, url) in params.withIndex()) {
30+
//download info from url
31+
val result = "result from : $url\n"
32+
total = total.plus(url)
33+
publishProgress(counter) //inform that some part of full request if completed
34+
}
35+
return total
36+
}
37+
38+
override fun onProgressUpdate(vararg values: Int?) {
39+
//show some progress updates based on Int from publishProgress
40+
}
41+
42+
override fun onPostExecute(result: String) {
43+
//do something on main thread when background job finished like update view
44+
}
45+
46+
override fun onCancelled() {
47+
//stop and clear or show some message
48+
}
49+
}
50+
51+
//to start just call
52+
//CustomAsyncTask().execute("url1", "url2", "url3")
53+
{% endhighlight %}
54+
55+
## Ograniczenia
56+
Implementacja klasy `AsyncTask` jako klasy wewnętrznej może powodować wycieki pamięci (`leak memmory`). Instancja takiej klasy przechowuje referencje do klasy zewnętrznej (np. `Activity`, `Fragment`) niezależnie od stanu cyklu życia obiektu klasy zewnętrznej co powstrzymuje `Garbage Collector` przed usunięciem referencji. Aby temu zapobiec należy stworzyć klasę jako statyczną wewnętrzną (`static inner`), zagnieżdżoną (`nested`) lub na poziomie `top-level`. W przypadku wymaganej referencji do `Activity` warto posłużyć się słabą referencją `WeakReference` lub przekazać obiekt `Listener` implementujący oczekiwane zachowanie interfejsu użytkownika.
57+
58+
{% highlight kotlin %}
59+
class MainActivity : AppCompatActivity(), Listener {
60+
61+
//reference to task allows to manage it's lifecycle
62+
private var asyncTask : SaferAsyncTask? = null
63+
64+
override fun onCreate(savedInstanceState: Bundle?) {
65+
super.onCreate(savedInstanceState)
66+
setContentView(R.layout.activity_main)
67+
68+
button.setOnClickListener {
69+
//AsyncTask is single shot, so create new task every time
70+
if(asyncTask == null || asyncTask?.status != AsyncTask.Status.RUNNING) {
71+
asyncTask = SaferAsyncTask(this)
72+
asyncTask?.execute("url1", "url2", "url3")
73+
}
74+
}
75+
}
76+
77+
override fun onDestroy() {
78+
//stop the task when exit from Activity
79+
if(asyncTask?.status == AsyncTask.Status.RUNNING)
80+
asyncTask?.cancel(true)
81+
super.onDestroy()
82+
}
83+
84+
override fun onStarting() {
85+
//create progress dialog
86+
}
87+
88+
override fun onProgress(progress: Int) {
89+
//update progress dialog
90+
}
91+
92+
override fun onFinished(result: String) {
93+
//close progress dialog
94+
//show the result
95+
}
96+
97+
override fun onCancel() {
98+
//close progress dialog
99+
//show error message
100+
}
101+
102+
private class SaferAsyncTask(listener : Listener) : AsyncTask<String, Int, String>() {
103+
104+
private val reference = WeakReference<Listener>(listener)
105+
106+
override fun onPreExecute() {
107+
reference.get()?.onStarting()
108+
}
109+
110+
override fun doInBackground(vararg params: String): String {
111+
var total = ""
112+
for((counter, url) in params.withIndex()) {
113+
val result = "result from : $url\n"
114+
total = total.plus(result)
115+
publishProgress(counter) //inform that some part of full request if completed
116+
}
117+
return total
118+
}
119+
120+
override fun onProgressUpdate(vararg values: Int?) {
121+
values[0]?.let { reference.get()?.onProgress(it) }
122+
}
123+
124+
override fun onPostExecute(result: String) {
125+
reference.get()?.onFinished(result)
126+
}
127+
128+
override fun onCancelled() {
129+
reference.get()?.onCancel()
130+
}
131+
}
132+
}
133+
134+
private interface Listener {
135+
fun onStarting()
136+
fun onProgress(progress : Int)
137+
fun onFinished(result : String)
138+
fun onCancel()
139+
}
140+
{% endhighlight %}
141+
142+
Ponadto należy również zwrócić uwagę na fakt iż tak skonstruowane zadanie nie przetrwa zmiany konfiguracji co będzie skutkowało utratą aktualnego progresu. Aby temu zapobiec można umieścić obiekt `AsyncTask` w obiekcie `Fragment` z ustawionym `setRetainInstance(true)` lub w `ViewModel`. Co więcej, `AsyncTask` jest zadaniem jednorazowym i każde jego ponowne wywołanie wymaga nowej instancji w związku z czym warto zadbać o odpowiednią obsługę stanu zadania w zależności od cyklu życia. Ogranicza również komunikację z innymi zadaniami współbieżnymi wykonywanymi na innych wątkach.
301 KB
Loading
11 KB
Loading
28.6 KB
Loading

0 commit comments

Comments
 (0)