Kotlin-2-类与接口
1 类的继承与重写
1.1 没有主构造方法的继承:
/** * 没有主构造器,有次构造函数的时候 */ open class Animal{ //次构造函数的参数 不能使用var来修饰 有var代表该参数为成员变量,会生成对应的get set方法(主构造函数的参数可以使用var来修饰) constructor(name: String, age: Int){ //初始化 println("-------父类次级构造函数---------") } } /** * 子类 * * 使用: var dog = Dog("小狗",1) println(dog) dog.name = "修改后的名字" println(dog) */ class Dog : Animal { var name: String; var age: Int; constructor(name: String, age: Int) : super(name,age){ println("-------继承类的次级构造函数---------") this.name = name; this.age = age; } override fun toString(): String { return "重新toString():Dog{name=$name,age=$age}" } }
打印日志:
1.1 有主构造函数的继承(子类没有主构造方法):
/** * 有主构造函数 并且有成员变量name */ open class Animal(var name: String){ } class Dog : Animal { /** * 子类没有主构造函数,所以次构造函数需要调用父类的构造函数 */ constructor(name: String, age: Int) : super(name) { println("-------继承类的次级构造函数---------") } override fun toString(): String { return "重新toString():Dog{name=$name}" } }
使用与日志:
var dog = Dog("小狗",14) println(dog) dog.name = "修改后的名字" println(dog)
1.3 子类与父类都有主构造函数的继承:
/** * 有主构造函数 并且有成员变量name */ open class Animal(var name: String){ } /** * 因为Animal里面有了成员变量name,所以子类的name不能使用var修饰,只能当个方法参数来使用 */ class Dog(name: String,var age: Int) : Animal(name) { override fun toString(): String { return "重新toString():Dog{name=$name,age=$age}" //因为继承了Animal 所以能使用Animal里的name成员 } }
1.4 属性重写与方法重写:
/** * 有主构造函数 并且有成员变量name */ open class Animal(open var name: String){ /** * 如果该方法可以被子类重写,需要指定成open */ open public fun getPlay(){ println("执行了父类的getPlay()方法") } } /** * 因为Animal里面有了成员变量name,所以子类的name不能使用var修饰,只能当个方法参数来使用 */ class Dog(name: String,var age: Int) : Animal(name) { //属性重写 override var name: String get() = super.name set(value) { super.name = value + "_重写"; } //方法重写 override fun getPlay() { super.getPlay() println("执行子类的getPlay()方法") } override fun toString(): String { return "重新toString():Dog{name=$name,age=$age}" } }
使用与日志:
var dog = Dog("小狗",1) println(dog) dog.name = "修改后的名字" println(dog) dog.getPlay()
1.5 重写的方法出现方法名冲突时:
open class A { open fun f () { print("A") } } interface B { fun f() { print("B") } //接口的成员变量默认是 open 的 } //继承A 实现B接口 class C : A(), B{ /** * 当子类重写的父类,有重名方法时,需要指定调用 */ override fun f() { super<A>.f()//调用 A.f() super<B>.f()//调用 B.f() } }
使用与日志:
val c = C() c.f();//输出为:AB
2 实现接口-方法与属性的重写
/** * Kotlin 接口与 Java 8 类似,使用 interface 关键字定义接口,允许方法有默认实现: */ interface InterfaceA { /** * 接口中的属性只能是抽象的,不允许初始化值。实现接口时,必须重写该属性 */ var name: String? //? 代表 该属性值可为null值 fun methodA1() fun methodAB(){ println("执行了A接口方法的默认实现") } } interface InterfaceB { //与InterfaceA的方法重名 fun methodAB(){ println("执行了B接口方法的默认实现") } } //继承AppCompatActivity 并实现接口A与B class KotlinActivity : AppCompatActivity(),InterfaceA, InterfaceB{ /** * 重写接口A中的属性 */ override var name: String ?= null //由于接口中定义了name属性可为null 所以这里可以赋值为null 否则需要设置默认值 get() = field set(value) {field = value} override fun methodA1() { println("重写接口A的接口") } override fun methodAB() { super<InterfaceA>.methodAB() //指定 调用接口A的方法 super<InterfaceB>.methodAB() println("重写接口A与B的默认实现方法") } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_kotlin) methodA1() methodAB() println("name = $name") name = "修改name的值" println("name = $name") } }
打印日志:
3 扩展 与 扩展的作用范围
扩展函数可以在已有类中添加新的方法,不会对原类做修改,java中需要实现这样的功能需要继承该类,若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。
3.1 扩展属性
1.扩展属性允许定义在类或者kotlin文件中,不允许定义在函数中;
2.初始化属性因为属性没有后端字段(backing field),所以不允许被初始化,只能由显式提供的 getter/setter 定义;
3.扩展属性只能被声明为 val。
定义扩展属性
package com.kotlin.test.extend import com.kotlin.test.Dog /** * 创建一个kotlin文件,然后将扩展属性与函数写到该文件下,其他类需要使用的时候导入包就行 * 比如需要使用lastIndex的时候: * import com.kotlin.test.extend.lastIndex * import com.kotlin.test.extend.* 代表导入该包下所有的东东 * * 扩展函数与属性可以在类中定义,也可以在kotlin文件中定义, * 区别在于在类中定义的扩展函数只能在该类中使用, * 而在文件中定义的扩展函数与属性,使用者只需要导包就行 */ //为List集合扩展一个属性 val <T> List<T>.myLastIndex get() = this.size - 1 fun Dog.extendMethod(){ println("为Dog类定义一个扩展函数") }
测试代码:
import android.os.Bundle import android.support.v7.app.AppCompatActivity import com.kotlin.test.extend.myLastIndex //导包 import com.ts.gittest.R class KotlinActivity : AppCompatActivity(){ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_kotlin) var a = ArrayList<String>() a.add("1") a.add("1") a.add("1") println("集合a的长度sizi=${a.size}") //输出为:3 println("通过扩展属性获取集合a最后一个元素的索引值=${a.myLastIndex}") //输出为:2 } }
3.2 扩展函数
/** * 形式:需要扩展的类.方法名 * 若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。 */ fun A.functionName(){ println("为类A新增加了一个扩展函数") this.name = "修改后的值" } class A { public var name: String ?= null }
测试代码与打印日志:
var a = A() a.functionName() println("a的name=${a.name}") var b = A() println("b的name=${b.name}")
3.3 伴生对象的扩展(java中的静态)
class Dog { /** * 伴生对象的扩展(伴生对象跟java中的静态类似,该类的多个对象共享该伴生对象。) */ companion object { var name: String ?= null //静态变量
const val str = "静态常量" } } class KotlinActivity : AppCompatActivity(){ /** * 为Dog类的伴生对象扩展一个方法 * 组成部分:类.Companion.扩展方法名 */ fun Dog.Companion.extendMethod() { println("我是伴生对象的扩展函数") } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_kotlin) var dog = Dog() Dog.extendMethod() //伴生对象跟java的静态一样,直接使用类名.方法来调用 println("Dog的伴生对象属性=${Dog.name}") //调用伴生对象 } }
打印日志:
4.1 泛型
//类上定义泛型 class Box<T>(t: T) { var value = t } //Kotlin 泛型函数的声明与 Java 相同,类型参数要放在函数名的前面 fun <T> boxIn(value: T): T{ return value } //泛型约束 T为Dog的子类 fun <T : Dog> sort(list: List<T>) { }
声明处的类型变异使用协变注解修饰符:in、out
//out:该泛型参数可以用作方法的返回值类型,但是无法作为方法的输入参数类型 class Dog<out T>(val t: T){ fun methodTest(): T{ return t //因为t为out修饰 所以可用作输出类型,但是不能用做该方法的输入参数 } } // in:该泛型参数只能用作方法的输入参数类型,但是无法作为方法的返回值类型 class Animal<in T>(t: T) { //因为使用in修饰的T 所以t可以作为该方法的输入参数类型,但是不能作为返回值的类型 fun methodTest(t: T) { } }
5.1 枚举类
//枚举常量用逗号分隔,每个枚举常量都是一个对象。 enum class EnumClass{ A, B, C } /** * 常量可以被初始化,但是要告知常量的类型 * 枚举类的常量的两个属性值: * val name: String //获取枚举名称 * val ordinal: Int //获取枚举值在所有枚举数组中定义的顺序 * value:枚举类常量的值 */ enum class EnumClass2(val value: String){ A("11"), // B("12"), C("13") } //测试 val a1 = EnumClass.A Log.e("tag", "枚举名字=" + a1.name + ",顺序值=" + a1.ordinal + ",值= 没有为类指定类型,所以没有值") val a2 = EnumClass2.A Log.e("tag", "枚举名字=" + a2.name + ",顺序值=" + a2.ordinal + ",值=" + a2.value) Log.e("tag","通过名字获取枚举类常量:") val valueOf = EnumClass2.valueOf("A") //转换指定 name 为枚举值,若未匹配成功,会抛出IllegalArgumentException Log.e("tag", "枚举名字=" + valueOf.name + ",顺序值=" + valueOf.ordinal + ",值=" + valueOf.value) Log.e("tag","遍历枚举类的所有值:") var values: Array<EnumClass2> = EnumClass2.values(); for (value in values) { Log.e("tag", "枚举名字=" + value.name + ",顺序值=" + value.ordinal + ",值=" + value.value) }
打印日志:
6.1 object的使用
6.1.1 对象表达式—— 对象表达式是在创建他们的地方立即初始化的
class KotlinActivity : AppCompatActivity(){ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_kotlin) //使用匿名对象继承基类,避免需要创建子类的麻烦 var obj = object : A(2),B { override fun bMethod(): Int { println("重写了B接口的方法") return this.y } } println("bMethod() = ${obj.bMethod()}") //注意:如果你使用匿名对象作为公有函数 println("${aMethod().x}") //println("${bMethod().x}") 不能调用.x ,因为返回类型为 Any } open class A(x: Int){ public var y: Int = x } interface B{ public fun bMethod(): Int } //使用匿名对象作为私有方法 返回类型为:匿名对象本身 private fun aMethod() = object { var x: String = "x" } //使用匿名对象作为共有方法 返回类型为:Any public fun bMethod() = object { var x: String = "x" } }
日志:
6.1.2 对象声明(使用object修饰类来定义单例) —— 对象声明是在第一次被访问到时初始化的
class KotlinActivity : AppCompatActivity(){ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_kotlin) var a = SingtonClass var b = SingtonClass a.printlnClass() b.printlnClass()//打印的地址一致 //引用该对象,我们直接使用类名即可: SingtonClass.printlnClass() //直接使用类名调用 KotlinActivity.SingtonClass.printlnClass() } /** * 使用object关键字修饰的类 该类为单例模式 * 对象声明是在第一次被访问到时延迟初始化的 * 如果有外部类,不能访问到外部类的方法和变量 */ object SingtonClass{ fun printlnClass(){ println(this) } } }
6.1.3 伴生对象(java中的静态) —— 类被加载的时候初始化的
class Animal{ /** * * 伴生对象 跟java中的静态类似,伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配 * * 静态常量与方法 * * 调用方法: * 所在类.Companion.testMethod() */ companion object { //常量 调用:Animal.imgPath public const val imgPath: String = "extra_imgPath" /** * 调用: * Animal.Companion.getName() * Animal.Companion.setName() */ public var name: String ?= null; /** * 调用: * Animal.Companion.testMethod() */ public fun testMethod(): String?{ return this.toString() } } }
注意:一个类里面只能声明一个内部关联对象,即关键字 companion 只能使用一次。 跟java静态不一样的是,伴生对象可以继承基类与实现接口
7 委托
7.1 类委托
// 创建接口 interface Base { fun print() } // 实现此接口的被委托的类 class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } // 通过关键字 by 建立委托类 因为委托了b 所以不需要重写Base的接口 class Derived(b: Base) : Base by b{ } val b = BaseImpl(10) Derived(b).print() // 输出 10 执行BaseImpl类的方法
7.2 属性委托
// 定义包含属性委托的类 class Example { //属性name 委托Delegate类,所以Delegate类需要定义getValue与setValue方法 var str: String by Delegate() var age: String by Delegate() } // 委托的类 class Delegate { val map: HashMap<String,Any> = HashMap<String,Any>() /** * thisRef:被委托的类(这里为Example) * property.name:属性名(这里为str) */ operator fun getValue(thisRef: Any?, property: KProperty<*>): String{ println("getValue(),类$thisRef, 这里委托了 ${property.name} 属性") if(map.get(property.name) is String){ return map.get(property.name) as String } return "不是字符串类型" } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("setValue(),类$thisRef 的 ${property.name} 属性赋值为 $value") //将属性名作为key map.put(property.name,value) } } //测试 var example = Example() example.str = "str_value" //会执行Example类的 setValue() example.age = "age_value" println(example.str) //会执行Example类的 getValue() println("==========") println(example.age)
日志:
7.3 标准委托——延迟加载 Lazy
/** * 也就是当第一次加载的时候执行下面所有的表达式,再次执行时只会直接返回结果 */// 注意:延迟加载的变量只能是常量 private val lazyValue: String by lazy { Log.e("tag", "执行延迟加载") "value" //这里可以执行任意表达式 //最后的返回值 "最后一行必须是延迟加载的返回值" }
输出结果:
/** 执行延迟加载 第一次执行:lazyValue = 最后一行必须是延迟加载的返回值 第二次执行:lazyValue = 最后一行必须是延迟加载的返回值 */ Log.e("tag","第一次执行:lazyValue = " + lazyValue) Log.e("tag","第二次执行:lazyValue = " + lazyValue)
7.4 标准委托——可观察属性
class User { /** * 返回更改时调用指定回调函数的读/写属性的属性委托。 */ //initialValue属性的初始值 var name: Int by Delegates.observable(0) { property, old, new -> Log.e("tag","name的值被改变") Log.e("tag", "$old -> $new") } /** * 返回一个读/写属性的属性委托,在更改时调用指定的回调函数,允许回调否决修改。 * * 如果回调函数返回“true”,则该属性的值将被设置为新值, * 如果回调函数返回“false”,则丢弃该新值并且该属性保持其旧值。 */ var gender: Int by Delegates.vetoable(0) { property, old, new -> Log.e("tag","gender的值被改变") old < new //为true时,才会更改gender的值 } } fun test(){ val user = User() user.name = 2 // 输出 0 -> 2 user.name = 1 // 输出 2 -> 1 user.gender = 2 Log.e("tag",user.gender.toString()) // 输出 2 user.gender = 1 //由于 1 < 2 所以没做修改gender的值 Log.e("tag", user.gender.toString()) // 输出 2 }
日志:
7.5 标准委托——map映射
class User(val map: Map<String, Any?>) { val name: String by map //user.name 时,会通过map去取值 val age: Int by map } val user = User(mutableMapOf( "name" to "小明", //创建一个类型为[Pair]的元组。 "age" to 15)) //如果map中没有name这个键值对 运行时会报错 println(user.name) // 输出:"小明" println(user.age) // 输出:15
8.Kotlin的扩展程序-kotlin-android-extensions
作用:不需要在activity、fragment里使用findViewById就能找到控件了,文档地址
buildscript { ext.kotlin_version = '1.2.30' repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.1.3' //添加插件依赖 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } }
在app的builder.gradle中添加:
apply plugin: 'com.android.application' //依赖插件脚本,如下两个插件缺一个不可,kotlin-android为基本支持android 开发插件,kotlin-android-extensions就是省去我们频繁找id的插件了 apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' dependencies { //还需要添加这个 implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" }
然后在activity中找控件就变成这样了:
//自动导包的 import kotlinx.android.synthetic.main.activity_about_we.* class KotlinTestActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_about_we) //直接使用布局中的id就可以了 llUpdata.visibility = View.GONE } }
官方文档说明:https://www.kotlincn.net/docs/tutorials/android-plugin.html
9.kotlin 协程的使用(异步操作)
线程与协程的对比:协程更轻量级,占用更少的系统资源; 更高的执行效率;
协程:不阻塞当前线程,在后台创建一个新协程,也可以指定协程调度器,运行在指定线程中
协程的启动方式有两种:
1.launch启动协程,不能获取协程执行的结果
2.async启动,可以获取协程中执行的结果
在android中的使用,需要先添加依赖
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
并且kotlin的版本需要1.3.20以上
9.1 简单例子:
效果图:
将上面的GlobalScope异步操作拆开来写:
效果图:
也可以指定在UI线程中启动一个协程:
9.2 runBlocking 阻塞线程的使用:
runBlocking也能指定运行在子线程还是主线程,但是这个不应该在协程中使用,主要是为main
函数和测试设计的。
fun method2(){ println("开始") val launch = GlobalScope.launch{ //在后台启动一个新的线程并继续(无阻塞) delay(2000L) println("World!" + ",线程:" + Thread.currentThread().name) } println("Hello," + ",线程:" + Thread.currentThread().name) // runBlocking 跟 delay不同的是,delay只能在子线程中使用, // 并且runBlocking 是阻塞的,runBlocking里面的代码没跑完会停在这 runBlocking { println("runBlocking," + ",线程:" + Thread.currentThread().name) delay(3000L) // ……我们延迟 3 秒来保证 JVM 的存活 } println("结束") }
效果图:
9.3 使用join,等待子线程执行完毕
效果图:
再比如这样一个例子:
效果图:
9.3 取消协程 (可看出协程的魅力,与线程的不同之处)
fun setup(textView: TextView, btn: View) { val job = GlobalScope.launch(Dispatchers.Main) { // 在主线程中启动协程 for (i in 10 downTo 1) { // 从 10 到 1 的倒计时 textView.text = "倒计时: $i, 当前线程:${Thread.currentThread().name}" // 更新文本 /** * delay 函数 只能在协程中调用 * 当 delay 函数的等待期间UI 并不会冻结,因为它不会阻塞 UI 线程——它只会挂起协程。 */ delay(1000) // 等待1秒钟, //Thread.sleep(1000) 与 delay不同的是,sleep会阻塞线程 } textView.text = "结束" } btn.setOnClickListener { job.cancel()// 在点击时取消协程,它可以在任何地方被调用。 在已经取消或已完成的协程上调用它不会做任何事情。 } }
效果图:
9.4 设置协程的超时时间:
withTimeoutOrNull:超时后返回null
runBlocking { //设置协程的超时时长,这里代表1.3秒后超时 val result = withTimeoutOrNull(1300L) { repeat(1000) { i -> println("挂起半秒,次数i = $i ...") delay(500L) } "结束后返回的结果" // 方法的返回值 } println("结果 $result") }
日志:
withTimeout:超时后报异常 TimeoutCancellationException
9.5 通过async启动协程:
@Test fun addition_isCorrect() { assertEquals(4, 2 + 2) runBlocking { /** * 通过 async 启动协程,有返回值,通过launch启动的没有 * await 代表等待协程的返回结果 */ val defer1 = GlobalScope.async { job1() } defer1.join() defer1.cancel() val result1 = defer1.await(); val result2 = GlobalScope.async { job2() }.await() val result3 = GlobalScope.async { job3() } .await() println(result1) println(result2) println(result3) } } /** * suspend 代表这是一个挂起函数 */ suspend fun job1(): String { println("job1开始执行了") delay(2000L)//挂起 2秒 println("job1执行结束了") return "job1的返回值" } suspend fun job2(): String { println("job2开始执行了") delay(2000L) println("job2执行结束了") return "job2的返回值" } suspend fun job3(): String { println("job3开始执行了") delay(2000L) println("job3执行结束了") return "job3的返回值" }
效果图:
9.6 协成调度(切换线程):
import kotlinx.coroutines.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var block: suspend CoroutineScope.() -> Unit = { Log.e("tag","当前线程:${Thread.currentThread().name}") //withContext:使用给定的协程上下文调用指定的挂起块,直到完成并返回结果 val result = withContext( context = Dispatchers.IO, block = { Log.e("tag", "切换Context后的线程:${Thread.currentThread().name}") return@withContext "返回的结果" } ) Log.e("tag","当前线程:${Thread.currentThread().name},拿到切换后的线程的结果:$result") } GlobalScope.launch( context = Dispatchers.Main, block = block ) Log.e("tag","onCreate() 结束") } }
效果图: 需要注意的是 这里主线程执行结束后,协成才开始执行
由于博客园代码不支持kotlin,下面给一个截图,看上面的简写:
协程学习例子:
秒懂协程的代码:
suspend fun method1() { withContext(Dispatchers.IO) { delay(3000) Log.e("test", "method1") } } suspend fun method2() { Log.e("test", "method2") } //输出日志: //20:38:25 test: end //20:38:28 test: method1 //20:38:28 test: method2 fun main() { //lifecycleScope 为Activity提供的scope lifecycleScope.launch { method1() method2() } Log.e("test", "end") } //上面的main方法 等价于下面代码 fun main() { Thread(Runnable { Thread.sleep(3000) Log.e("test", "method1") runOnUiThread { Log.e("test", "method2") } }).start() Log.e("test", "end") }
使用lifecycleScope:
当Activity执行onDestroy() 后,lifecycleScope会执行coroutineContext.cancel()
fun method() { lifecycleScope.launch { Log.e("tag", "Main 1 线程开始---") val result = withContext(Dispatchers.IO) { Thread.sleep(5000) //delay(5000) if (this.isActive){ //判断协程是否被取消了 Log.e("tag", "IO 1 isActive = ${isActive}") } Log.e("tag", "IO 1 线程执行完毕") "结果" } Log.e("tag", "Main线程执行完毕: result = ${result}") } }
协程没取消,正常情况下的输出日志:
如果在sleep的5秒内,退出了activity,此时协程被取消,输出日志如下:
可见,协程被取消后,子线程还是在运行的,只是主现场的代码不会执行了,原因是内部抛出了CancellationException的异常,如果try catch一下,比如:
fun method() { lifecycleScope.launch { Log.e("tag", "Main 1 线程开始---") try { withContext(Dispatchers.IO) { Thread.sleep(5000) Log.e("tag", "IO 1 线程执行完毕") } } catch (e: Exception) { Log.e("tag", "Exception 1 = ${e.message}")//JobCancellationException } Log.e("tag", "Main线程执行完毕") } }
输出日志:
总结: 当协程被取消后,会抛出JobCancellationException,该异常不会导致程序崩溃,如果是其他异常会崩溃, 可以tryCatch来捕获异常
使用suspendCancellableCoroutine将回调函数转换为协程:
fun method() { lifecycleScope.launch { val result = withContext(Dispatchers.IO) { var aaa: String? = null try { aaa = getResult() } catch (e: Exception) { } aaa } Log.e("tag", "result = $result") } } //使用suspendCancellableCoroutine将回调函数转换为协程,可以通过 cancel() 方法手动取消协程的执行 suspend fun getResult(): String = suspendCancellableCoroutine { cancellableContinuation -> cancellableContinuation.invokeOnCancellation { Log.e("tag", "协程被取消的回调 -> invokeOnCancellation: exception = ${it}") } Thread { Thread.sleep(5000) if (System.currentTimeMillis() % 10 > 6) { if (cancellableContinuation.isCancelled) return@Thread cancellableContinuation.resume("返回的内容") } else { cancellableContinuation.resumeWithException(Exception("抛出异常")) //程序会崩溃, 所以调用该方法的地方 需要try catch } }.start() }
协成中的内存泄漏,取消协成,协成之间的父子关系,GlobalScope与CoroutineScope的区别:
协成之间的关系 可参考:
https://www.jianshu.com/p/2857993af646
https://mp.weixin.qq.com/s/Tv-jEjJAn_gZ_M3qBG4Azw
目的:防止协成的内存泄漏,统一取消协成(启动一个协程需要指定CoroutineScope,当要取消协程的时候只需要调用CoroutineScope.cancel() ),
比如ViewModle中为我们提供了一个viewModelScope
协程间父子关系有三种影响:
1.父协程手动调用cancel()或者异常结束,会立即取消它的所有子协程。
2.父协程必须等待所有子协程完成(处于完成或者取消状态)才能完成。
3.子协程抛出未捕获的异常时,默认情况下会取消其父协程。
协程与Retrofit2.6的配合,指定协程的Job,统一关闭协程:
class MainActivity : AppCompatActivity() { /** * 管理用该CoroutineScope启动的协程 */ val presenterScope: CoroutineScope by lazy { //指定Job 方便统一处理取消协程 CoroutineScope(Dispatchers.Main + Job()) } /** * 需要注意的是 调用了cancle后,presenterScope就不能再次开启协程了 */ private fun endCoroutine(){ presenterScope.cancel() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btnStart.setOnClickListener{ Log.e("tag","开始") presenterScope.launch { try { val result = withContext(Dispatchers.IO){ val login = login() Log.e("tag","结果:${login.toString()}") return@withContext login } //主线程 Log.e("tag","回调:${Thread.currentThread().name}") }catch (e: CancellationException){ Log.e("tag","协程被取消 ${e.message}") } catch (e: Exception) { e.printStackTrace() Log.e("tag","网络请求失败:${e.javaClass.name} ${e.message}") } } Log.e("tag","结束") } //取消协程 btnEnd.setOnClickListener { endCoroutine() } } private suspend fun login(): WanResponse<Any> { val apiService = Retrofit.Builder() .baseUrl(ApiService.BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build() .create(ApiService::class.java) val response = apiService.login("130******","123456") return response } }
retrofit配合协程取消网络请求的原理:
https://blog.csdn.net/knight1996/article/details/102492883 (可见:内部使用的挂起函数,是异步执行的),所以下图这样也是没问题的
注意点:协程的cancel()原理是改变了协程对象的内部状态,但并没有终止逻辑代码的执行,具体看下面例子:
//当调用 viewModelScope.cancel()后,下面的日志会中断 viewModelScope.launch { withContext(Dispatchers.IO){ var i = 1 while (this.isActive){ //viewModelScope.cancel()后 isActive就为false了,改变了协程的活动状态 Thread.sleep(1000) Log.e("tag","i = ${i++} 当前线程:${Thread.currentThread().name}") } } } //当调用 viewModelScope.cancel()后,下面的日志并不会中断,会继续一直打印 viewModelScope.launch { withContext(Dispatchers.IO){ var i = 1 while (true){ Thread.sleep(1000) Log.e("tag","i = ${i++} 当前线程:${Thread.currentThread().name}") } } }