Kotlin

实践系列:浅析异步任务AsyncTask

2019-02-03  本文已影响6人  798fe2ac8685

实践系列:浅析异步任务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() // 主线程退出循环
                }
            }
        }
    }
}

上一篇下一篇

猜你喜欢

热点阅读