我们可以把安卓中的线程笼统的分为UI线程和工作线程,UI线程主要是用来更新UI界面,工作线程主要用来处理耗时的逻辑。
线程的创建
new Thread
最基本的创建线程的方式,这种方式创建一个线程非常简单,不过现实开发中我们一般不会使用这种方式,因为它缺乏统一等管理,可能会创建太多的线程这些线程之间互相竞争,可能导致系统资源因为被占用太多而卡死。
AsyncTask
安卓系统提供的轻量级线程工具(使用场景:需要知道任务等执行进度,多个任务串行执行)
缺点:默认情况下多个线程是顺序执行的(如果某个任务过于耗时会导致后面的任务迟迟得不到执行)、生命周期跟宿主不同步,可能会造成内存泄漏。
我们可以使用asyncTask.executeOnExecutor()方法传递一个线程池过去,让其有并发执行的能力。
从安卓3.0出现AsyncTask陪我们安卓开发者走过了很长的时间,不过谷歌一直在发展,最新版本的sdk中 AsyncTask 已经被标记为废弃了。
HandlerThread
具备线程间通讯和任务持续运行的能力,使用场景比如轮训、所有任务串行执行、比如主线程需要和工作线程通信。
缺点: 它不会向普通线程一样,任务完成就自己销毁,它会一直运行,所以处理不当可能会造成内存泄漏。所以在必要的时候我们需要手动调用它的quit或者quitSafeley方法来关闭它。
IntentService
继承自Service,可以已service的方式运行在后台的线程
适用场景 需要跨页面读取任务进度比如后台长传图片、后台操作数据库
任务完成之后会自行停止不需要手动stopService
ThreadPoolExecutor
使用线程池来创建线程是企业级开发中使用的最多的一种方式 安卓系统内部提供了下面的几个线程池创建方式,不过一般我们都会使用自己创建的线程池灵活好管理,等下面在说。
Executors.newCachedThreadPool();//线程可复用的线程池
Executors.newSingleThreadExecutor();//线程数量为1的线程池
Executors.newFixedThreadPool(3);//固定数量的线程池
Executors.newScheduledThreadPool(3);//可指定定时任务的线程池
无论我们用哪种方式创建一个线程,创建完成之后都不是立即得到执行的,因为每个线程都有优先级,线程的优先级越高越早得到执行
线程的优先级
安卓中提供了两种方式来给线程设置优先级:
- java.lang.Thread.setPriority(int newPriority)
- android.os.Procress.setThreadPriority(int newPriority)
第一种是Java语言本身提供的设置线程优先级的方式,设置的值为1~10,值越高线程的优先级越高,UI线程的优先级是5 ,使用这种方式设置的线程优先级在安卓上面表现并不明显。
第二种是安卓中特有的设置线程优先级的方式,主要是为了保证UI线程能更容易获取CPU的时间片 该值的取值范围是 -20~19 值越低优先级越高,UI线程的优先级是 -10。这种方式在安卓上使用效果比较明显。
线程的优先级有继承的特性,比如我们在主线程中设置了一个优先级,那么从主线程中创建出来的别的线程的优先级跟主线程的优先级式一样的,所以假如我们创建一个耗时任务,这时候需要给它设置一个低优先级,否则它就跟主线程优先级一样了会抢主线程的时间片。
线程的状态切换
线程主要有下面的几种状态
状态 | 描述 |
---|---|
NEW | 初始状态,线程刚被创建 还没有执行start方法 |
RUNNABLE | 运行状态 运行中和就绪可执行都是运行状态 |
BLOCKED | 阻塞状态 被线程锁阻塞 |
WAITING | 等待状态 需要其他线程通知唤醒 |
TIME_WAITING | 超时等待状态 可以在指定时间之后自行返回 |
TERMINATED | 终止状态 当前线程已经执行完毕 |
- 当我们新建了一个线程的时候状态是NEW
- 当我们调用了其start方法之后,状态变为RUNNABLE。
- 当线程执行完毕或者发生了异常 状态变为 TERMINATED
- 当我们调用了object的wait()方法或者其他线程的thread.join方法,当前线程进入WAITING状态
- 当我们调用wait(3) 、join(3) 等方法的时候传递了等待的时常,那么当前线程进入TIME_TERMINATED状态
- 当线程在执行过程中遇到同步锁比如 synchronized关键字获取不到锁就会进入BLOCK状态
可以改变线程状态的方法
- sheep:让线程进入休眠状态 正常会释放资源锁 如果sleep在synchronized代码块中则不会释放
- yeild:暂停当前线程,让同级别或者更高优先级的线程执行 不会释放资源锁
- join:等待目标线程执行完后在执行当前线程
- wait:进入等待池,释放资源对象锁 可使用notify或者notifyAll方法唤醒
线程间的通信
- 子线程向主线程发现消息
- 主线程向子线程发送消息
子线程向主线程发送消息我们都知道 直接在主线程创建handler 然后从子线程发送消息即可
主线程如何向子线程发送消息呢,这时候需要子线程要有处理消息的能力,也就是其内部需要有消息队列并且开启了looper消息循环,所以我们需要在子线程中调用Looper.prepare() 和Looper.loop() 方法创建Looper并开启无限循环。在创建handler的时候将该looper对象发传进去,这样通过该handler发送的消息就是发送到该子线程中处理了。具体详细实现可以查看HandlerThread的源码。
线程安全
线程安全的本质:当线程并发执行的时候,同一时间只能有一个线程访问同步资源,线程的执行结果对其他线程可见
保证线程安全的方式一般有下面的几个
- synchronized
- ReentrantLock
- AtomicInteger、AtomicBoolean等原子类
下面从不同的角度来看一下线程安全
根据线程要不要锁住同步资源 分为悲观锁和乐观锁
synchronized和ReetrantLock,他俩认为这地方肯定会发生线程安全的问题,所以直接先把同步资源锁住,这称为悲观锁。
AtomicInteger、AtomicBoolean就是乐观锁先不锁住同步资源,在用的时候去对比
根据锁住的同步资源要不要阻塞 分为阻塞锁和自旋锁
synchronized和ReetrantLock 就是阻塞锁,当一个线程占用了资源之后,后面来的线程会进入阻塞状态等待该线程执行完毕,该线程执行完自后会发送信号,后面的线程收到信号后退出阻塞状态
AtomicInteger、AtomicBoolean等原子类是自旋锁 ,当一个线程占用了资源的时候,后面来的线程会一直while循环判断该资源是否被释放,直到前面的线程释放为止
根据获取资源的时候要不要排队 分为公平锁和非公平锁
ReetrantLock可以实现公平锁
ReetrantLock和synchronized可以实现非公平锁
根据一个线程中的多个流能不能获取同一把锁 分为 可重入锁和非可重入锁
synchronized和ReetrantLock都可以作为可重入锁
不可重入锁目前没有
根据多个线程是否可以共享一把锁 分为 共享锁和排他锁
共享锁 : ReadLock
排他锁: WriteLock
synchronized 在JDK1.6的时候被优化过,它的锁会从无锁到 偏向锁 到轻量级锁 到重量级锁慢慢自动升级
ReetrantLock和synchronized实现锁的原理:比如A、B、C三个线程竞争一个同步资源,A获取到锁之后,B线程再过来获取看到锁已经被别的线程用着呢,就会阻塞等待,直到A线程用完之后释放锁,B在跟C去竞争该锁的使用权,谁先拿到谁先用,另一个继续进入阻塞状态。
AtomicInteger、AtomicBoolean等原子类的原理:比如线程A拿到内存中的同步资源之后执行自己的逻辑,执行完之后在往回写的时候,看看内存中的数据有没有被别的线程修改,如果没有就将自己的结果写入,如果被修改了,就重新拿到新的值在执行自己的逻辑直到成功写入到内存中
AtomicInteger等原子类适用于多线程计数,并发量小的场景,使用方式很简单比如:
AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.getAndIncrement(); //增加
atomicInteger.getAndDecrement(); //减少
关键字同步
voilate
修饰成员变量的可见性 ,使用violate修饰的变量每次在被线程访问的时候,会强制从共享内存读取最新的值,当成员变量的值发生变化的时候,强制将结果写回到内存中。
violate只能保证原子操作的线程安全,对于非原子操作的 比如 count++
这种是无法保证线程安全的,因为count++内部有好几步操作:首先需要先拿到count当前的值,然后对该值进行+1,最后把+1后的值赋值给count在写入到内存中。
synchronized
可以锁Java对象、Java类、代码块
synchronized void method(){} //锁方法 没有获取到对象锁的其他线程都无法访问该方法
static synchronized void method(){}//锁class 不同的Java实例也需要排队执行 因为jvm中class只有一个
void method(){//锁代码块 当A线程进入到代码块中当时候,其他线程可以访问代码块外面的代码
//...其他代码
synchronized (this){}
}
优点:就算我们的同步方法出现了异常,JVM也会自动帮我们释放锁,不会出现死锁的情况
缺点:不能中途释放锁、不能中断一个正在获取锁的线程,只能等待有锁的线程执行完成或者异常退出;不够灵活,在多个线程竞争锁的时候我们不知道是否获取成功;每个锁只有单一的条件不能设置超时。
ReentrantLock 功能很多:悲观锁、可重入锁、公平锁、非公平锁
基本使用
ReentrantLock lock = new ReentrantLock(true/false);
try {
lock.lock();
...
}finally {
lock.unlock();
}
一定要在finally中手动释放锁 否则不会自动释放锁
构造方法中的true和false 代表是否是公平锁,ReentrantLock默认是非公平锁
- 公平锁:所有进入阻塞的线程依次排队
- 非公平锁:允许线程插队,当一个新的线程过来的时候允许它立即尝试获取锁,这样就避免了线程一直阻塞唤醒阻塞唤醒 性能高。不过也有可能导致某个线程一直被插队得不到执行,不过为了性能这点还是可以忍受的
一些重要方法
lock.lock(); //获取不到锁会阻塞
lock.tryLock(); //获取到锁会返回true
lock.tryLock(1000, TimeUnit.MILLISECONDS); //在一定的时间内不断的尝试去获取锁
lock.lockInterruptibly();//可以使用 Thread.interrupted() 中断线程退出锁的竞争
进阶使用
static class ReentrantLockTask{
ReentrantLock lock = new ReentrantLock(true);
Condition condition1;
Condition condition2;
ReentrantLockTask(){
condition1 = lock.newCondition();
condition2 = lock.newCondition();
}
void method1(){
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void method2(){
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void notifyWho(){
condition1.signal();
}
}
使用ReentrantLock的Condition 可以精准的控制一个线程的等待与唤醒,比如两个线程交替工作的场景,可以使用Condition来控制
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
reentrantReadWriteLock.readLock();
reentrantReadWriteLock.writeLock();
ReentrantReadWriteLock中的readLock可以实现并发读的操作,比如在线文档查看,ReentrantReadWriteLock中的writeLock可以实现线程安全的写 比如在线文档编辑。
如何正确的使用锁:
大多数情况下 我们不需要知道锁的一些细节,直接使用synchronized就可以了,JDK1.6优化以后它的性能已经很好了
如果我们需要知道锁的细节 比如锁是否获取成功 或者自己决定什么时候释放锁 那就使用ReentrentLcok
多使用给代码块加锁的方式,比如下面的方式,只给用到锁的地方加锁,其他代码还可被多个线程访问,效率就会高一些
void method(){
//...其他代码
synchronized (this){}
//...其他代码
}
将读锁和写锁分离,大多数情况下读的比较多,而读是不需要加锁的,写的时候少,只给写加锁就可以了。
线程池
为什么要使用线程池呢?
- 通过复用已经创建的线程,来降低线程创建和销毁造成的性能损耗
- 提高响应速度,不必等地啊线程的创建可以立即执行
- 提高线程的可管理性
线程池的构造方法
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2, 100, 10, TimeUnit.SECONDS,
new PriorityBlockingQueue<>(),
r -> new Thread("threadName"),
(r, executor) -> {
}
);
threadPoolExecutor.execute(() -> {
});
线程池创建的时候会传入好多参数,这些参数是啥意思呢,下面顺序来说
线程池的常用方法:
execute(Runnable command)// 提交任务交给线程池执行
void shutdown() //关闭线程池 等待任务执行完成
List<Runnable> shutdownNow() //关闭线程池 不等待任务执行完成
long getTaskCount() //获取线程池中所有任务的数量
long getCompletedTaskCount() //返回线程池中已执行完的线程的数量
int getPoolSize() //获取线程池中已创建的线程的数量
int getActiveCount() //获取当前线程池中正在运行的线程的数量
execute方法的执行流程:
提交一个Runnable,如果已经创建的线程大于核心线程,就判断等待队列是否满了,如果没满就放入等待队列中,如果满了就创建非核心线程。如果已经创建的线程已经大于了能创建的最大线程的数量,就执行拒绝策略
如果已经创建的线程小于核心线程或者小于最大线程,这时候就把提交的Runnable放入线程中去执行。首先检查线程池的状态是否创建新的线程,然后将Runnable封装成Worker对象,添加到工作队列,启动线程,在run方法中执行runWorker方法,runWorker中开始while循环执行本次任务,任务结束后继续去队列中取任务并执行达到复用的目的。
最开始的时候我们说啦,企业开发中一般不会使用系统提供的几个创建线程池的方式,因为他们不支持线程的优先级,也不支持线程池的暂停恢复关闭,线程池中结果回调,线程设置name等功能,所以一般我们都会自己封装一个全局的线程池来使用,比如下面:
object MyExecutor {
private const val TAG:String = "MyExecutor"
private var threadPoolExecutor: ThreadPoolExecutor
private var lock:ReentrantLock = ReentrantLock()
private var pauseCondition: Condition = lock.newCondition()
private var isPaused:Boolean = false
private val mainHandler: Handler = Handler(Looper.getMainLooper())
init {
val cupCount = Runtime.getRuntime().availableProcessors()
val corePoolSize = cupCount +1
val maxPoolSize = cupCount*2+1
val keepAliveTime = 30L
val seq = AtomicLong()
threadPoolExecutor = object :ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
PriorityBlockingQueue(),
ThreadFactory {
val thread = Thread()
thread.name="myThread-"+seq.getAndIncrement()
return@ThreadFactory thread
}
){
override fun beforeExecute(t: Thread?, r: Runnable?) {
super.beforeExecute(t, r)
//在该方法中判断 如果线程池需要暂停 则阻塞 这里使用ReentrantLock来阻塞
if(isPaused){
lock.lock()
try {
pauseCondition.await()
}finally {
lock.unlock()
}
}
}
override fun afterExecute(r: Runnable?, t: Throwable?) {
super.afterExecute(r, t)
//在这里可以监控线程的耗时 线程的数量等
Log.d(TAG,"afterExecute task priority is"+(r as PriorityRunnable).priority)
}
}
}
@JvmOverloads
fun execute(@IntRange(from = 0, to= 10)priority : Int = 0,runnable: Runnable){
threadPoolExecutor.execute(PriorityRunnable(priority,runnable))
}
@JvmOverloads
fun execute(@IntRange(from = 0, to= 10)priority : Int = 0,runnable: Callable<*>){
threadPoolExecutor.execute(PriorityRunnable(priority,runnable))
}
abstract class Callable<T>:Runnable{
override fun run() {
mainHandler.post{onPrepare()}
doInBackground()
mainHandler.post { onComplete() }
}
open fun onPrepare(){}
open fun doInBackground(){}
open fun onComplete(){}
}
class PriorityRunnable(val priority:Int, private val runnable: Runnable):Runnable,Comparable<PriorityRunnable>{
override fun run() {
runnable.run()
}
override fun compareTo(other: PriorityRunnable): Int {
return if (this.priority>other.priority) 1 else if (this.priority<other.priority) -1 else 0
}
}
@Synchronized
fun pause(){
isPaused = true
Log.d(TAG,"MyExecutor is pause")
}
@Synchronized
fun resume(){
isPaused = false
lock.lock()
try {
pauseCondition.signalAll()
}finally {
lock.unlock()
}
Log.d(TAG,"MyExecutor is resume")
}
}
上面利用PriorityBlockingQueue和Condition简单封装了一个具有优先级并且可以控制暂停和唤起线程的工具类。
如果我们想要增大线程池的吞吐量,可以使用SynchronousBlockQueue这个阻塞队列,当核心线程满了之后,它不会等待队列装满,新来任务的时候回直接创建非核心线程去执行,所以速度相对比较快。
还可以通过任务的属性比如IO任务还是CPU任务设计相对应参数的线程池
还可以在该类里面统计线程的耗时
协程
最简单的用法
var job = GlobalScope.launch { //xxx}
GlobalScope.launch(Dispatchers.Main) { //xxx}
var deferred = GlobalScope.async { //xxx }
上面的代码就开启了一个全局作用域的协程,括号里面的代码就运行在协程中了。
GlobalScope.launch { }
和 GlobalScope.async { }
都可以开启一个协程,就是返回值不同,一个是返回job,一个是返回deferred。deferred是Job的子类 比Job多了一个await()
方法。
Job是一个接口,它主要又以下几个方法
- start() 手动启动一个协程
- join() 它会挂起当前协程,等待新建的job执行完毕
- cancel() 取消一个协程
- await() 是deferred中的方法 阻塞协程等待结果的返回,不会阻塞线程
除了GlobalScope,官方还提供了lifecycleScope.launch { }
和viewModelScope.launch { }
lifecycleScope是和activity或者fragment生命周期绑定的,viewModelScope是和JetPack中的ViewModel的生命周期绑定的。使用特定作用域的协程可以减少内存泄漏的风险。
IDE中点击launch进入其构造函数看一下
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
协程等launch方法需要传递3个参数
- context : CoroutineContext 协程的context 我们前面例子里面传递的
Dispatchers.Main
就是它的一个子类,另外还有Dispatchers.IO
等 - start: CoroutineStart 协程等启动模式 包括
CoroutineStart.DEFAULT
,CoroutineStart.LAZY
,CoroutineStart.ATOMIC
,CoroutineStart.UNDISPATCHED
模式 | 说明 |
---|---|
DEFAULT | 创建即启动协程 可以随时取消 |
LAZY | 延迟模式 仅在需要等时候才启动协程 |
ATOMIC | 自动模式 创建即启动协程 创建前不可取消 |
UNDISPATCHED | 立即执行协程 直到运行到它内部的第一个挂起点 |
- block: suspend CoroutineScope.() -> Unit:闭包代码块,需要在协程中执行的代码
当我们发起网络请求的时候,使用协程来控制执行顺序是非常方便的,比如下面的小demo
object CoroutineTest {
/**
* request1 request2 request3 3个异步任务顺序执行
*/
fun start(){
GlobalScope.launch(Dispatchers.Main) {
var res1 = request1()
var res2 = request2(res1)
var res3 = request3(res2)
updateUI(res3)
}
}
/**
* 先执行request1 然后并发执行 request2 request3 当request2 request3都返回之后在更新UI
*/
fun start1(){
GlobalScope.launch(Dispatchers.Main) {
Log.d("CoroutineTest","start1 is running")
val res1 = request1()
val deferred2 = GlobalScope.async { request2(res1) }
val deferred3 = GlobalScope.async { request3(res1) }
updateUI(deferred2.await(),deferred3.await())
}
}
private fun updateUI(res2:String,res3: String) {
Log.d("CoroutineTest","updateUI threadName-->${Thread.currentThread().name}")
Log.d("CoroutineTest","updateUI -->${res2}-->${res3}")
}
private fun updateUI(res3: String) {
Log.d("CoroutineTest","updateUI threadName-->${Thread.currentThread().name}")
Log.d("CoroutineTest","updateUI -->${res3}")
}
suspend fun request1():String{
delay(1000)
Log.d("CoroutineTest","request1 threadName-->${Thread.currentThread().name}")
return "request from request1"
}
suspend fun request2(str:String):String{
delay(1000)
Log.d("CoroutineTest","request2 threadName-->${Thread.currentThread().name}")
return "request from request2"
}
suspend fun request3(str:String):String{
delay(1000)
Log.d("CoroutineTest","request3 threadName-->${Thread.currentThread().name}")
return "request from request3"
}
上面代码中start方法控制3个request顺序执行,start1方法控制先执行request1在同时执行request2和request3。使用起来非常方便。
协程挂起和恢复的原理
挂起函数:被suspended关键字修饰的方法在编译阶段,编译器会修改方法的签名,包括方法的返回值、修饰符、入参、方法体实现。
我们定义一个挂起函数
suspend fun request():String{
delay(1000)
Log.i("CoroutineCase","request")
return "form request"
}
只是从上面的kotlin代码中我们无法看出来挂起和恢复到底是什么样的,我们需要从其字节码入手
在androidStudio 的 Tools-->Kotlin-->ShowKotlinByteCode 查看其字节码,然后反编译成Java代码来查看Java代码的实现,最终可以看到经过Kotlin的编译器处理之后的代码如下
@Nullable // 1 编译之后给原来的方法添加了一个参数Continuation
public final Object request(@NotNull Continuation var1) {
Object $continuation;
label20: {
// 2 判断入参Continuation是否已经包装成了ContinuationImpl
if (var1 instanceof <undefinedtype>) {
$continuation = (<undefinedtype>)var1;
if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
break label20;
}
}
//3 包装成ContinuationImpl对象 起内部有个参数label 初始值为0
$continuation = new ContinuationImpl(var1) {
// $FF: synthetic field
Object result;
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
this.result = $result;
this.label |= Integer.MIN_VALUE;
//4 在回调中再次调用request方法
return CoroutineCase.this.request(this);
}
};
}
Object $result = ((<undefinedtype>)$continuation).result;
//5 var4 返回一个常量 CoroutineSingletons.COROUTINE_SUSPENDED
Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
//6 判断lable的值 初始值为0
switch(((<undefinedtype>)$continuation).label) {
case 0:
//6 将label的值置为1
ResultKt.throwOnFailure($result);
((<undefinedtype>)$continuation).label = 1;
//7 调用协程方法,并将回调ContinuationImpl传进去
//该协程的执行结果通过传入的回调参数返回
//如果当前协程的返回值为COROUTINE_SUSPENDED就直接return
if (DelayKt.delay(1000L, (Continuation)$continuation) == var4) {
return var4;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
//第7步执行完后通过回调执行到第4步对label进行包装后再次调用该方法
//这时候因为label为1就会break继续走到下面的代码了
Log.i("CoroutineCase", "request");
return "form request";
}
上面的代码就是kotlin编译器把我们前面写的协程改装之后的样子,
首先它给我们的方法添加了一个参数Continuation,并包装成ContinuationImpl。这个Continuation其实就是一个Callback回调,当挂起结束之后通过其resumeWith方法恢复挂起。
public interface Continuation<in T> {
/**
* The context of the coroutine that corresponds to this continuation.
*/
public val context: CoroutineContext
/**
* Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
* return value of the last suspension point.
*/
public fun resumeWith(result: Result<T>)
}
然后我们看到switch语句中,判断label为如果为0,就直接返回了。直到挂起函数中的代码执行完之后,回调到前面代码中注释4的位置再次执行我们的request方法。这次在执行的时候再次执行到switch语句中的时候,label已经为1了,就不会阻塞继续往下面执行了。
所以协程挂起和恢复的本质就是方法的返回和回调。当不满足条件的时候就直接return,直到回调成功之后才会满足执行条件继续向下执行。
使用suspend关键字修饰的函数一定是挂起函数吗?答 不是
我们在来到switch语句的0的位置,可以看到一个if语句,只有if (DelayKt.delay(1000L, (Continuation)$continuation) == var4)
成立的时候才会return,var4是一个常量CoroutineSingletons.COROUTINE_SUSPENDED
。也就是说我们在suspend方法中调用的代码返回var4的时候才会挂起。delay(1000)
函数为啥就返回了var4呢?这里我们看到这里其实调用的是DelayKt.delay方法,点进去一看
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // don't delay
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
可以看到delay方法里面代码包裹在suspendCancellableCoroutine代码块中。实际上需要协程中运行的代码是suspendCancellableCoroutine包裹的才会挂起。继续跟随suspendCancellableCoroutine的源码就能看到第一次会返回常量CoroutineSingletons.COROUTINE_SUSPENDED
。网络请求框架Retrofit内部也是通过suspendCancellableCoroutine来适配协程的。