协程是什么
协程的概念其实很早之前就有了,并非Kotlin提出来的。百度一下发现有很多的所谓比较正式的定义,但是理解起来我骂娘。最多的是那协程与线程做比对的,得出的结论大多是协程和线程一样是用来实现异步任务的,异步也就是不阻塞当前的线程,可是线程都是顺序执行的,这就导致一个矛盾的问题出现,所以你查到的文章写得其实都不是真确的!其实协程最关键的知识就是调度器,调度器可以启动一个协程,调度器的原理知识我们现在可以不用去考虑。那么协程与线程线程和异步有什么关系呢?答案是毛关系。协程之所以可以实现异步的功能是因为调度器底层使用线程来运行我们的任务并且通过接口回调的方式为我们返回结果。
阻塞函数
调度器只能调用阻塞函数,而阻塞函数只能被阻塞函数或者调度器调用。一个正常的函数前面加上一个suspend关键字就是阻塞函数,其余的地方和普通函数一样。
调度器
Android中系统已经为我们实现了两个协程调度器,分别是launch和async,他们除了返回值不一样其余都是类似的,分别返回Job和Deferred,默认调度器都是在子线程中运行我们的任务也就是默认是异步的。
协程的使用
注意:需要在APP中的gradle文件添加协程依赖库,如:
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.19.2'
一般我们使用协程都是实现一个异步的任务,如简单的加载一个图片:
private lateinit var def: Deferred<Unit>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
def = async {
setImg()
}
}
suspend fun setImg() {
val http = URL(IMGURL).openConnection()
val bm = BitmapFactory.decodeStream(http.inputStream)
runOnUiThread {
imgv.setImageBitmap(bm)
}
}
这里我们定义了一个阻塞方法setImg(),然后在里面加载图片,使用async调用我们的阻塞方法来启动一个协程。调用async会返回一个Deferred对象,我们可以在ondestory的时候来取消我们的协程,如下:
override fun onDestroy() {
super.onDestroy()
Log.e("kk", "${def.cancel()}")//销毁activity时取消掉协程
}
cancel方法会返回一个Boolean类型的值,true代表成功,false代表其它情况,如协程已经执行完毕。来看下运行效果:
其实async方法有3个参数,因为使用了默认参数值得原因,所以我们可以不需要指定任何的东西。函数原型如下:
public fun <T> async(
context: CoroutineContext = DefaultDispatcher,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
其中context就是指定我们的协程在哪个线程中执行的,默认是在子线程中:
public val DefaultDispatcher: CoroutineDispatcher = CommonPool
我们也可以设置为在调用的线程中执行,只需要传递context的参数为Unconfined,如下:
def = async(Unconfined){
setImg()
}
下面用几行代码来实现一个倒计时的功能,上代码:
class MainActivity : AppCompatActivity() {
private lateinit var def: Deferred<Unit>
private var count = 10
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
def = async(Unconfined) {
timer()
}
}
private suspend tailrec fun timer() {//使用尾递归,实现一个倒计时
Log.e("kk", "${Thread.currentThread().name}")
if (--count != 0) {
Snackbar.make(imgv, "$count", Snackbar.LENGTH_SHORT).show()
delay(1000)
timer()
} else {
Snackbar.make(imgv, "时间到!", Snackbar.LENGTH_LONG).show()
}
}
override fun onDestroy() {
super.onDestroy()
Log.e("kk", "${def.cancel()}")//销毁activity时取消掉协程
}
}
看关键的函数timer,这个函数使用了tailrec来修饰,是一个尾递归函数,其实和普通函数一样,就是如果符合尾递归规则,编译器在编译函数的时候会优化函数的调用,也就是使用for循环来实现函数的调用避免栈溢出。函数内部使用了一个delay函数,这个函数的作用就是挂起协程,也就是相当于线程中的wait调用。这样几行代码就实现了一个倒计时功能,而且稳入泰山,不会报任何莫名其妙的exception。运行效果如下:
总结
协程在Kotlin中还处于试验功能,所以还没有加入到默认库中,我们使用也是添加依赖来用的。刚开始接触到这个概念的时候看了一大波资料,也有官方的文档,但是总觉得写得不清楚,所以学会之后有了这篇文章。等有时间再顺一遍底层的逻辑,学学其设计的思路。做开发这么久,发现其实编程最难的是思维方式,思维的高度决定了我们能力的高低。最后说一句,协程不难,just do it!.
欢迎关注微信公众号“android教科书”,最新最好的文章第一时间送到手!扫描下面的二维码即可: