Kotlin Anko库的异步解决方案 doAsync与uiThread的实现理解

1,258 阅读4分钟

Android项目过程中,看到了Anko库的异步框架doAsync()与uiThread()方法,然而Anko库已经被废弃了,目前来说Kotlin协程会是更好的异步解决方案,但我作为初学者还是去了解了一下其实现原理和源码,如有错误理解,欢迎批评指正。

a. doAsync是如何使用的?

doAsync{
	运行耗时的后台任务
	uiThread{
	需要在主线程执行的任务
	}
}

这里的doAsync{...},我们实际上调用的是 doAsync扩展函数,其源代码如下

fun <T> T.doAsync(
        exceptionHandler: ((Throwable) -> Unit)? = null,
        task: AnkoAsyncContext<T>.() -> Unit
): Future<Unit> {
    val context = AnkoAsyncContext(WeakReference(this))
    return BackgroundExecutor.submit {
        try {
            context.task()
        } catch (thr: Throwable) {
            exceptionHandler?.invoke(thr) ?: Unit
        }
    }
}

doAsync接收两个参数:

  1. 异常情况下的exceptionHandler,默认值为null
  2. 需要在异步环境下运行的函数(代码块),这里的AnkoAsyncContext的实际用途后续会说到。

doAsync的返回类型是Future

Future类在Kotlin官方文档中的解释如下,即,一个用于表达抽象计算的类,其结果可能在未来变得可获取。

Class representing abstract computation, whose result may become available in the future.

如上代码块中,doAsync没有括号内的task参数,这是因为kotlin中,如果最后一个参数为函数类型,那么我们就可以在括号中省略,并用花括号的形式表达出来,所以它等同于:

doAsync(exceptionHandler=null,
	task={...}//code block we need to run.
)

b. doAsync的本质是什么?

我们继续看它的源码:

fun <T> T.doAsync(
        exceptionHandler: ((Throwable) -> Unit)? = null,
        task: AnkoAsyncContext<T>.() -> Unit
): Future<Unit> {
    val context = AnkoAsyncContext(WeakReference(this))
    return BackgroundExecutor.submit {
        try {
            context.task()
        } catch (thr: Throwable) {
            exceptionHandler?.invoke(thr) ?: Unit
        }
    }
}

class AnkoAsyncContext<T>(val weakRef: WeakReference<T>)
  1. 在函数参数全部引入后,doAsync首先创建了一个AnkoAsyncContext类,这个类内部仅包含一个对于弱引用变量,弱引用的对象是调用doAsync的,存储在context内。弱引用可以用来检索对象的强引用,或者用来判断对象是否以及被内存管理器GC回收。

  2. 通过BackgroundExecutor.submit来提交我们的context.task(),接下来我们观察这其中发生了什么:

    • BackgroundExecutor是什么?
    internal object BackgroundExecutor {
        var executor: ExecutorService =
            Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors())
    
        fun <T> submit(task: () -> T): Future<T> {
            return executor.submit(task)
        }
    }
    

    根据如上代码,BackgroundExecutor是定义在Async.kt内部的单例类,其内部的唯一变量executor在每次该类初始化的时候便由Executors创建了新的线程池,线程池的类型为newScheduledThreadPool,也就是延迟连接池,可以在给定的延迟时间后执行命令,数量为avilableProcessors()的二倍。

    假设,我们不想要使用内部提供的BackgroundExecutor,也可以自己定义executorService作为运算的线程池,这一方法重载doAsync提供给了我们。

    fun <T> T.doAsync(
            exceptionHandler: ((Throwable) -> Unit)? = null,
            executorService: ExecutorService, //放入我们自定义的线程池
            task: AnkoAsyncContext<T>.() -> Unit
    ): Future<Unit>
    

    submit函数则接收一个函数参数task,并交由executor提交。

c. uiThread是怎么实现的?

uiThread()方法嵌套在doAsync内部实现回到UI主线程进行运算,其方法源码如下:

/**
 * Execute [f] on the application UI thread.
 * [async] receiver will be passed to [f].
 * If the receiver does not exist anymore (it was collected by GC), [f] will not be executed.
 */
fun <T> AnkoAsyncContext<T>.uiThread(f: (T) -> Unit): Boolean {
    val ref = weakRef.get() ?: return false
    if (ContextHelper.mainThread == Thread.currentThread()) {
        f(ref)
    } else {
        ContextHelper.handler.post { f(ref) }
    }
    return true
}

uiThread()方法运行在doAsync()方法内部,此时的上下文Context是我们先前看到的,只包含一个当前线程的弱引用AnkoAsyncContext。

  val context = AnkoAsyncContext(WeakReference(this))
  return BackgroundExecutor.submit {
      try {
          context.task()
  1. uiThread首先查看当前的AnkoAsyncContext()绑定的弱引用是否还在,如果不在了说明调用这个doAsync的线程已经被GC回收了,便return false,不继续运行后续内容了。
  2. 然后它进行一个判断,如果当前所在的就是主线程,那么直接运行task,否则我们通过主线程handler的post方法来提交这个task运行。

总结

我们再回过头看调用doAsync的全过程

doAsync{
	task()
	uiThread{
	handleUI()
	}
}
  1. doAsync()方法首先将当前运行的Context进行弱引用存储起来,用于以后感知这个Context还是否存在。
  2. 随后,他检查内部Executor单例类,来查看ExecutorService线程池是否被建立,如果没被建立,那就创建一个newScheduledExecutorThreadPool,线程数量为当前可用processors的两倍。
  3. 如果线程池创建完毕,或是已创建,那么他就将task()提交给线程池运行。
  4. 来到uiThread(),它首先检查我们之前绑定的Context是否还在,如果不在说明已经被GC回收了,那我们就不继续运行后续内容,直接返回。
  5. 如果Context还在,uiThread()便检查,当前是不是在主线程,如果在,就直接运行handleUI() (假设我们在主线程里调用了uiThread(),那么确实没必要再交给handler去处理这条命令了)。如果不在,那么我们就找到主线程的Handler,并通过post(),把handleUI()提交给他去运行,以此实现回调到主线程。