定义一个线程,只需要新建一个类,继承自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()
我们创建的myThread是MyThread实例,它是一个实现了Runnable接口的对象,所以我们可以直接把他传入Thread的构造函数中。
接着我们再调用Thread的start方法,run方法中的内容就开始在子线程中运行了。
如果我们不想专门定义一个类去实现runnable接口,那可以使用lambda,这种更为常见:
Thread {
//具体逻辑
}.start()
kotlin中给我们提供了更简单的启动方式:
thread {
//具体逻辑
}
Android的ui也是线程不安全的,如果想要更新ui,必须在主线程中进行,否则会出现异常。
但是有的时候,我们必须在子线程中执行一些耗时任务,然后根据这些任务的执行结果来更新ui。
对此,Android提供了一套异步消息处理机制。它主要由4个部分组成:
Message | 线程之间传递的消息 |
Handler | 用于发送和处理消息 |
MessageQueue | 消息队列,存放所有通过Handler发送的消息。每个线程中只会有一个MessageQueue。 |
Looper | 每个线程中MessageQueue的管家,调用Looper的loop方法后,会进入一个无限循环中。每当发现MessageQueue中存在一个Message时,就会将它取出并传入到Handler中。每个线程只有一个Looper。 |
了解了异步消息处理机制后,我们把整一个处理流程走一遍。
而一条message经过图示中的处理后,也就从子线程进入了主线程,从不能更新ui变成了可以。
为了更方便在子线程操作ui,android提供了AsyncTask,即使对异步消息处理机制完全不了解,也可以十分简单的使用。
首先AsyncTask是一个抽象类,所以我们想要使用他,就必须用一个类去继承他。在继承时我们可以为AsyncTask提供3个泛型参数:
Params | 执行AsyncTask时需要传入的参数,可用于在后台任务中使用 |
Progress |
在后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位
|
Result |
当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型
|
因此,一个自定义AsyncTask可以写成如下形式:
class DownloadTask : AsyncTask() {
...
}
此处我们传入了3个泛型参数,第一个指定为Unit,表示在执行AsyncTask的时候不需要传入参数给后台任务;第二个指定为Int,表示使用整型来作为进度展示单位;第三个指定为Boolean,表示使用它来反馈执行结果。
目前我们自定义的DownloadTask还是一个空任务,我们需要重写AsyncTask的方法,经常需要重写的有4个:
onPreExecute() | 在后台任务开始执行之前调用,用于进行一些初始化操作 |
doInBackground(Params...) | 这个方法中的所有代码都会在子线程中运行,因此我们把所有耗时任务都放在此处。 任务一旦完成,return回执行结果。如果AsyncTask的第三个参数是Unit,可以不返回执行结果。 因为是在此执行子线程操作,所以不能进行ui操作,如果需要更新ui,可以调用publishProgress(Progress...)方法来完成 |
onProgressUpdate(Progress...) | 当在后台任务中调用了调用publishProgress(Progress...)方法,该方法将会很快被调用,而该方法中携带的参数,就是从后台任务中传递过来的。 我们在这个方法中可以进行ui操作,利用参数中的值就可以对ui更新。 |
onPostExecute(Result) | 当后台任务执行完毕并return时,该方法很快会被调用。返回的数据会传入该方法中,可以利用这些数据进行ui操作。 |