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 协程的使用(异步操作)

文档说明    文档中的例子     所需库的github地址

线程与协程的对比:协程更轻量级,占用更少的系统资源; 更高的执行效率;

协程:不阻塞当前线程,在后台创建一个新协程,也可以指定协程调度器,运行在指定线程中

协程的启动方式有两种:

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}")
                }
            }
        }

 

ViewModel与Flow的使用(配合retrofit):

ViewModel与Flow的使用

posted @ 2018-03-28 15:41  ts-android  阅读(110)  评论(0编辑  收藏  举报