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接纳两个参数:
- 异常情况下的exceptionHandler,默认值为null
- 需要在异步环境下运转的函数(代码块),这里的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>)
-
在函数参数悉数引入后,doAsync首要创立了一个AnkoAsyncContext类,这个类内部仅包括一个对于弱引证变量,弱引证的对象是调用doAsync的,存储在context内。弱引证能够用来检索对象的强引证,或许用来判别对象是否以及被内存管理器GC收回。
-
通过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()
- uiThread首要查看当时的AnkoAsyncContext()绑定的弱引证是否还在,假如不在了说明调用这个doAsync的线程已经被GC收回了,便return false,不继续运转后续内容了。
- 然后它进行一个判别,假如当时所在的就是主线程,那么直接运转task,否则咱们通过主线程handler的post办法来提交这个task运转。
总结
咱们再回过头看调用doAsync的全过程
doAsync{
task()
uiThread{
handleUI()
}
}
- doAsync()办法首要将当时运转的Context进行弱引证存储起来,用于今后感知这个Context仍是否存在。
- 随后,他查看内部Executor单例类,来查看ExecutorService线程池是否被树立,假如没被树立,那就创立一个newScheduledExecutorThreadPool,线程数量为当时可用processors的两倍。
- 假如线程池创立结束,或是已创立,那么他就将task()提交给线程池运转。
- 来到uiThread(),它首要查看咱们之前绑定的Context是否还在,假如不在说明已经被GC收回了,那咱们就不继续运转后续内容,直接返回。
- 假如Context还在,uiThread()便查看,当时是不是在主线程,假如在,就直接运转handleUI() (假定咱们在主线程里调用了uiThread(),那么确实没必要再交给handler去处理这条指令了)。假如不在,那么咱们就找到主线程的Handler,并通过post(),把handleUI()提交给他去运转,以此完成回调到主线程。