美文网首页Kotlin
实践系列:浅析异步任务AsyncTask

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

作者: 798fe2ac8685 | 来源:发表于2019-02-03 22:18 被阅读6次

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

    相关文章

      网友评论

        本文标题:实践系列:浅析异步任务AsyncTask

        本文链接:https://www.haomeiwen.com/subject/boyusqtx.html