美文网首页
Kotlin核心基础之高阶函数,协程基本用法

Kotlin核心基础之高阶函数,协程基本用法

作者: Owen270 | 来源:发表于2024-12-20 18:06 被阅读0次

怎么理解kotlin的挂起函数?

Kotlin协程进阶使用
Kotlin协程在工作中有用过吗? (qq.com)

1.kotlin和java编译后都是java字节码,相对于java有啥优势和劣势?

(1).kotlin优势

  • 代码简洁,开发效率更高
    不用使用findViewById,支减少冗余代码,支持空安全,扩展函数,属性,高阶函数和lambda表达式,内联函数。

  • 内存消耗更少
    由于空安全特性和内联函数优化,它能够生成更高效的字节码,从而减少内存的使用。kotlin协程提供了一中轻量级的并发处理方式,可以进一步降低内存的占用。

  • kotlin是未来Android开发主流的语言,一些Android的新特性,新组件都会优先支持kotlin版本。

(2).kotlin劣势
kotlin编译速度相对java较慢,因为她需要进行额外的类型检查(空安全),和代码转换(kotlin-java-java字节码,复杂,耗性能)

备注:Kotlin与Java在运行时性能方面基本相当,由于kotlin支持inline函数,lambda表达式,在某些情况下性能还要由于java.

2.Kotlin内置的高阶函数run的原理是什么,与let函数有啥区别?

(1).inline :表示这是一个内联函数,将函数代码直接插入调用处,避免了调用普通方法时,栈帧的创建,销毁所带来的开销。
(2).<T,R> T.run :T代表要为T扩展出一个名为run的函数,R表示lambda表达式最后一行返回的类型。
(3).block:T.()->R:
block:表示lambda的名称
T.():表示输入参数是T本身,让lambda表达式持有了this表示(T)调用者本身
R:表示lambda最后一行返回的类型.
(T) :表示输入参数是T本身,让lambda表达式持有了it表示(T)调用者本身

public inline fun<T,R> T.let(block:(T)->R){
    return block(this)
}

public inline fun<T,R> T.run(block:T.()->R):R{
      return block()
}
//with不是泛型的扩展方法,而是全局方法
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    return receiver.block()
}

public inline fun <T> T.also(block: (T) -> Unit): T {
    block(this)
    return this
}
public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}



fun main(){
   var  result1="778".run{  
         true
        length
    }
   println(result1)   //3
  var result2="778".let{
         999
         "$it" //778
  }
}

为啥所有的类型都可以使用run或者let,那是因为高阶函数let和run是对泛型进行扩展,意味着所有类型都等于泛型,所以任何地方都可以使用.run和let的区别在于lambda表达式一个持有this表示调用者本身,一个持有it表示调用者本身。

public inline <T> T.apply(block:T.()->unit):T
public inline <T> T.also(block:(T)->unit):T
public int <T,R> with(receiver:T,block:T.()->R):R

3.kotlin语言泛型的形变是什么?【PECS原则 extends out || super in】

  • 不变:指的是此泛型既可以是消费者,可以是生产者,没有任何继承相关的概念.
class Student<T> {}   
  • 协变 : 指的是此泛型只能是生产者 ,泛型类型只能是T或者T的子类,只能get数据,无法add数据.
ArrayList<out T>            
  • 逆变:指的是此泛型只能是消费者,泛型类型只能是T类型,或者T的父类,只能add数据,无法get【如果get数据,数据类型只能是Object】
ArrayList<in T>     

4.协程的基本使用.

(1).启动协程的几种方式.
  • launch{} CoroutineScope接口的扩展方法,启动一个携程,不阻塞当前线程,返回一个Job对象协程.
public fun CoroutineScope.launch(
     context:CoroutineScope=EmptyCoroutineContext,
     start:CoroutineStart = CoroutineStart.DEFAULT,
     block:suspend CoroutineScope.()->Unit
):Job {  //Job是返回值, {} 是launch的方法体实现 【不要搞混淆了】
 //launch方法体实现,返回Job对象
      val newcontenxt=newCoroutineContext(context)
     val coroutine=if(start.isLazy){
          LazyStandaloneCoroutine(newContext,block);//实现了job接口
     }else{
        StandaloneCoroutine(newContext,active=true)  //实现了job接口
    }
     coroutine.start(start,coroutine,block)  //启动job
    return  coroutine;//返回实现了Job接口的协程对象
}

Job.cancel()可以用来取消协程,控制协程的生命周期.

  • async{} CoroutienScope接口的扩展方法,启动一个协程,不阻塞当前线程,返回一个Deferred<T>类,可以通过wait获取T对象.
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine= if (start.isLazy){
        LazyDeferredCoroutine<T>(newContext, block)  //实现了Deferred接口
    } else{
       DeferredCoroutine<T>(newContext, active = true) //实现了Deferred接口
    }
    coroutine.start(start, coroutine, block)   //启动协程
    return coroutine //返回实现了Deferred接口的协程对象
}
  • runBlocking{} 全局方法,创建并启动协程,返回值是lambda表达式block的最后一行的返回值.(Unit或者其他具体类型)
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
    val currentThread = Thread.currentThread()
    val contextInterceptor = context[ContinuationInterceptor]
    val eventLoop: EventLoop?
    val newContext: CoroutineContext
    if (contextInterceptor == null) {
        eventLoop = ThreadLocalEventLoop.eventLoop
        newContext = GlobalScope.newCoroutineContext(context + eventLoop)
    } else {
        eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
            ?: ThreadLocalEventLoop.currentOrNull()
        newContext = GlobalScope.newCoroutineContext(context)
    }
    val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
    coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
    return coroutine.joinBlocking()
}
//这一段代码会阻塞主线程,直接导致黑屏。
runBlocking {
     delay(10000)
 }
//即使指定启动的协程运行在IO线程,也会阻塞主线程,导致黑屏
runBlocking(Dispatchers.IO) {
   delay(10000L)
}
//依然会阻塞主线程导致黑屏
 GlobalScope.launch(Dispatchers.Main){
       runBlocking {
                delay(10000)
        }

 }
//不会阻塞主线程
 GlobalScope.launch(Dispatchers.IO){
       runBlocking {
                delay(10000)
        }
 }
(2).withContext() suspend修饰,并不会启动协程,只能在suspend挂起方法或者协程中调用,用于切换线程,并挂起协程。(暂停协程,但是执行协程的线程可以继续执行其他任务,不会阻塞,等到协程恢复,该线程可以继续执行)
public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T
   
 lifecycleScope.launch(Dispatchers.IO) {
            delay(1000)
            withContext(Dispatchers.Main){ //返回值是Unit 也就是void  //切换到主线程 ,此时协程的IO线程可以去执行其他任务
                println("Test001:withContext:${Thread.currentThread().name}")
            }
        }
(3).coroutineScope() suspend修饰,并不会启动协程,只能在suspend挂起方法或者协程中调用,创建一个新的协程作用域【或者说可以在已有的协程内部创建一个新协程】
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val coroutine = ScopeCoroutine(uCont.context, uCont)
        coroutine.startUndispatchedOrReturn(coroutine, block)
 }


coroutineScope {  }  //报错 ,不能直接用,只能在协程里面使用

runBlocking { //非suspend全局方法,直接启动协程
   coroutineScope {  }   //正常使用,因为runBlocking创建了协程//创建一个新的协程作用域
  delay(1000L) 
}

(4).协程上下文,一般用调度器Dispatchers来切换线程,它是CoroutineContext接口的实现类.

  • Dispatchers.Main 协程代码执行在Android主线程
  • Dispatchers.Unconfined 不限制协程代码执行在哪个线程【主线程或者其他空闲线程】
  • Dispatcher.Default JVM共享线程池分配线程执行协程代码
  • Dispatcher.IO IO线程池分配线程执行协程代码

5.协程的挂起和阻塞

(1).suspend非阻塞式挂起函数
image.png
fun testCoroutineInActivity() {
       GlobalScope.launch(Dispatchers.Main) { //1.启动协程1
           println("Test001:执行在协程中...")
           GlobalScope.launch (Dispatchers.IO){//2.启动协程2
               println("Test001:异步执行result1")
               delay(1000)   
               println("Test001:result1:1234")
           }
           GlobalScope.launch(Dispatchers.IO) {//3.启动协程3
               println("Test001:异步执行result2.")
               delay(1000) 
               println("Test001:result2:123456")
           }
           println("Test001:执行完毕...")
       }
}
注意:协程2和协程3中的delay函数只是挂起了协程2和协程3,不会影响协程1中的主线程的执行.

执行结果如下:


image.png
(2).协程都被挂起了,那么挂起函数的函数体由谁执行呢?

当协程执行到挂起函数式,协程的执行会被暂停(即协程被挂起),但它并不会阻塞执行该挂起函数的线程,挂起函数的函数体任然由当前线程继续执行。
为了更清楚的解释这一点,我们可以使用一下步骤:
(1).协程启动:当一个协程开始运行时,它会在某个线程(Dispatchers.Main)上执行。
(2).遇到挂起函数:当协程遇到挂起函数时,协程的执行会被暂停。
(3).挂起函数执行:尽管协程被暂停了,挂起函数的函数体任然会在原始线程上执行。(除非该挂起函数明确指定了其他的执行上下文)
(4).挂起函数完成:一旦挂起函数完成其工作,它会通知协程库,然后协程会恢复执行。
注意:当协程被挂起时,主线程并没有被阻塞,而是可以执行其他的任务,等到挂起函数执行完毕,协程恢复时,又可以在主线程中继续执行。
总结一下:在协程中使用挂起函数时,任何可能得"阻塞"操作都会转移到其他线程上执行,这样启动协程的原始线程(例如主线程)就不会被实际阻塞,这样使得协程特别适合UI线程编程,因为它可以确保UI线程保持响应。

6.kotlin协程在工作中有用过吗?

kotlin协程是一个线程框架,提供了一种轻量级的并发处理方式,通过非阻塞挂起和恢复实现了用同步代码的方式编写异步代码,把原本运行在不同线程的代码写在一个代码块{}里面,看起来就像是同步代码。
协程的目的是,简化复杂的异步代码逻辑,用同步的代码编写方式实现复杂异步代码逻辑。

(1).几种封装好的协程
  • CoroutineScope(Dispatchers.IO)构造函数启动协程 ,通过Dispatchers指定运行的线程。


    image.png
  • GlobalScope启动协程(默认不是主线程)


    image.png
  • MainScope启动协程(默认运行在主线程)


    image.png
  • viewModelScope启动协程(默认运行在主线程)


    image.png
  • lifecycleScope启动协程(默认运行在主线程)


    image.png

协程就是一个线程框架,是对线程的封装,提供了一种轻量并发的处理方式,通过非阻塞式挂起和恢复的方式,用同步代码的方编写式实现复杂的异步代码逻辑,把原本运行在不同线程的代码写在一个代码块里面,看起来就像同步代码。

(2).如果一个页面需要同时并发请求多个接口,当所有的接口都请求完成需要做一些合并处理,然后更新UI,如何并发处理呢?
  • 方法一:为每个接口设置一个boolean值,每当接口请求成功,boolean值设置为true,当最后一个接口请求成功后,在更新UI,这样就达到了并发的目的 【管理多个boolean值,不够优雅,太累了】

  • 方法二:RXjava的zip操作符【达到发射一次,将结果合并处理的目的】

   fun testRxjavaZip(){
        println("-------------")
        val observable1: Observable<HttpResult<List<Banner>>> = HttpRetrofit.apiService.getBanners().subscribeOn(Schedulers.io()).observeOn(
            AndroidSchedulers.mainThread())
        val observable2:Observable<HttpResult<ArrayList<HomeData.DatasBean>>> =HttpRetrofit.apiService.getTopArticles().subscribeOn(Schedulers.io()).observeOn(
            AndroidSchedulers.mainThread())
        val observable3: Observable<HttpResult<List<KnowledgeData>>> = HttpRetrofit.apiService.getKnowledgeTree().subscribeOn(Schedulers.io()).observeOn(
            AndroidSchedulers.mainThread())
        var result1: HttpResult<List<Banner>>? =null
        var result2: HttpResult<ArrayList<HomeData.DatasBean>>? =null
        var result3: HttpResult<List<KnowledgeData>>? =null
        Observable.zip(observable1, observable2, observable3,
            object: Function3<
                    HttpResult<List<Banner>>,
                    HttpResult<ArrayList<HomeData.DatasBean>>,
                    HttpResult<List<KnowledgeData>>,
                    Boolean>{
                override fun apply(t1: HttpResult<List<Banner>>, t2: HttpResult<ArrayList<HomeData.DatasBean>>, t3: HttpResult<List<KnowledgeData>>): Boolean {
                    result1=t1
                    result2=t2
                    result3=t3
                    return t1!=null&&t2!=null&&t3!=null
                }
            }).subscribe(object:Observer<Boolean>{
            override fun onSubscribe(d: Disposable) {

            }

            override fun onError(e: Throwable) {

            }

            override fun onComplete() {

            }

            override fun onNext(t: Boolean) {
                if(t){//对结果进行处理
                    println("成功获取结果");
                    println(Gson().toJson(result1))
                    println(Gson().toJson(result2))
                    println(Gson().toJson(result3))
                }
            }
        });

    }
  • kotlin协程提供了一种轻量级的并发处理方式
    fun testCoroutineInActivity() {
      //1.launch启动协程,返回Job对象,通过job.cancel()取消任务
     val job:Job=CoroutineScope(Dispatchers.IO).launch() { 
            println("Test001:查看运行的线程1:"+Thread.currentThread().name)
            val start= System.currentTimeMillis()
            val result1=async {//运行在主线程,非异步
                println("Test001:查看运行的线程2:"+Thread.currentThread().name)
                delay(1000)
                "123"
            }
         //2.async  启动协程,得到有返回值的Deferred对象
            val result2:Deferred<String> = async(Dispatchers.IO) { //异步 
                println("Test001:查看运行的线程3:"+Thread.currentThread().name)
                delay(2000)
                 "456"

            }
            val result3=result1.await()+result2.await();
            println("Test001:并发耗时:"+ (System.currentTimeMillis() - start)+"||result3:"+result3)
            withContext(Dispatchers.IO){
                println("Test001:查看运行的线程4:"+ Thread.currentThread().name)
                delay(1000)
            }
            println("Test001:查看运行的线程5:"+ Thread.currentThread().name)
        }
    }
  • kotlin协程解决地狱回调问题【先调用接口1获取数据,然后拿到接口1的结果作为参数调用接口2】
// 注意:在真实开发过程中,MainScope作用域用的非常常用
MainScope().launch(){     // 注意:此协程块默认是在UI线程中启动协程
    // 下面的代码看起来会以同步的方式一行行执行(异步代码同步获取结果)
    val token = apiService.getToken()   // 网络请求:IO线程,获取用户token
    val user = apiService.getUser(token)// 网络请求:IO线程,获取用户信息
    nameTv.text = user.name             // 更新 UI:主线程,展示用户名
    val articleList = apiService.getArticleList(user.id)// 网络请求:IO线程,根据用户id获取用户的文章集合哦
    articleTv.text = "用户${user.name}的文章页数是:${articleList.size}页"   // 更新 UI:主线程
}

相关文章

  • 2021-12-10

    拓展函数 高阶函数 内联函数 lamda表达式 函数式编程 jetpack kotlin 协程 flow bind...

  • Kotlin学习之高阶函数和Lambda表达式的基础用法

    Kotlin学习之高阶函数和Lambda表达式的基础用法 一、定义和调用高阶函数的写法 kotlin.collec...

  • Kotlin 基础精华篇

    Kotlin 基础精华篇Kotlin 内联函数let、with、run、apply、alsoKotlin 协程学习...

  • Kotlin 协程学习总结

    Kotlin 基础精华篇Kotlin 内联函数let、with、run、apply、alsoKotlin 协程学习...

  • Kotlin 内联函数let、with、run、apply、al

    Kotlin 基础精华篇Kotlin 内联函数let、with、run、apply、alsoKotlin 协程学习...

  • 2020总结

    2020年的所有 Kotlin 协程 高阶函数 扩展函数 Flutter Android插件通讯 WEB上的兼容处...

  • Kotlin协程它不香吗?

    本博客的目的: 知道Kotlin协程是什么,为什么要用Kotlin协程 快速上手Kotlin协程 抓住核心,避免被...

  • Android版kotlin协程入门(四):kotlin协程开发

    kotlin协程在Android中的基础应用 通过前面的三个章节,现在我们已经了解了kotlin协程的基本使用和相...

  • Kotlin基础语法

    kotlin核心点:协成、高阶、闭包 一、Var与Val 二、函数 三、字符串模板 四、NULL检查机制 五、区间...

  • kot

    #Kotlin之班门弄斧 ##面向对象 ##java和kotlin的交互 ##协程及协程框架 ## 面向对象 ...

网友评论

      本文标题:Kotlin核心基础之高阶函数,协程基本用法

      本文链接:https://www.haomeiwen.com/subject/ddnsejtx.html