Android 的多线程编程与 Java 多线程编程基本是使用相同的语法,比如定义一个线程只需要新建一个类继承自 Thread,重写父类的 run() 方法
class MyThread : Thread() {
override fun run() {
// 编写具体的逻辑
}
}
启动这个线程也很简单,创建 MyThread 的实例,调用 start() 方法,这样 run() 方法中的代码就会在子线程中运行了
MyThread().start()
当然,使用继承的方式耦合性有点高,可以选择实现 Runnable 接口的方式定义一个线程
class MyThread : Runnable {
override fun run() {
// 编写具体的逻辑
}
}
如果使用这种写法,启动线程的方式也需要进行相应的改变
val myThread = MyThread()
Thread(myThread).start()
也可以使用 Lambda 的方式
Thread {
// 编写具体的逻辑
}.start()
以上几种方式相比都不陌生,Kotlin 还给我们提供了更简单的开启线程的方式,我们只需要在 Lambda 表达式中编写具体的逻辑即可,连 start() 方法都不用调用
thread {
// 编写具体的逻辑
}
Android 的 UI 是线程不安全的,如果想更新应用程序里的 UI 元素,必须在主线程中进行,否则会出现异常。但有些时候,我们必须在子线程执行一些耗时任务,然后根据任务的执行结果更新 UI,这时可以使用 Android 提供的一套异步消息处理机制解决
class MainActivity : AppCompatActivity() {
// 定义一个整型变量 updateText,用于表示更新 UI 动作
val updateText = 1
val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
// 在这里进行 UI 操作
when (msg.what) {
// 更新 UI
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
thread {
// 创建一个 Message 对象
val msg = Message()
// 指定 what 值为 updateText
msg.what = updateText
// 将这条 msg 发送给 handler
handler.sendMessage(msg)
}
}
}
}
下面对 Android 的异步机制做简要介绍,主要由四个部分组成:
Message
Message 是在线程之间传递的消息,可以在内部携带少量的信息,用于不同线程之间传递数据。比如上述代码使用了 Message 的 what 字段,除此之外还可以使用 arg1 和 arg2 字段携带一些整型数据,使用 obj 字段携带一个 Object 对象
Handler
处理者,主要用于发送和处理消息。发送消息一般使用 sendMessage() 方法、post() 方法等,而发出的消息经过一系列地辗转处理后,最终会传递到 Handler 的 handlerMessage() 方法中
MessageQueue
消息队列,主要用于存放所有通过 Handler 发送的消息,这部分消息会一直存在于消息队列中,等待被处理,每个线程中只会有一个 MessageQueue 对象
Looper
Looper 是每个线程中 MessageQueue 的管家,调用 Looper 的 loop() 方法后,就会进入一个无限循环中,然后每当发现 MessageQueue 中存在一条消息时,就会将它取出,并传递到 Handler 的 handlerMessage() 方法中,每个线程只会有一个 Looper 对象
现在再来把异步消息处理的流程梳理一下:首先需要在主线程中创建一个 Handler 对象,并重写 handlerMessage() 方法。然后子线程创建一个 Message 对象,并通过 Handler 将这条消息发送出去。之后这条消息会被添加到 MessageQueue 的队列等待被处理。Looper 会一直尝试从 MessageQueue 中取出待处理消息,由 handleMessage() 方法处理
AsyncTask 的实现原理也是基于异步消息处理机制,只是 Android 帮我们做了更好的封装
AsyncTask 是一个抽象类,使用时必须创建一个子类去继承它,在继承时我们可以为 AsyncTask 类指定三个泛型参数:
一个最简单的自定义 AsyncTask 可以写成如下形式:
class DownloadTask : AsyncTask<Unit, Int, Boolean>() {
...
}
这里我们把 AsyncTask 的第一个泛型参数指定为 Unit,表示在执行 AsyncTask 时不需要传入参数给后台任务。第二个泛型参数指定为 Int,表示使用整型数据作为进度显示单位。第三个泛型参数指定为 Boolean,表示使用布尔型数据反馈执行结果
AsyncTask 经常需要重写的方法有以下四个:
onPreExecute()
这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框
doInBackgroound(Params…)
这个方法中的所有代码都会在子线程中运行,应该在这里处理所有耗时任务,任务一旦完成,就可以通过 return 语句返回结果。如果需要更新 UI,比如反馈当前任务的执行进度,可以调用 publishProgress(Progress…) 方法来完成
onProgressUpdate(Progress…)
当调用了 publishProgress(Progress…) 方法后,该方法就会被调用,携带的参数就是后台任务中传递过来的,在这里可以对 UI 进行操作
onPostExecute(Result)
当后台任务执行完毕并通过 return 语句返回时,这个方法很快就会被调用,返回的数据会作为参数传递到此方法中,可以利用返回的数据进行一些 UI 操作
因此,一个完整的自定义 AsyncTask 就可以写成如下形式:
class DownloadTask : AsyncTask<Unit, Int, Boolean>() {
override fun onPreExecute() {
// 显示进度对话框
progressDialog.show()
}
override fun doInBackground(vararg params: Unit?) = try {
while (true) {
// 这是一个虚构的方法
val downloadPercent = doDownload()
publishProgress(downloadPercent)
if (downloadPercent >= 100) {
break
}
}
true
} catch (e: Exception) {
false
}
override fun onProgressUpdate(vararg values: Int?) {
// 在这里更新下载进度
progressDialog.setMessage("Download ${
values[0]}%")
}
override fun onPostExecute(result: Boolean?) {
// 关闭进度对话框
progressDialog.dismiss()
// 在这里提示下载结果
if (result) {
Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "Download failed", Toast.LENGTH_SHORT).show()
}
}
}
想要启动这个任务,只需要以下代码即可
DownloadTask().execute()