耗时操作需要放在子线程中运行,否则会导致主线程被阻塞,从而影响用户对软件的正常使用。
一、线程基本用法
Kotlin中使用线程方法和Java类似,可以选择继承Thread类或实现Runnable接口来实现线程,而Kotlin还给我们提供了一种更加简单的开启线程的方法,写法如下:
thread {
// 编写具体逻辑
}
thread是Kotlin一个内置顶层函数,只需要在Lambda中编写逻辑即可,start()也不需要。
二、在子线程中更新UI——异步消息处理机制
class MainActivity : AppCompatActivity() {
// 消息标记
val updateText = 1
val handler = object : Handler() {
override fun handleMessage(msg: Message) {
// 在这里进行UI操作,这里是在主线程中
when(msg) {
updateText -> {}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
thread {
val msg = Message()
msg.what = updateText
// 将Message对象发送出去
handler.sendMessage(msg)
}
}
}
Android中的异步消息处理主要由4个部分组成,Message、Handler、MessageQueue、Looper。
-
Message
Message是在线程之前传递的消息,内部可以携带少量信息并在线程间传递,除了what字段,还可以使用arg1和arg2字段来携带一些整形数据,obj字段携带一个Object对象。 -
Handler
处理者,主要用于发送和处理消息,发送消息一般用sendMessage()或post(),发出的消息经过一系列辗转处理后最终传递到Handler的handleMessage()方法中。 -
MessageQueue
消息队列,用于存放所有通过Handler发送的消息,消息会一直存在于消息队列中等待被处理,每个线程中只有一个MessageQueue对象。 -
Looper
每个线程中的MessageQueue管家,调用Looper的loop()方法后,就会进入一个无限循环中,每当在MessageQueue中发现一条消息时,就将它取出并传递到Handler的handleMessage()方法中,每个线程中只有一个Looper对象。
三、在子线程中更新UI——AsyncTask
AsyncTask可能即将废弃。为了更方便在子线程中对UI进行操作,Android还提供了AsyncTask等一些好用的工具,借助AsyncTask即使对异步消息处理机制完全不了解,也可以十分简单的从子线程切换到主线程,AsyncTask实现原理也是基于异步消息处理机制的,Android帮我们做了很好的封装。
AsyncTask是一个抽象类,在继承时可以为AsyncTask指定3个泛型参数,这三个参数用途如下:
-
Params
在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。 -
Progress
后台执行任务时,如需在界面显示当前进度,使用这里指定的泛型作为进度单位。 -
Result
任务执行完毕后,如需对结果进行返回,则使用这里指定的泛型作为返回值类型。
一个比较完整的自定义AsyncTask可以写成如下形式:
class DownloadTask : AsyncTask<Unit, Int, Boolean>() {
override fun onPreExecute() {
// 显示进度对话框
progressDialog.show()
}
override fun doInBackground(vararg params: Unit?) = try {
while (true) {
// 虚构方法
val downloadPercent = doDownload()
// 调用onProgressUpdate()方法并传入参数
publishProgress(downloadPercent)
if (downloadPercent >= 100) {
break
}
}
true
} catch (e: Exception) {
false
}
override fun onProgressUpdate(vararg values: Int?) {
// 在这里更新下载进度,这里可以操作UI
progressDialog.setMessage("Download ${values[0]}%")
}
override fun onPostExecute(result: Boolean) {
// 关闭进度对话框
progressDialog.dismiss()
if(result) {
...
} else {
...
}
}
}
// 启动任务
fun main() {
// execute()可以传参,会到doInBackground()中
DownloadTask().execute()
}
4个重写方法说明:
-
onPreExecute()
这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框。 -
doInBackgroung(Params...)
方法中所有代码都会在子线程中进行,应该在这里去处理所有的耗时任务,任务一旦完成就可以通过return语句将执行结果返回,如果AsyncTask第三个泛型参数指定Unit,就可以不返回任务执行结果。注意这个方法中不可以进行UI操作,如需更新UI参数,可以调用publishProgress(Progress...)方法来完成。 -
onProgressUpdate(Progress...)
在后台任务中调用publishProgress()方法后,onPregressUpdate()方法很快就会被调用,该方法中参数就是在后台任务中传递过来的,在这个方法里可以对UI进行操作,利用参数中的数值对界面元素进行相应的更新。 -
onPostExecute(Result)
当后台任务执行完毕并通过return语句进行返回时,这个方法很快就会被调用,返回的数据会作为参数传递到此方法中,可以利用返回的数据进行一些UI操作比如说提醒任务执行的结果,以及关闭进度条对话框等。
四、在子线程中更新UI——runOnUiThread()
runOnUiThread()方法对异步消息处理机制进行了一层封装,它背后的工作原理二中的Handler、Message消息机制是一样的,借助这个方法就可以在线程中将数据更新到界面上:
private fun showResponse(response: String) {
runOnUiThread {
// 在这里进行UI操作,将结果显示到界面上行
}
}