In Android, threading can be performed in the standard way by using threads. It is not recommended to just fire naked threads without any control. So, for this purpose, you can use the ThreadPools and Executor classes.
To demonstrate this, we will update our application. Create a new package called execution with a class called TaskExecutor. Make sure it looks like this:
package com.journaler.execution
import java.util.concurrent.BlockingQueue
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
class TaskExecutor private constructor(
corePoolSize: Int,
maximumPoolSize: Int,
workQueue: BlockingQueue<Runnable>?
) : ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
0L,
TimeUnit.MILLISECONDS,
workQueue
) {
companion object {
fun getInstance(capacity: Int): TaskExecutor {
return TaskExecutor(
capacity,
capacity * 2,
LinkedBlockingQueue<Runnable>()
)
}
} }
We extended the ThreadPoolExecutor class and companion object with the member method for executor instantiation. Let's apply it to our existing code. We will switch from the AsyncTask class we used to TaskExecutor. Open the NoteActivity class and update it as follows:
class NoteActivity : ItemActivity() { ... private val executor = TaskExecutor.getInstance(1) ... private val locationListener = object : LocationListener { override fun onLocationChanged(p0: Location?) { p0?.let { LocationProvider.unsubscribe(this) location = p0 val title = getNoteTitle() val content = getNoteContent() note = Note(title, content, p0) executor.execute { val param = note var result = false param?.let { result = Db.insert(param) } if (result) { Log.i(tag, "Note inserted.") } else { Log.e(tag, "Note not inserted.") } } } } override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?)
{} override fun onProviderEnabled(p0: String?) {} override fun onProviderDisabled(p0: String?) {} } ... private fun updateNote() { if (note == null) { if (!TextUtils.isEmpty(getNoteTitle()) &&
!TextUtils.isEmpty(getNoteContent())) { LocationProvider.subscribe(locationListener) } } else { note?.title = getNoteTitle() note?.message = getNoteContent() executor.execute { val param = note var result = false param?.let { result = Db.update(param) } if (result) { Log.i(tag, "Note updated.") } else { Log.e(tag, "Note not updated.") } } } } ... }
As you can see, we replaced AsyncTask with the executor. Our executor will handle only one thread at a time.
Other than the standard thread approach, Android also provides Handlers as one of the options for developers. Handlers are not a replacement for threads, but an addition! A Handler instance registers itself with its parent thread. It represents a mechanism to send data to that particular thread. We can send instances of the Message or Runnable class. Let's illustrate its use with an example. We will update the Notes screen with an indicator that will be green if everything is performed correctly. If database persisting fails, it will be red. Its default color will be grey. Open the activity_note.xml file and extend it with the indicator. The indicator will be plain view, as shown here:
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android=
"http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/black_transparent_40" android:orientation="vertical"> ... <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <View android:id="@+id/indicator" android:layout_width="40dp" android:layout_height="40dp" android:layout_alignParentEnd="true" android:layout_centerVertical="true" android:layout_margin="10dp" android:background="@android:color/darker_gray" /> <EditText android:id="@+id/note_title" style="@style/edit_text_transparent" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/title" android:padding="@dimen/form_padding" /> </RelativeLayout> ... </LinearLayout> </ScrollView>
Now, when we add the indicator, it will change its color depending on the database insertion result. Update your NoteActivity class source code like this:
class NoteActivity : ItemActivity() { ... private var handler: Handler? = null .... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) handler = Handler(Looper.getMainLooper()) ... } ... private val locationListener = object : LocationListener { override fun onLocationChanged(p0: Location?) { p0?.let { ... executor.execute { ... handler?.post { var color = R.color.vermilion if (result) { color = R.color.green } indicator.setBackgroundColor( ContextCompat.getColor( this@NoteActivity, color ) ) } } } } override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?)
{} override fun onProviderEnabled(p0: String?) {} override fun onProviderDisabled(p0: String?) {} } ... private fun updateNote() { if (note == null) { ... } else { ... executor.execute { ... handler?.post { var color = R.color.vermilion if (result) { color = R.color.green }
indicator.setBackgroundColor
(ContextCompat.getColor( this@NoteActivity, color )) } } } } }
Build your application and run it. Create a new note. You will notice that the indicator changed color to green after you entered a title and the message content.
We will make some more changes and do the same thing with the Message class instance. Update your code according to this example:
class NoteActivity : ItemActivity() { ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) handler = object : Handler(Looper.getMainLooper()) { override fun handleMessage(msg: Message?) { msg?.let { var color = R.color.vermilion if (msg.arg1 > 0) { color = R.color.green } indicator.setBackgroundColor
(ContextCompat.getColor( this@NoteActivity, color )) } super.handleMessage(msg) } } ... } ... private val locationListener = object : LocationListener { override fun onLocationChanged(p0: Location?) { p0?.let { ... executor.execute { ... sendMessage(result) } } } override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?)
{} override fun onProviderEnabled(p0: String?) {} override fun onProviderDisabled(p0: String?) {} } ... private fun updateNote() { if (note == null) { ... } else { ... executor.execute { ... sendMessage(result) } } } ... private fun sendMessage(result: Boolean) { val msg = handler?.obtainMessage() if (result) { msg?.arg1 = 1 } else { msg?.arg1 = 0 } handler?.sendMessage(msg) } ... }
Pay attention to the Handler instantiation and the sendMessage() method. We obtained the Message instance using the obtainMessage() method from our Handler class. As the message argument, we passed an integer datatype. Depending on its value, we will update the indicator color.