实践系列:浅析异步任务AsyncTask
前言:为了更好的理解Android系统组件的实现,学习中应以实践为主。本节内容将会实现
Handler
,Loop
,MessageQueue
的基础功能(具体实现与Android内部无关)。最后将通过自定义AsyncTask
理解线程切换的使用。注意:本节内容实践在 JVM 下实现,语言将采用 Kotlin ,对 Kotlin 语言 生理不适者 速速远离。
我随便写写,您随便看看
MessageQueue
MessageQueue
是该系统中最简单的一个组建,仅用于暂存放接收到的消息。
首先我们任意创建一个Kotlin文件(下文中代码若未说明,则都写在此文件中),该文件中用到的包仅为
import java.util.*
import kotlin.concurrent.thread
接下来为消息以及消息队列部分,代码较简单不再赘述。
//消息类型
private enum class MessageType {
//普通消息对象
MSG,
// Post消息
POST
}
/**
* 消息对象
* @param what 消息内容
* @param extra 消息附加数据
*/
open class Message(
val what: Int = 0,
val extra: Any? = null
)
// 为了区分消息类型而创建的消息对象
private class LoopMessage(
what: Int = 0,
extra: Any? = null,
val type: MessageType
) : Message(what, extra)
//消息队列
private class MessageQueue : LinkedList<LoopMessage>() {
//退出标记
private var mRun = true
fun exit() {
mRun = false
}
/**
* 该方法为阻塞方法,如果队列中没有数据将会一直阻塞下去
* 直到有消息到来或者循环退出。
*/
fun next(): LoopMessage? {
while (isEmpty() && mRun) Thread.sleep(1)
return if (mRun) removeFirst() else null
}
}
Loop
loop的含义是“使......成环”。做过Android开发应该知道Android程序的主线程在启动之时就已经开启了一个
MainLoop
,既然Loop会造成循环,为什么Android的主线程没有ANR呢?如果对这个问题不了解,请先看下面这篇博客。为什么Android程序中的Looper.loop()不会造成ANR异常
在开始创建Looper对象之前,我们需要明确一个需求:每个线程最多只能拥有一个Looper
对象。
也就是说在每一个线程中,都需要有一块线程独占的空间用来存放数据。在《操作系统》这门课中,我们学过一个经典的结论:进程是资源分配的基本单位,线程是调度的基本单位。但是这并不是意味着线程就完全没有自己的资源了。
在Java中,每一个线程都拥有的资源叫做线程栈,Java中的栈是与线程相关联的,每当创建一个线程是,JVM就会为这个线程创建一个对应的Java栈。每当线程中执行一个方法的时候,栈就会创建一个栈帧来存放方法相关的信息(变量、操作数、返回值等)。
在线程中有一块独特的区域,里面的数据是线程独占而不是共享
private val sThreadLocal = ThreadLocal<Looper>()
该数据结构将其中的数据和线程栈绑定在一起,只要是在同一个线程中,无论当前运行在哪一个栈帧中都能访问该数据,但不能访问其他栈存放的数据。
class Looper private constructor() {
//构造器使用private修饰表示程序员无法通过构造器创建该对象
companion object {
/**
* 准备操作,检查当前线程中的[Looper]对象是否已经创建
*/
fun prepare() {
if (sThreadLocal.get() != null)
throw RuntimeException("Only one Looper may be created per thread!")
sThreadLocal.set(Looper())
}
/**
* 开启循环是实现接收消息,直到调用[Looper.exit]退出
*/
fun loop() {
(sThreadLocal.get() ?: throw RuntimeException("Looper not exit on per thread")).loop()
}
/**
* 退出当前循环
*/
fun exit() {
(sThreadLocal.get() ?: throw RuntimeException("Looper not exit on per thread")).exit()
sThreadLocal.remove()
}
}
private val mQueue = MessageQueue()
private var mRun = true
private val mHandlerList = LinkedList<Handler>()
private fun loop() {
while (true) {
// 该方法是阻塞方法,如果没有收到消息将会一直阻塞下去
//如果消息为null表示当前正在退出
val loopMessage = mQueue.next() ?: break
when (loopMessage.type) {
MessageType.MSG -> { //普通消息对象,需要将消息分发给对应的接收者
val pair = loopMessage.extra as Pair<Handler, Message>
val nList = LinkedList(mHandlerList)
for (handler in nList) {
if (mRun && handler == pair.first) handler.handleMessage(pair.second)
}
}
MessageType.POST -> { //Post消息,不需要指明接收者,仅需在当前线程运行即可
@Suppress("UNCHECKED_CAST")
(loopMessage.extra as () -> Unit)()
}
}
}
}
//退出Loop循环
private fun exit() {
mRun = false
mHandlerList.clear()
mQueue.exit()
}
//将创建的Handler对象绑定到当前线程的Looper对象中
fun handlerRegister(handler: Handler) {
mHandlerList.add(handler)
}
//将普通消息封装后插入到消息队列
fun sendMessage(handler: Handler, msg: Message) {
mQueue.addLast(LoopMessage(type = MessageType.MSG, extra = handler to msg))
}
//将post消息封装中插入消息队列
fun post(action: () -> Unit) {
mQueue.addLast(LoopMessage(type = MessageType.POST, extra = action))
}
}
Handler
Handler的作用有两个——发送消息和处理消息,程序使用Handler发送消息,该消息将被发送到指定的MessageQueue中。
abstract class Handler {
private val looper = sThreadLocal.get()?.apply { handlerRegister(this@Handler) }
/**
* 由用户实现该接口用于接收跨线程的消息
*/
abstract fun handleMessage(msg: Message)
/**
* 该方法可以在任意线程中调用,之后该消息将被送往目标线程的消息队列
*/
fun sendMessage(msg: Message) = looper?.sendMessage(this, msg)
/**
* 该方法可以在任意线程中调用,之后函数将在目标线程中执行
*/
fun post(action: () -> Unit) = looper?.post(action)
}
fun handler(handle: (Message) -> Unit) = object : Handler() {
override fun handleMessage(msg: Message) = handle(msg)
}
AsyncTask
异步任务是Android中常见的实现任务不同模块在不同线程间切换的方法。虽然在实际开发中有许多更优方式,但是这种实现异步任务的思想还是相当重要的。
/**
* 异步任务
* @param Source 源数据类型
* @param Progress 进度回调数据类型
* @param Result 运算结果数据类型
*/
abstract class AsyncTask<Source, Progress, Result> {
companion object {
const val RESULT = 0
const val PROGRESS = 1
}
private val handler = handler { msg ->
when (msg.what) {
0 -> { // 这是结果回调
val result = msg.extra as Result
onPostExecute(result)
}
1 -> { // 这是进度回调
val progress = msg.extra as Progress
onProgress(progress)
}
}
}
/**
* 该方法执行于目标线程,用于执行任务
*/
abstract fun doInBackground(vararg params: Source, result: (Result) -> Unit)
/**
* 该方法回调于当前线程,在任务执行完成之后运行
* @param result 任务执行结果
*/
abstract fun onPostExecute(result: Result)
/**
* 该方法回调于当前线程,在任务执行之前运行
*/
open fun onPreExecute() {}
/**
* 该方法回调于当前线程,用于进度数据回调
*/
open fun onProgress(value: Progress) {}
/**
* 设置进度回调数据
*/
fun setProgress(value: Progress) {
handler.sendMessage(Message(what = PROGRESS, extra = value))
}
/**
* 执行该异步任务
*/
fun execute(vararg params: Source) {
onPreExecute()
thread {
doInBackground(*params) { result ->
handler.sendMessage(Message(what = RESULT, extra = result))
}
}
}
}
实战测试
为了验证上述实现的有效性,我们创建一个新的kotlin文件。
我们以主线程为目标,实现新线程下载文件并将下载进度与结果回调到主线程。
需要注意的是,接收消息的线程需要注册Looper而不是发送消息的线程。
注意:下列网络请求API来自Jdk9,如果环境低于9可以使用其他下载方式比如okHttp代替。
import jdk.incubator.http.HttpClient
import jdk.incubator.http.HttpRequest
import jdk.incubator.http.HttpResponse
import thread.AsyncTask
import thread.Looper
import java.io.File
import java.io.FileOutputStream
import java.net.URI
fun main(args: Array<String>) {
// 检查Looper对象状态
Looper.prepare()
//创建一个异步任务
val asyncTask = object : AsyncTask<String, Float, String>() {
override fun doInBackground(vararg params: String, result: (String) -> Unit) {
val client = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder()
.uri(URI(params[0]))
.GET()
.build()
//执行请求
client.send(request, HttpResponse.BodyHandler.asInputStream()).apply {
// 获取文件长度
headers().firstValueAsLong("Content-Length").ifPresent { length ->
val targetFile = FileOutputStream(File(params[1]))
val inputStream = body()
var i = 0
val bytes = ByteArray(1024)
var dSize = 0
while (inputStream.read(bytes).apply { i = this } > 0) {
dSize += i
targetFile.write(bytes, 0, i)
// 回调进度到主线程
setProgress(dSize.toFloat() / length)
}
// 将结果发送到主线程
result("over")
}
}
}
override fun onPostExecute(result: String) {
println(result)
Looper.exit() // 主线程退出循环
}
override fun onProgress(value: Float) {
println("current progress ${(value * 100).toInt()}%")
}
}
// 执行该异步任务
// 第一个参数为图片url地址
// 第二个参数是图片保存到本机的地址
asyncTask.execute(
"http://img4q.duitang.com/uploads/item/201303/15/20130315223944_EvRW3.thumb.700_0.jpeg",
"E:\\test.jpeg"
)
//主线程开始循环接收消息
Looper.loop()
}
充分利用Kotlin的Lambda优势
在以上请求不变的情况下,利用Kotlin的函数式编程,抛弃AsyncTack的固定写法,使得程序具有更高的灵活性
private var mainHandler: Handler? = null
fun runOnMainThread(action: () -> Unit) = mainHandler?.post(action)
fun main(args: Array<String>) {
// 检查Looper对象状态
Looper.prepare()
mainHandler = handler { }
onStart()
//主线程开始循环接收消息
Looper.loop()
}
fun onStart() {
thread {
val client = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder()
.uri(URI("http://img4q.duitang.com/uploads/item/201303/15/20130315223944_EvRW3.thumb.700_0.jpeg"))
.GET()
.build()
//执行请求
client.send(request, HttpResponse.BodyHandler.asInputStream()).apply {
// 获取文件长度
headers().firstValueAsLong("Content-Length").ifPresent { length ->
val targetFile = FileOutputStream(File("E:\\test.jpeg"))
val inputStream = body()
var i = 0
val bytes = ByteArray(1024)
var dSize = 0
while (inputStream.read(bytes).apply { i = this } > 0) {
dSize += i
targetFile.write(bytes, 0, i)
// 回调进度到主线程
runOnMainThread {
println("current progress ${(dSize * 100F / length).toInt()}%")
}
}
// 将结果发送到主线程
runOnMainThread {
println("over")
Looper.exit() // 主线程退出循环
}
}
}
}
}
网友评论