Skip to content

Commit 547de47

Browse files
committed
handlerthread and intentservice post added
1 parent 6f56395 commit 547de47

File tree

9 files changed

+346
-1
lines changed

9 files changed

+346
-1
lines changed
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
---
2+
layout: post
3+
title: "HandlerThread"
4+
date: 2019-06-24
5+
categories: ["Background"]
6+
image: background/handlerthread
7+
github: background/tree/master/handlerthread
8+
description: "Background threading"
9+
keywords: "thread, handler, handlerthread, looper, threadpool, executor, runnable, callable, future, async, task, background, threading, android, programowanie, programming"
10+
---
11+
12+
## Thread
13+
Klasa `Thread` w `Java` reprezentuje wątek wykonania programu i jest podstawą wielu implementacji mechanizmów wielowątkowości. Pozwala na wykonanie pracy na innym wątku. W czystej implementacji bez wsparcia innych klas może służyć do wykonywania długich zadań bez komunikacji z wątkiem głównym. Wątek działa niezależnie od cyklu zycia aplikacji, dlatego potrafia przetrwać zmianę konfiguracji czy wyjście z aplikacji (trafia do póli wątków). Zatrzymanie działania następuje automatycznie po zakończeniu pracy lub w sposób manualny (metodą `interrupt`). Może być tylko raz wykonany zatem każde uruchomienie oczekiwanej pracy wymaga nowej instancji co sprawia, że nie nadaje sie do ponownego użytku. Komunikacja z wątkiem głównym może przebiegać przy użyciu klasy `Handler`.
14+
15+
{% highlight kotlin %}
16+
class ThreadActivity : AppCompatActivity() {
17+
18+
val runnable = Runnable {
19+
//some background job
20+
runOnUiThread {
21+
//UI job if needed
22+
}
23+
}
24+
val thread = Thread(runnable) //pass runnable or create anonymous object
25+
26+
override fun onCreate(savedInstanceState: Bundle?) {
27+
super.onCreate(savedInstanceState)
28+
setContentView(R.layout.activity_thread)
29+
30+
button.setOnClickListener {
31+
//do not allow start thread if is started
32+
if(thread.state == Thread.State.NEW)
33+
thread.start()
34+
}
35+
}
36+
37+
override fun onDestroy() {
38+
super.onDestroy()
39+
if(thread.state != Thread.State.TERMINATED)
40+
thread.interrupt() //stop the thread if needed
41+
}
42+
}
43+
{% endhighlight %}
44+
45+
## Looper
46+
Mówiąc o wątkach w Android nie sposób nie wspomnieć o klasie `Looper`, która używana jest do uruchamiania pętli wiadomości dla wątku. Wspiera wykonanie pracy wątku przez dostarczanie wybranych wiadomości i zadań z kolejki do odpowiednich obiektów klasy `Handler`. Każdy wątek `Thread` może mieć jedną uniknalną instancje `Looper`.
47+
48+
## Handler
49+
`Handler` umożliwia komunikacje między wątkami poprzez wykorzystanie kanału wiadomości. Jest pośrednio powiązany z wątkiem `Thread` poprzez bezpośrednie przekazanie obiektu `Looper` danego wątku w konstruktorze (domyślnie jest to `Looper` wątku w którym `Handler` jest tworzony). Działa więc na wątku, który jest właścicielem dostarczonej instancji `Looper`. Wiadomość `Message` może być wysyłana z dowolnego miejsca przez `sendMessage` oraz zostać odebrana przez `handleMessage` na wątku obiektu `Handler`. Natomiast zadanie `Runnable` wywoływane jest metodą post, a jego wykonanie odbywa się na wątku obiektu `Handler`.
50+
51+
{% highlight kotlin %}
52+
class HandlerActivity : AppCompatActivity() {
53+
54+
val START = 1
55+
val FINISH = 2
56+
57+
val runnable = Runnable {
58+
//some background job
59+
handler.sendMessage(createMessage(FINISH, "result")) //send message to handle action
60+
handler.post {
61+
//do action on UI without message communication
62+
}
63+
}
64+
65+
val thread: Thread = Thread(runnable)
66+
67+
val handler = object: Handler(Looper.getMainLooper()) { //pass Looper of thread owner
68+
override fun handleMessage(msg: Message?) {
69+
super.handleMessage(msg)
70+
if(msg?.what == START && thread.state == Thread.State.NEW) {
71+
thread.start()
72+
}
73+
else if(msg?.what == FINISH) {
74+
//some action with msj.obj result
75+
}
76+
}
77+
}
78+
79+
fun createMessage(what : Int, obj : Any) : Message {
80+
val message = Message()
81+
message.what = what
82+
message.obj = obj
83+
return message
84+
}
85+
86+
override fun onCreate(savedInstanceState: Bundle?) {
87+
super.onCreate(savedInstanceState)
88+
setContentView(R.layout.activity_handler)
89+
90+
button.setOnClickListener {
91+
//instead of direct thread.start() it can be done by sending message
92+
handler.sendEmptyMessage(START)
93+
}
94+
}
95+
96+
override fun onDestroy() {
97+
super.onDestroy()
98+
handler.removeCallbacksAndMessages(null) //remove pending work from MessageQueue
99+
thread.interrupt()
100+
}
101+
}
102+
{% endhighlight %}
103+
104+
## HandlerThread
105+
`HandlerThread` jest rozbudowaną klasą `Thread`, która posiada własną instancję `Looper` i `Handler` dzięki czemu potrafi kolejkować zadania i może być użyty wielokrotnie. Zadania wykonywane są synchronicznie w sposób sekwencyjny. Obiekt `HandlerThread` podobnie jak Thread jest aktywny dopóki nie zostanie zniszczony automatycznie przez system lub ręcznie.
106+
107+
{% highlight kotlin %}
108+
class HandlerThreadActivity : AppCompatActivity() {
109+
110+
//work to do by requestHandler
111+
val runnable = Runnable {
112+
//some background job
113+
responseHandler.sendMessage(Message()) //send message or post the job
114+
responseHandler.post {
115+
//some UI action
116+
}
117+
}
118+
119+
//handler on the main thread, override handleMessage if needed
120+
val responseHandler = object: Handler(Looper.getMainLooper()) {
121+
override fun handleMessage(msg: Message?) {
122+
super.handleMessage(msg)
123+
//some action on UI
124+
}
125+
}
126+
127+
//handler on the background thread of handlerThread object
128+
lateinit var requestHandler: Handler
129+
130+
//instead of Thread use reusable HandlerThread
131+
//this can be also done by extend HandlerThread class and putting there Handler object
132+
val handlerThread = HandlerThread("HandlerThread")
133+
134+
override fun onCreate(savedInstanceState: Bundle?) {
135+
super.onCreate(savedInstanceState)
136+
setContentView(R.layout.activity_handler_thread)
137+
138+
handlerThread.start() //start the thread
139+
requestHandler = Handler(handlerThread.looper) //now Looper of handlerThread can be passed
140+
141+
button.setOnClickListener {
142+
requestHandler.post(runnable) //post the job to MessageQueue
143+
}
144+
}
145+
146+
override fun onDestroy() {
147+
super.onDestroy()
148+
//remove tasks and messages from handlers
149+
requestHandler.removeCallbacksAndMessages(null)
150+
responseHandler.removeCallbacksAndMessages(null)
151+
handlerThread.quitSafely() //quit HandlerThread's Looper
152+
handlerThread.interrupt() //stop current job
153+
}
154+
}
155+
{% endhighlight %}
156+
157+
## Pula wątków
158+
Pula wątków (`ThreadPoolExecutor`) jest pojedynczą kolejką typu `FIFO` składającą się z grupy wątków roboczych, które w momencie osiągnięcia stanu dostępności pobierają zadanie z kolejki. Umożliwia asynchroniczne wykonanie zadań przy zachowaniu optymalnej strategii przetwarzania wielowątkowoego, dostarcza mechanizm zarządzania wątkami (dodawanie, anulowanie, priorytety) oraz prostą statystykę. Pula wątków może być użyta przez jedną z implementacji interfejsu `Executor`, `Callable` czy `Future`. Przeważnie instancje uzyskuje się przy pomocy metody wytwórczej z klasy `Executors` oferującej kilka rodzajów typu puli watków.
159+
160+
{% highlight kotlin %}
161+
class ThreadPoolActivity : AppCompatActivity() {
162+
163+
//this ExecutorService is wrapped ThreadPoolExecutor (this class extends ExecutorService)
164+
//equivalent of ThreadPoolExecutor with min and max only 1 thread in pool and LinkedBlockingQueue
165+
val executor : ExecutorService = Executors.newSingleThreadExecutor()
166+
167+
val runnable = Runnable {
168+
//some background job
169+
uiHandler.post {
170+
//some UI job
171+
}
172+
}
173+
174+
val callable = Callable {
175+
//some background job and return result
176+
return@Callable "result"
177+
}
178+
179+
val uiHandler = Handler()
180+
181+
override fun onCreate(savedInstanceState: Bundle?) {
182+
super.onCreate(savedInstanceState)
183+
setContentView(R.layout.activity_thread_pool)
184+
185+
button1.setOnClickListener {
186+
//use executor with Runnable
187+
executor.execute(runnable)
188+
}
189+
190+
button2.setOnClickListener {
191+
//use executor with Callable
192+
val future : Future<String> = executor.submit(callable)
193+
val result = future.get()
194+
//this can be canceled by run future.cancel()
195+
}
196+
}
197+
198+
override fun onDestroy() {
199+
super.onDestroy()
200+
//remove tasks and messages from handlers
201+
uiHandler.removeCallbacksAndMessages(null)
202+
executor.shutdownNow() //kill all threads
203+
}
204+
}
205+
{% endhighlight %}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
layout: post
33
title: "AsyncTask"
4-
date: 2019-06-24
4+
date: 2019-07-01
55
categories: ["Background"]
66
image: background/asynctask
77
github: background/tree/master/asynctask
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
---
2+
layout: post
3+
title: "IntentService"
4+
date: 2019-07-08
5+
categories: ["Background"]
6+
image: background/intentservice
7+
github: background/tree/master/intentservice
8+
description: "Background threading"
9+
keywords: "intentservice, jobintentservice, broadcastreceiver, async, task, background, onhandleintent, threading, android, programowanie, programming"
10+
---
11+
12+
## Wstęp
13+
`IntentService` jest rozszerzeniem klasy `Service` o obsługę zadań przetwarzanych na w tle na dedykowanym wątku roboczym przy pomocy klasy Handler. Umożliwia wykonanie pracy w sposób asynchroniczny niezależnie od wątku głównego, jednakże obsługuje tylko jedno zadanie w danej chwili. Wysyłane żądania w postaci obiektu `Intent` zostają natychmiast obsłużone lub trafiają do kolejki oczekujących. Gdy cała kolejka zadań zostanie zrealizowana wówczas usługa automatycznie kończy pracę. Większość zdarzeń cyklu życia interfejsu użytkownika nie ma wpływu na działanie `IntentService` (w przeciwieństwie do `AsyncTask`). Przeznaczony jest przede wszystkim do prostych operacji w tle, które niekoniecznie wymagają reakcji interfejsu użytkownika na otrzymany rezultat.
14+
15+
## Implementacja
16+
Aby stworzyć komponent `IntentService` należy zdefiniować klasę rozszerzającą klasę `IntentService`, nadpisać metodę `onHandleIntent` odpowiedzialną za przechwytywanie żądań i podobnie jak przy klasycznej usłudze (`Service`) dodać do pliku manifestu (`AndroidManifest`).
17+
18+
{% highlight kotlin %}
19+
class CustomIntentService : IntentService("name") {
20+
21+
override fun onHandleIntent(intent: Intent?) {
22+
//retrieve data
23+
val action = intent?.action
24+
val data = intent?.dataString
25+
//do some work based on action
26+
}
27+
28+
//avoid to override Service's callback like onStartCommand
29+
//they are automatically invoked by IntentService
30+
}
31+
32+
class MainActivity : AppCompatActivity() {
33+
34+
override fun onCreate(savedInstanceState: Bundle?) {
35+
super.onCreate(savedInstanceState)
36+
setContentView(R.layout.activity_main)
37+
button.setOnClickListener { startIntentService() }
38+
}
39+
40+
//just call startService to run IntentService
41+
private fun startIntentService() {
42+
val intent = Intent(this, CustomIntentService::class.java)
43+
intent.action = "action"
44+
intent.putExtra("extra", "value")
45+
startService(intent)
46+
}
47+
}
48+
{% endhighlight %}
49+
50+
## Komunikacja
51+
`IntentService` nie dostarcza mechanizmu odpowiedzi zwrotnej do klientów. W celu implementacji komunikacji do interfejsu użytkownika lub innych fragmentów kodu aplikacji można posłużyć się m.in. `BroadcastReceiver` lub szyną zdarzeń.
52+
53+
{% highlight kotlin %}
54+
class IntentServiceCommunication : IntentService("name") {
55+
56+
override fun onHandleIntent(intent: Intent?) {
57+
//retrieve data
58+
val action = intent?.action
59+
val data = intent?.getStringExtra("extra")
60+
//do some work based on action
61+
62+
sendBroadcast()
63+
}
64+
65+
private fun sendBroadcast() {
66+
val broadcastIntent = Intent("FILTER_ACTION")
67+
broadcastIntent.putExtra("message", "result")
68+
sendBroadcast(broadcastIntent)
69+
Log.d("IntentService", "sendBroadcast")
70+
}
71+
}
72+
73+
class MainActivity : AppCompatActivity() {
74+
75+
private val receiver = CustomBroadcastReceiver()
76+
77+
override fun onCreate(savedInstanceState: Bundle?) {
78+
super.onCreate(savedInstanceState)
79+
setContentView(R.layout.activity_main)
80+
button.setOnClickListener { startIntentService() }
81+
82+
registerReceiver(receiver, IntentFilter("FILTER_ACTION"))
83+
}
84+
85+
override fun onDestroy() {
86+
//if service is running and don't need anymore callback then stop IntentService
87+
unregisterReceiver(receiver)
88+
super.onDestroy()
89+
}
90+
91+
fun startIntentService() {
92+
val intent = Intent(this, CustomIntentService::class.java)
93+
intent.action = "action"
94+
intent.putExtra("extra", "value")
95+
startService(intent)
96+
}
97+
98+
//implement as nested class if only need to interact with this activity
99+
class CustomBroadcastReceiver : BroadcastReceiver() {
100+
101+
override fun onReceive(context: Context?, intent: Intent?) {
102+
val message = intent?.getStringExtra("message")
103+
//do something like update UI
104+
}
105+
}
106+
}
107+
{% endhighlight %}
108+
109+
## Ograniczenia
110+
Poza koniecznością zapewnienia mechanizmu odpowiedzi zwrotnej do klienta oraz ograniczeniem przepływu pracy do jednego wątku należy uwzględnić także restrykcje procesów w tle dla `Service` (w tym `IntentService`) w wyniku których usługa musi działać w trybie `foreground` (inaczej zostanie po pewnym czasie zatrzymana). Rozwiązaniem tego problemu może być użycie `JobIntentService` lub wystartowanie usługi w trybie foreground (`startForegroundService`) i wyświetlenie notyfikacji z poziomu usługi (`startForeground`).
111+
112+
## JobIntentService
113+
`JobIntentService` w zależności od wersji systemu dobiera optymalną strategię wykonywania kolejki zadań w tle. W przypadku wersji systemu od `Android Oreo` zadania zostaną wykonane przy użyciu `JobScheduler`, natomiast dla starszych wersji usługa zostanie wywołana w standardowy sposób przy pomocy metody `startService`. Klasa rozszerzająca `JobIntentService` powinna nadpisać metodę `onHandleWork` w której będzie wykonywana praca (podobnie jak w `onHandleIntent` w `IntentService`). Klient dodaje zadanie do kolejki wywołując `enqueueWork`.
114+
115+
{% highlight kotlin %}
116+
class CustomJobIntentService : JobIntentService() {
117+
118+
companion object {
119+
const val ID = 100
120+
121+
//method for enqueuing work to this service, just call from client to start
122+
fun enqueueWork(context : Context, work : Intent) {
123+
enqueueWork(context, CustomJobIntentService::class.java, ID, work)
124+
}
125+
}
126+
127+
override fun onHandleWork(intent: Intent) {
128+
//do some job here
129+
//use BroadcastReceiver or something else to post the result
130+
}
131+
}
132+
{% endhighlight %}
133+
134+
{% highlight xml %}
135+
<!-- remember to add Service to AndroidManifest in the same way like base JobService
136+
//with required BINB_JOB_SERVICE permission -->
137+
<service
138+
android:name=".CustomJobIntentService"
139+
android:permission="android.permission.BIND_JOB_SERVICE"/>
140+
{% endhighlight %}
244 KB
Loading
15.8 KB
Loading
34.9 KB
Loading
546 KB
Loading
17.7 KB
Loading
51.6 KB
Loading

0 commit comments

Comments
 (0)