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.pngfun 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:主线程
}
网友评论