Android多线程编程

我不是罗大锤 2021年11月23日 142次浏览

耗时操作需要放在子线程中运行,否则会导致主线程被阻塞,从而影响用户对软件的正常使用。

一、线程基本用法

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个部分组成,MessageHandlerMessageQueueLooper

  1. Message
    Message是在线程之前传递的消息,内部可以携带少量信息并在线程间传递,除了what字段,还可以使用arg1和arg2字段来携带一些整形数据,obj字段携带一个Object对象。

  2. Handler
    处理者,主要用于发送和处理消息,发送消息一般用sendMessage()或post(),发出的消息经过一系列辗转处理后最终传递到Handler的handleMessage()方法中。

  3. MessageQueue
    消息队列,用于存放所有通过Handler发送的消息,消息会一直存在于消息队列中等待被处理,每个线程中只有一个MessageQueue对象。

  4. Looper
    每个线程中的MessageQueue管家,调用Looper的loop()方法后,就会进入一个无限循环中,每当在MessageQueue中发现一条消息时,就将它取出并传递到Handler的handleMessage()方法中,每个线程中只有一个Looper对象。

Android多线程编程_1.jpg

三、在子线程中更新UI——AsyncTask

AsyncTask可能即将废弃。为了更方便在子线程中对UI进行操作,Android还提供了AsyncTask等一些好用的工具,借助AsyncTask即使对异步消息处理机制完全不了解,也可以十分简单的从子线程切换到主线程,AsyncTask实现原理也是基于异步消息处理机制的,Android帮我们做了很好的封装。

AsyncTask是一个抽象类,在继承时可以为AsyncTask指定3个泛型参数,这三个参数用途如下:

  1. Params
    在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。

  2. Progress
    后台执行任务时,如需在界面显示当前进度,使用这里指定的泛型作为进度单位。

  3. 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个重写方法说明:

  1. onPreExecute()
    这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框

  2. doInBackgroung(Params...)
    方法中所有代码都会在子线程中进行,应该在这里去处理所有的耗时任务,任务一旦完成就可以通过return语句将执行结果返回,如果AsyncTask第三个泛型参数指定Unit,就可以不返回任务执行结果。注意这个方法中不可以进行UI操作,如需更新UI参数,可以调用publishProgress(Progress...)方法来完成。

  3. onProgressUpdate(Progress...)
    在后台任务中调用publishProgress()方法后,onPregressUpdate()方法很快就会被调用,该方法中参数就是在后台任务中传递过来的,在这个方法里可以对UI进行操作,利用参数中的数值对界面元素进行相应的更新。

  4. onPostExecute(Result)
    当后台任务执行完毕并通过return语句进行返回时,这个方法很快就会被调用返回的数据会作为参数传递到此方法中,可以利用返回的数据进行一些UI操作比如说提醒任务执行的结果,以及关闭进度条对话框等。

四、在子线程中更新UI——runOnUiThread()

runOnUiThread()方法对异步消息处理机制进行了一层封装,它背后的工作原理二中的Handler、Message消息机制是一样的,借助这个方法就可以在线程中将数据更新到界面上:

private fun showResponse(response: String) {
    runOnUiThread {
        // 在这里进行UI操作,将结果显示到界面上行
    }
}