我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android中的Coroutine协程原理是什么

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android中的Coroutine协程原理是什么

这篇文章主要介绍了Android中的Coroutine协程原理是什么,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

前言

协程是一个并发方案。也是一种思想。

传统意义上的协程是单线程的,面对io密集型任务他的内存消耗更少,进而效率高。但是面对计算密集型的任务不如多线程并行运算效率高。

不同的语言对于协程都有不同的实现,甚至同一种语言对于不同平台的操作系统都有对应的实现。

我们kotlin语言的协程是 coroutines for jvm的实现方式。底层原理也是利用java 线程。

基础知识

生态架构

Android中的Coroutine协程原理是什么

相关依赖库

dependencies {   // Kotlin   implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.32"   // 协程核心库   implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"   // 协程Android支持库   implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"   // 协程Java8支持库   implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.4.3"   // lifecycle对于协程的扩展封装   implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"   implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"   implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"}

为什么一些人总觉得协程晦涩难懂?

网络上没有详细的关于协程的概念定义,每种语言、每个系统对其实现都不一样。可谓是众说纷纭,什么内核态用户态巴拉巴拉,很容易给我们带偏

kotlin的各种语法糖对我们造成的干扰。如:

  • 高阶函数

  • 源码实现类找不到

所以扎实的kotlin语法基本功是学习协程的前提。

实在看不懂得地方就反编译为java,以java最终翻译为准。

协程是什么?有什么用?

kotlin中的协程干的事就是把异步回调代码拍扁了,捋直了,让异步回调代码同步化。除此之外,没有任何特别之处。

创建一个协程,就是编译器背后偷偷生成一系列代码,比如说状态机。

通过挂起和恢复让状态机状态流转实现把层层嵌套的回调代码变成像同步代码那样直观、简洁。

它不是什么线程框架,也不是什么高深的内核态,用户态。它其实对于咱们安卓来说,就是一个关于回调函数的语法糖。。。

本文将会围绕挂起与恢复彻底剖析协程的实现原理

Kotlin函数基础知识复习

再Kotlin中函数是一等公民,有自己的类型

函数类型

fun foo(){}//类型为 () -> Unitfun foo(p: Int){}//类型为 (Int) -> Stringclass Foo{    fun bar(p0: String,p1: Long):Any{}    }//那么 bar 的类型为:Foo.(String,Long) -> Any//Foo就是bar的 receiver。也可以写成 (Foo,String,Long) ->Any

函数引用

fun foo(){} //引用是 ::foofun foo(p0: Int): String//引用也是 ::foo

咋都一样?没办法,就这样规定的。使用的时候 只能靠编译器推断

val f: () -> Unit = ::foo //编译器会推断出是fun foo(){} val g: (Int) -> String = ::foo //推断为fun foo(p0: Int): String

带Receiver的写法

class Foo{    fun bar(p0: String,p1: Long):Any{}}
val h: (Foo,String,Long) -> Any = Foo:bar

绑定receiver的函数引用:

val foo: Foo = Foo()val m: (String,Long) -> Any = foo:bar

额外知识点

val x: (Foo,String,Long) -> Any = Foo:barval y: Function3<Foo,String,Long,Any> = xFoo.(String,Long) -> Any = (Foo,String,Long) ->Any = Function3<Foo,String,Long,Any>

函数作为参数传递

fun yy(p: (Foo,String,Long)->Any){p(Foo(),"Hello",3L)//直接p()就能调用    //p.invoke(Foo(),"Hello",3L) 也可以用invoke形式}

Lambda

就是匿名函数,它跟普通函数比是没有名字的,听起来好像是废话

//普通函数fun func(){   println("hello");}//去掉函数名 func,就成了匿名函数fun(){   println("hello");    //可以赋值给一个变量val func = fun(){//匿名函数的类型val func :()->Unit = fun(){//Lambda表达式val func={   print("Hello");//Lambda类型val func :()->String = {print("Hello");"Hello" //如果是Lambda中,最后一行被当作返回值,能省掉return。普通函数则不行//带参数Lambdaval f1: (Int)->Unit = {p:Int ->print(p);//可进一步简化为val f1 = {p:Int ->print(p);    //当只有一个参数的时候,还可以写成val f1: (Int)->Unit = {   print(it);

关于函数的个人经验总结

函数跟匿名函数看起来没啥区别,但是反编译为java后还是能看出点差异

如果只是用普通的函数,那么他跟普通java 函数没啥区别。

比如 fun a() 就是对应java方法public void a(){}

但是如果通过函数引用(:: a)来用这个函数,那么他并不是直接调用fun a()而是重新生成一个Function0

挂起函数

suspend 修饰。

挂起函数中能调用任何函数。

非挂起函数只能调用非挂起函数。

换句话说,suspend函数只能在suspend函数中调用。

简单的挂起函数展示:

//com.example.studycoroutine.chapter.CoroutineRun.ktsuspend fun suspendFun(): Int {    return 1;}

挂起函数特殊在哪?

public static final Object suspendFun(Continuation completion) {    return Boxing.boxInt(1);}

这下理解suspend为啥只能在suspend里面调用了吧?

想要让道貌岸然的suspend函数干活必须要先满足它!!!就是给它里面塞入一颗球。

然后他想调用其他的suspend函数,只需将球继续塞到其它的suspend方法里面。

普通函数里没这玩意啊,所以压根没法调用suspend函数。。。

读到这里,想必各位会有一些疑问:

  • question1.这不是鸡生蛋生鸡的问题么?第一颗球是哪来的?

  • question2.为啥编译后返回值也变了?

  • question3.suspendFun 如果在协程体内被调用,那么他的球(completion)是谁?

标准库给我们提供的最原始工具

public fun <T> (suspend () -> T).startCoroutine(completion: Continuation<T>) {   createCoroutineUnintercepted(completion).intercepted().resume(Unit)}public fun <T> (suspend () -> T).createCoroutine(completion: Continuation<T>): Continuation<Unit> =   SafeContinuation(createCoroutineUnintercepted(completion).intercepted(), COROUTINE_SUSPENDED)

以一个最简单的方式启动一个协程。

Demo-K1

fun main() {   val b = suspend {       val a = hello2()       a  }   b.createCoroutine(MyCompletionContinuation()).resume(Unit)}suspend fun hello2() = suspendCoroutine<Int> {   thread{       Thread.sleep(1000)       it.resume(10086)class MyContinuation() : Continuation<Int> {   override val context: CoroutineContext = CoroutineName("Co-01")   override fun resumeWith(result: Result<Int>) {       log("MyContinuation resumeWith 结果 = ${result.getOrNull()}")

两个创建协程函数区别

startCoroutine 没有返回值 ,而createCoroutine返回一个Continuation,不难看出是SafeContinuation

好像看起来主要的区别就是startCoroutine直接调用resume(Unit),所以不用包装成SafeContinuation,而createCoroutine则返回一个SafeContinuation,因为不知道将会在何时何处调用resume,必须保证resume只调用一次,所以包装为safeContinuation

SafeContinuationd的作用是为了确保只有发生异步调用时才挂起

分析createCoroutineUnintercepted

//kotlin.coroutines.intrinsics.CoroutinesIntrinsicsH.kt@SinceKotlin("1.3")public expect fun <T> (suspend () -> T).createCoroutineUnintercepted(completion: Continuation<T>): Continuation<Unit>

先说结论

其实可以简单的理解为kotlin层面的原语,就是返回一个协程体。

开始分析

引用代码Demo-K1首先b 是一个匿名函数,他肯定要被编译为一个FunctionX,同时它还被suspend修饰 所以它肯定跟普通匿名函数编译后不一样。

编译后的源码为

public static final void main() {     Function1 var0 = (Function1)(new Function1((Continuation)null) {        int label;        @Nullable        public final Object invokeSuspend(@NotNull Object $result) {           Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();           Object var10000;           switch(this.label) {           case 0:              ResultKt.throwOnFailure($result);              this.label = 1;              var10000 = TestSampleKt.hello2(this);              if (var10000 == var3) {                 return var3;              }              break;           case 1:              var10000 = $result;           default:              throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");          }           int a = ((Number)var10000).intValue();           return Boxing.boxInt(a);        }        @NotNull        public final Continuation create(@NotNull Continuation completion) {           Intrinsics.checkParameterIsNotNull(completion, "completion");           Function1 var2 = new <anonymous constructor>(completion);           return var2;        public final Object invoke(Object var1) {           return((<undefinedtype>)this.create((Continuation)var1)).invokeSuspend(Unit.INSTANCE);    });     boolean var1 = false;     Continuation var7 = ContinuationKt.createCoroutine(var0, (Continuation)(newMyContinuation()));     Unit var8 = Unit.INSTANCE;     boolean var2 = false;     Companion var3 = Result.Companion;     boolean var5 = false;     Object var6 = Result.constructor-impl(var8);     var7.resumeWith(var6);  }

我们可以看到先是 Function1 var0 = new Function1创建了一个对象,此时跟协程没关系,这步只是编译器层面的匿名函数语法优化

如果直接

fun main() {   suspend {       val a = hello2()       a  }.createCoroutine(MyContinuation()).resume(Unit)}

也是一样会创建Function1 var0 = new Function1

解答question1

继续调用createCoroutine

再继续createCoroutineUnintercepted ,找到在JVM平台的实现

//kotlin.coroutines.intrinsics.IntrinsicsJVM.class@SinceKotlin("1.3")public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(   completion: Continuation<T>): Continuation<Unit> {//probeCompletion还是我们传入completion对象,在我们的Demo就是myCoroutine   val probeCompletion = probeCoroutineCreated(completion)//probeCoroutineCreated方法点进去看了,好像是debug用的.我的理解是这样的   //This就是这个suspend lambda。在Demo中就是myCoroutineFun   return if (this is BaseContinuationImpl)       create(probeCompletion)   else//else分支在我们demo中不会走到     //当 [createCoroutineUnintercepted] 遇到不继承 BaseContinuationImpl 的挂起 lambda 时,将使用此函数。       createCoroutineFromSuspendFunction(probeCompletion) {          (this as Function1<Continuation<T>, Any?>).invoke(it)      }}
@NotNullpublic final Continuation create(@NotNull Continuation completion) {Intrinsics.checkNotNullParameter(completion, "completion");Function1 var2 = new <anonymous constructor>(completion);return var2;}

把completion传入,并创建一个新的Function1,作为Continuation返回,这就是创建出来的协程体对象,协程的工作核心就是它内部的状态机,invokeSuspend函数

调用 create

@NotNullpublic final Continuation create(@NotNull Continuation completion) {Intrinsics.checkNotNullParameter(completion, "completion");Function1 var2 = new <anonymous constructor>(completion);return var2;}

把completion传入,并创建一个新的Function1,作为Continuation返回,这就是创建出来的协程体对象,协程的工作核心就是它内部的状态机,invokeSuspend函数

补充---相关类继承关系

Android中的Coroutine协程原理是什么

解答question2&3

已知协程启动会调用协程体的resume,该调用最终会来到BaseContinuationImpl::resumeWith

internal abstract class BaseContinuationImpl{   fun resumeWith(result: Result<Any?>) {          // This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume       var current = this       var param = result       while (true) {           with(current) {               val completion = completion!! // fail fast when trying to resume continuation without completion               val outcome: Result<Any?> =                   try {                       val outcome = invokeSuspend(param)//调用状态机                       if (outcome === COROUTINE_SUSPENDED) return                       Result.success(outcome)                  } catch (exception: Throwable) {                       Result.failure(exception)                  }               releaseIntercepted() // this state machine instance is terminating               if (completion is BaseContinuationImpl) {                   // unrolling recursion via loop                   current = completion                   param = outcome              } else {                   //最终走到这里,这个completion就是被塞的第一颗球。                   completion.resumeWith(outcome)                   return              }          }      }  }}

状态机代码截取

public final Object invokeSuspend(@NotNull Object $result) {   Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();   Object var10000;   switch(this.label) {   case 0://第一次进来 label = 0       ResultKt.throwOnFailure($result);      // label改成1了,意味着下一次被恢复的时候会走case 1,这就是所谓的【状态流转】      this.label = 1;       //全体目光向我看齐,我宣布个事:this is 协程体对象。      var10000 = TestSampleKt.hello2(this);      if (var10000 == var3) {         return var3;      }      break;   case 1:      ResultKt.throwOnFailure($result);      var10000 = $result;      break;   default:      throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");   }   int a = ((Number)var10000).intValue();   return Boxing.boxInt(a);}

question3答案出来了,传进去的是create创建的那个continuation

最后再来聊聊question2,从上面的代码已经很清楚的告诉我们为啥挂起函数反编译后的返回值变为object了。

以hello2为例子,hello2能返回代表挂起的白板,也能返回result。如果返回白板,状态机return,协程挂起。如果返回result,那么hello2执行完毕,是一个没有挂起的挂起函数,通常编译器也会提醒 suspend 修饰词无意义。所以这就是设计需要,没有啥因为所以。

最后,除了直接返回结果的情况,挂起函数一定会以resume结尾,要么返回result,要么返回异常。代表这个挂起函数返回了。

调用resume意义在于重新回调BaseContinuationImpl的resumeWith,进而唤醒状态机,继续执行协程体的代码。

换句话说,我们自定义的suspend函数,一定要利用suspendCoroutine 获得续体,即状态机对象,否则无法实现真正的挂起与resume。

suspendCoroutine

我们可以不用suspendCoroutine,用更直接的suspendCoroutineUninterceptedOrReturn也能实现,不过这种方式要手动返回白板。不过一定要小心,要在合理的情况下返回或者不返回,不然会产生很多意想不到的结果

suspend fun mySuspendOne() = suspendCoroutineUninterceptedOrReturn<String> { continuation ->    thread {        TimeUnit.SECONDS.sleep(1)        continuation.resume("hello world")    }    //因为我们这个函数没有返回正确结果,所以必须返回一个挂起标识,否则BaseContinuationImpl会认为完成了任务。     // 并且我们的线程又在运行没有取消,这将很多意想不到的结果    kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED}

而suspendCoroutine则没有这个隐患

suspend fun mySafeSuspendOne() = suspendCoroutine<String> { continuation ->    thread {        TimeUnit.SECONDS.sleep(1)        continuation.resume("hello world")    }    //suspendCoroutine函数很聪明的帮我们判断返回结果如果不是想要的对象,自动返    kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED}
public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T =    suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->    //封装一个代理Continuation对象        val safe = SafeContinuation(c.intercepted())        block(safe)        //根据block返回结果判断要不要返回COROUTINE_SUSPENDED        safe.getOrThrow()    }

SafeContinuation的奥秘

//调用单参数的这个构造方法internal actual constructor(delegate: Continuation<T>) : this(delegate, UNDECIDED)@Volatileprivate var result: Any? = initialResult //UNDECIDED赋值给 result //java原子属性更新器那一套东西private companion object {       @Suppress("UNCHECKED_CAST")       @JvmStatic       private val RESULT = AtomicReferenceFieldUpdater.newUpdater<SafeContinuation<*>, Any?>(           SafeContinuation::class.java, Any::class.java as Class<Any?>, "result"      )  }internal actual fun getOrThrow(): Any? {   var result = this.result // atomic read   if (result === UNDECIDED) { //如果UNDECIDED,那么就把result设置为COROUTINE_SUSPENDED       if (RESULT.compareAndSet(this, UNDECIDED, COROUTINE_SUSPENDED)) returnCOROUTINE_SUSPENDED       result = this.result // reread volatile var   return when {       result === RESUMED -> COROUTINE_SUSPENDED // already called continuation, indicate COROUTINE_SUSPENDED upstream       result is Result.Failure -> throw result.exception       else -> result // either COROUTINE_SUSPENDED or data <-这里返回白板}public actual override fun resumeWith(result: Result<T>) {       while (true) { // lock-free loop           val cur = this.result // atomic read。不理解这里的官方注释为啥叫做原子读。我觉得 Volatile只能保证可见性。           when {             //这里如果是UNDECIDED 就把 结果附上去。               cur === UNDECIDED -> if (RESULT.compareAndSet(this, UNDECIDED, result.value)) return             //如果是挂起状态,就通过resumeWith回调状态机               cur === COROUTINE_SUSPENDED -> if (RESULT.compareAndSet(this, COROUTINE_SUSPENDED, RESUMED)){                   delegate.resumeWith(result)                   return              }               else -> throw IllegalStateException("Already resumed")          }      }
val safe = SafeContinuation(c.intercepted())block(safe)safe.getOrThrow()

先回顾一下什么叫真正的挂起,就是getOrThrow返回了“白板”,那么什么时候getOrThrow能返回白板?答案就是result被初始化后值没被修改过。那么也就是说resumeWith没有被执行过,即:block(safe)这句代码,block这个被传进来的函数,执行过程中没有调用safe的resumeWith。原理就是这么简单,cas代码保证关键逻辑的原子性与并发安全

继续以Demo-K1为例子,这里假设hello2运行在一条新的子线程,否则仍然是没有挂起。

{   thread{       Thread.sleep(1000)       it.resume(10086)  }}

感谢你能够认真阅读完这篇文章,希望小编分享的“Android中的Coroutine协程原理是什么”这篇文章对大家有帮助,同时也希望大家多多支持编程网,关注编程网行业资讯频道,更多相关知识等着你来学习!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android中的Coroutine协程原理是什么

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android中的Coroutine协程原理是什么

这篇文章主要介绍了Android中的Coroutine协程原理是什么,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。前言协程是一个并发方案。也是一种思想。传统意义上的协程是单线
2023-06-29

golang协程实现的原理是什么

Golang中的协程(goroutine)是一种轻量级的线程,由Go语言的运行时系统进行管理。协程的实现原理主要包括以下几个方面:调度器:Golang的运行时系统包含一个调度器,负责协程的创建、调度和管理。调度器使用了一种称为"工作窃取"的
2023-10-25

go协程调度的原理是什么

Go协程的调度原理是基于M:N的模型,其中M代表操作系统的线程,N代表Go协程。Go运行时系统会创建一定数量的操作系统线程,每个线程被称为M,用于执行Go协程。这些M线程会在需要的时候自动创建和销毁,以适应不同的负载。Go运行时系统还会维
2023-10-23

golang协程调度的原理是什么

Golang的协程调度器的原理是基于M:N的模型。其中M代表操作系统的线程(Machine),N代表Golang的协程(Goroutine)。Golang的调度器维护了一个全局的运行队列,其中包含了所有待执行的协程。调度器会根据一定的策略
2023-10-23

go协程调度原理是什么

Go协程调度的原理是基于M:N调度模型。其中,M代表操作系统的线程(Machine),N代表Go语言的协程(Goroutine)。在启动时,Go程序会创建一组操作系统的线程(M),每个线程都维护着一个协程队列。当一个协程需要执行时,调度器会
2023-10-07

golang协程实现原理是什么

Golang协程实现的原理是使用了一种称为"轻量级线程"或"用户态线程"的概念,即Goroutine(协程)。在Goroutine中,不会为每个协程创建一个完整的操作系统线程,而是通过使用更少的内存和资源,将多个协程调度在少量的操作系统线程
2023-08-31

Android中Lint的原理是什么

这篇文章将为大家详细讲解有关Android中Lint的原理是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。Lint 的工作过程lint 工具的代码扫描工作流:应用源文件:源文件包含组成
2023-06-14

Android中Lifecycle的原理是什么

本文小编为大家详细介绍“Android中Lifecycle的原理是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“Android中Lifecycle的原理是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。
2023-06-29

Android中ViewPager的原理是什么

ViewPager是Android中的一个布局容器控件,主要用于实现页面切换效果。它的原理是通过管理多个Fragment或View的显示与隐藏,实现页面的滑动切换。具体原理如下:1. ViewPager通过PagerAdapter来管理多个
2023-09-25

android线程池的原理是什么

Android线程池的原理是通过管理和调度线程来实现并发执行任务的机制。线程池主要由线程池管理器、工作队列和线程池的线程组成。线程池管理器:线程池管理器负责创建、销毁和管理线程池。它根据任务的类型和优先级来决定将任务分配给线程池中的线程执行
2023-09-23

golang协程调度的实现原理是什么

Golang的协程调度器采用了一种称为M:N调度的策略。这意味着它将M个用户级线程(也称为goroutines)调度到N个内核级线程(也称为操作系统线程)上执行。调度器的实现原理如下:调度器会在启动时创建一组操作系统线程,称为M。这些线程
2023-10-27

HTML5中WebSocket协议的实现原理是什么

这篇文章将为大家详细讲解有关HTML5中WebSocket协议的实现原理是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。WebSocket协议的目的是为了工作于现有的网络基础设施。作为这
2023-06-09

android leakcanary的原理是什么

Android LeakCanary是一个用于检测内存泄漏的开源库。它的原理主要包括以下几个步骤:1. 监测对象的引用关系:LeakCanary会监测应用中所有的对象引用关系,包括Activity、Fragment、View等。它会跟踪对象
2023-09-23

Android ANR的原理是什么

本篇内容介绍了“Android ANR的原理是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、ANR说明和原因1.1 简介ANR全称:
2023-06-21

Android的root原理是什么

本篇内容主要讲解“Android的root原理是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android的root原理是什么”吧!0x00 关于rootlinux和类Unix系统的最初设
2023-06-27

HTTP协议的工作原理是什么

这篇文章主要介绍“HTTP协议的工作原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“HTTP协议的工作原理是什么”文章能帮助大家解决问题。HTTP协议(HyperText Transfer
2023-06-04

Android中AsyncTask的工作原理是什么

这篇文章给大家分享的是有关Android中AsyncTask的工作原理是什么的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。概述实际上,AsyncTask内部是封装了Thread和Handler。虽然AsyncTa
2023-06-15

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录