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()提交给他去运转,以此完成回调到主线程。