Kotlin入门

106 阅读11分钟

Kotlin入门

参考资料

Kotlin 官方文档 中文版 book.kotlincn.net/ Kotlin 协程 blog.csdn.net/weixin_4481…

Kotlin代码转JAVA

Tools->kotlin->Show Kotlin Bytecode-> Decompole

一、基础语法

    var str = "abc"
    fun getName : Boolean(){
        return true
    }


    类 class Person { /*……*/ }
    类 构造函数
    class Person constructor(firstName: String) { /*……*/ }
    如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。
    class Person(firstName: String) { /*……*/ }
    class Person(val firstName: String, val lastName: String, var isEmployed: Boolean = true)


    继承 open class Base(p: Int) // 该类开放继承
    class Derived(p: Int) : Base(p)

    方法
    fun do(){

    }

    fun do() : Unit{

    }

    声明属性
    class Address {
        var name: String = "Holmes, Sherlock"
        var street: String = "Baker"
        var city: String = "London"
        var state: String? = null
        var zip: String = "123456"
    }

    fun copyAddress(address: Address): Address {
        val result = Address() // Kotlin 中没有“new”关键字
        result.name = address.name // 将调用访问器
        result.street = address.street
        return result
    }


    for (i in 0 until 100) {
        println(i) // 输出: 0 ~ 99
    }

    for (i in 0..100) {
        println(i) // 输出: 0 ~ 100
    }

    for (i in 100 downTo 0) {
        println(i) // 输出: 100 ~ 0
    }

    for (i in 0..10 step 2) {
        print("${if (i == 10) i else "$i--"}") // 输出: 0--2--4--6--8--10
    }


    when(animal) {
        EAGLE, DUCK -> println("鸟类")
        DOLPHIN, TIGER -> println("兽类")
        CARP -> println("鱼类")
        LOCUST -> println("昆虫类")
        else -> println("未知动物")
    }

    object.isEmpty
    object.isBlank
    object?.let{}?:{}
            
 // Java接口回调
mVar.setEventListener(new ExamEventListener(){
 
    public void onSuccess(Data data){
      // ...
    }
 
 });

// 同等效果的Kotlin接口回调(无使用lambda表达式)
mVar.setEventListener(object: ExamEventListener{
     
    public void onSuccess(Data data){
      // ...
    } 
});

// Kotlin接口回调(使用lambda表达式,仅留下参数)
mVar.setEventListener({
   data: Data ->
   // ... 
})

// 继续简化
// 简化1:借助kotlin的智能类型推导,忽略数据类型
mVar.setEventListener({
   data ->
   // ... 
})

// 简化2:若参数无使用,可忽略
mVar.setEventListener({
   // ... 
})

// 简化3:若setEventListener函数最后一个参数是一个函数,可把括号的实现提到圆括号外
mVar.setEventListener(){
   // ... 
}

// 简化3:若setEventListener函数只有一个参数 & 无使用到,可省略圆括号
mVar.setEventListener{
   // ... 
}

二、var val 及常用数据类型

常用数据类型:Int、Long、Float、Double、Short、Byte、char、String、Boolean


Boolean true和false
String

    var str = """ 
            abcdefg 
            hijklmn 
            opqrst 
            uvwxyz """

var val const
var 表示可变(mutable),
val 表示只读(read-only)而不是不可变(immutable)val 等价于 Java 的 final
const 只能修饰没有自定义 getter 的 val 属性,而且它的值必须在编译时确定。类似Java 中可以使用 static final
[const val相对于val 不会生成get方法。性能更好] 1.只能是:字符串和基础类型。【String or a primitive type】 2.只能修饰:全局常量【top level】、object的成员、companion 对象的成员 3.const不能单独修饰某个变量,必须和val同时修饰一个变量。

class Address { 
    var province = "zhejiang" 
    val city = "hangzhou" 
}
public final class Address { 
    public final java.lang.String getProvince(); 
    public final void setProvince(java.lang.String); 
    public final java.lang.String getCity(); 
    public Address(); 
}

val只读(read-only),而不是不可变(immutable)。

class Person {
  var name :String = "liangfei"
  var age :Int = 30

  val nickname: String
    get() {
      return if (age > 30) "laoliang" else "xiaoliang"
    }

  fun grow() {
    age += 1
  }
}

转存失败,建议直接上传图片文件
提示:Const 'val' are only allowed on top level, in named objects, or in companion objects 只可以声明在顶级,或者使用companion objects声明

三、Object与companion objects

juejin.cn/post/684490…
区别:
companion object 类中只能一个 声明周期跟类同步,Only one companion object is allowed per class
objcet 没有限制声明 更多用在对象声明,对象表达式使用。 可以声明在类里 也可以声明在顶级包下 top-level declaration
使用原则:
如果想写工具类的功能,直接创建文件,写 top-level「顶层」函数。(声明在包下) 如果需要继承别的类或者实现接口,就用 object 或 companion object。

1.object关键字可以表达两种含义:一种是对象表达式,另一种是 对象声明。

对象表达式

val textView = findViewById<TextView>(R.id.tv)
textView.setOnClickListener(object : OnClickListener {
        override fun onClick(p0: View?) {
            Toast.makeText(this@TestActivity, "点击事件生效", Toast.LENGTH_LONG)
        }

})
上面代码其实就是我们经常要给 `view` 设置的点击事件,
`OnClickListener` 事件是一个匿名类的对象,用`object`来修饰。

对象声明

直接声明类
用object 修饰的类为静态类,里面的方法和变量都为静态的。
object DemoManager {
    private val TAG = "DemoManager"
        
    fun a() {
        Log.e(TAG,"此时 object 表示 声明静态内部类")
    }
    
}


又可以声明静态内部类
类内部的对象声明,没有被inner 修饰的内部类都是静态的
class DemoManager{
    object MyObject {
        fun a() {
            Log.e(TAG,"此时 object 表示 直接声明类")
        }
    }
}


 kotlin中调用
 fun init() {
    MyObject.a()
 }

 java中调用
 MyObject.INSTANCE.a();


#####object单例 饿汉式 反编译代码

object StringUtils {
    fun getLength(text: String?): Int = text?.length ?: 0
}

//反编译
public final class StringUtils {
   @NotNull
   public static final StringUtils INSTANCE;  //静态单例对象

   public final int getLength(@Nullable String text) {
      return text != null ? text.length() : 0;
   }

   private StringUtils() {
   }

   static {  //静态代码块
      StringUtils var0 = new StringUtils();
      INSTANCE = var0;
   }
}

2.companion object

companion object 修饰为伴生对象,伴生对象在类中只能存在一个,类似于java中的静态方法 Java 中使用类访问静态成员,静态方法。

companion object {
    private val TAG = "DemoManager"

    fun b() {
        Log.e(TAG,"此时 companion objec t表示 伴生对象")
    }
}

kotlin 中调用
fun init(){
       b()
}


java 中调用
复制代码
DemoManager.Companion.b();

Kotlin
object AA {
    const val HVAC_POWER_MODE = "car.hvac.power_mode"
}
Java反编译
public final class AA {
   @NotNull
   public static final String HVAC_POWER_MODE = "car.hvac.power_mode";
   @NotNull
   public static final AA INSTANCE;

   private AA() {
   }

   static {
      AA var0 = new AA();
      INSTANCE = var0;
   }
}

----------------
Kotlin
class BB {
    companion object {
        const val BB_VALUE = "bbvalue"
    }
}
Java反编译  
public final class BB {
   @NotNull
   public static final String BB_VALUE = "bbvalue";
   @NotNull
   public static final BB.Companion Companion = new BB.Companion((DefaultConstructorMarker)null);

   public static final class Companion {
      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}
----------------  
Kotlin
文件名 CC.kt
const val CC_VALUE = "cc_value"

Java反编译
public final class CCKt {
   @NotNull
   public static final String CC_VALUE = "cc_value ";
}

四、get set

www.jianshu.com/p/ba3869f16…
默认情况下每个属性都具有getter/setter方法
声明一个属性的完整语法如下:

var <propertyName>[: <PropertyType>] [= <property_initializer>] 
    [<getter>]
    [<setter>]

属性初始值、getter/setter是可缺省,如果属性类型可以从初始值或getter中推断出来则也可缺省。val类型的属性不具备setter。

属性的getter/setter均可复写,即自定义访问器。如果我们定义了一个自定义的setter,那么每次给属性赋值时都会调用它。

open class Person {
    var age: Int = 10
        //getter缺省为默认
        //setter设置参数前打印参数
        set(value) {
            println("setter $value")
            //field关键字指向属性本身
            field = value
        }
}

@JvmStatic
fun main(args: Array<String>) {
    val p = Person()
    println(p.age)
    p.age = 30
    println(p.age)
}

JAVA思想 例如viewmmodel对外提供String

class Test {
    private var _name = "aaa"
    val name :String
        get() = _name
        
    //kotlin 
    var age = "age"
        private set

}

五、# 内置函数let、also、with、run、apply

let & run

  • 两者都是T的扩展函数,也就是任何类型对象都调用run、let;
  • 两者的返回值是:最后一行非赋值代码作为闭包的返回值,否则返回Unit;
  • run的闭包使用this来访问函数调用者,let的闭包使用it来访问函数的调用者。

apply & also

  • 两者都是T的扩展函数,也就是任何类型对象都调用apply、also;

  • 两者的返回值都是this,也就是函数调用者;

  • apply的闭包使用this来访问函数调用者,also的闭包使用it来访问函数的调用者。

1 let使用方法

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


// 作用1:使用it替代object对象去访问其公有的属性 & 方法
object.let{
   it.todo()
}

// 作用2:判断object为null的操作
object?.let{//表示object不为null的条件下,才会去执行let函数体
   it.todo()
}

// 注:返回值 = 最后一行 / return的表达式

String?.let{"99"}?:"0"

let使用示例 定义一个变量在特定的范围域内,避免一些判断null的操作

// 使用Java
if( mVar != null ){
    mVar.function1();
    mVar.function2();
    mVar.function3();
}

// 使用kotlin(无使用let函数)
mVar?.function1()
mVar?.function2()
mVar?.function3()

// 使用kotlin(使用let函数)
// 方便了统一判空的处理 & 确定了mVar变量的作用域
mVar?.let {
       it.function1()
       it.function2()
       it.function3()
}

2 also使用方法

类似let函数,但区别在于返回值:

  • let函数:返回值 = 最后一行 / return的表达式
  • also函数:返回值 = 传入的对象的本身

使用示例

public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}


// also函数
var result = mVar.also {
               it.function1()
               it.function2()
               it.function3()
               999
}
// 最终结果 = 返回一个mVar对象给变量result

3 run使用方法

结合了let、with两个函数的作用,即:

  1. 调用同一个对象的多个方法 / 属性时,可以省去对象名重复,直接调用方法名 / 属性即可
  2. 定义一个变量在特定作用域内
  3. 统一做判空处理

使用方法

public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

object.run{
// ... 
}
// 返回值 = 函数块的最后一行 / return表达式

使用示例

// 此处要调用people的name 和 age属性,且要判空
// kotlin
val people = People("carson", 25)
people?.run{
    println("my name is $name, I am $age years old")
}

// Java
User peole = new People("carson", 25);
String var1 = "my name is " + peole.name + ", I am " + peole.age + " years old";
System.out.println(var1);

4 apply使用方法

作用 & 应用场景

与run函数类似,但区别在于返回值:

  • run函数返回最后一行的值 / 表达式
  • apply函数返回传入的对象的本身

应用场景

对象实例初始化时需要对对象中的属性进行赋值 & 返回该对象

使用示例

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

kotlin
复制代码
// run函数
val people = People("carson", 25)
val result = people?.run{
    println("my name is $name, I am $age years old")
    999
}
// 最终结果 = 返回999给变量result

// Java
val people = People("carson", 25)
val result = people?.run{
    println("my name is $name, I am $age years old")
    999
}
// 最终结果 = 返回一个people对象给变量result

5 with使用方法

fun main(args: Array<String>) {  
    val book = Book()  
  
    with(book) {  
        name = "《计算机网络》"  
        price = 40  
    }  
    print(book)  
}

image.png

六、?、?:、!!、:: 特殊符号

符号一:?

声明一个变量可为空。

var userData : UserData? = null

符号二:?.

表示这个变量可以为null。该变量如果为null时,不会执行该变量后面的逻辑,也不会抛出空指针异常,俗称空安全。如果不为null,会正常执行该变量后面的内容。

UserData?.name  

符号三:!!

非空断言运算符 加在变量后面,表示该变量如果为null时,会抛出空指针异常,像java语法一样空指针不安全;如果不为null,才会正常执行该变量后面的内容。

UserData!!.name

符号四:?:

Elvis操作符: A ?: B 如果A 的值为空,则会返回B。一般不推荐用!!,建议使用?:来规避崩溃、报错的发生

println(UserData ?: "当UserData为空时执行")

符号五:::

Kotlin 中 双冒号操作符 表示把一个方法当做一个参数,传递到另一个方法中进行使用,通俗的来讲就是引用一个方法。

class ColonMagic {

    fun main(msg: Array<String>) {
        //调用peopleDo方法,并传入参数  “I say !” 和 say方法体
        //此处 ::say 是将say方法作为参数传入peopleDo方法
        //此处只是将say作为参数,而不会调用say方法,也就不会println(message),不会输出日志
        peopleDo("I say !", ::say)
    }

    /**
     * 一个参数
     * message:String类型
     */
    private fun say(message: String) {
        println(message)
    }

    /**
     * 两个参数
     * msg: String类型
     * dosay: (String) -> Unit 一个参数为String不需要返回值的方法体
     */
    private fun peopleDo(msg: String, doSay: (String) -> Unit) {
        //doSay(msg)调用的就是传入的say方法,即say(msg),只不过参数名是doSay本质是say方法
        //此处打印出来 I say !  日志
        doSay(msg)
    }
}

符号六:===


=就是赋值,
== 相当于Java中的equls()用于比较对象的结构(内容)是否相等(对象的结构就是属性和方法),
=== 用于比较对象的引用是否指向同一个对象,运行时如果是基本数据类型===等价于 ==

符号七:…

range表达式 表示多少到多少之间

if(num in 10..100)//表示10..100 表示 10到100

符号八:

反引号
1、Kotlin中可以用反引号解决关键字冲突问题
2、将一个不合法的字符变为合法字符,这种情况一般可以用在一个Kotlin方法不希望被Java调用时使用,因为Java语言层面上不支持反引号这种语法。

fun `is`(){
    //用is这种关键字去命名函数
}

fun `哈哈哈哈`(){
    //用中文这种不合法字符去命名函数   
}

符号九:$

拼接符号

"货号 ${data.code}"    //将data.code值拼接到货号后 
//相当于java中
"货号" + data.code

符号十:@

1、限定this的类型

class User {
    inner class State{
        fun getUser(): User{
            //返回User
            return this@User
        }
        fun getState(): State{
            //返回State
            return this@State
        }
    }
}

2、跳转到指定标签处,可以直接跳出多层循环。像汇编语言中的Loop

loop@ for (itemA in arraysA) {   //直接跳出双循环
     var i : Int = 0
      for (itemB in arraysB) {
         i++
         if (itemB > 2) {
             break@loop
         }
         println("itemB:$itemB")
     }
}

使用注意

(1..7).forEach {
    if (it == 3) {
        return@forEach
    }
    Log.d("TAG", "Num: $it")
}
不能跳出循环 只能跳出当前循环 相当于continue

正确写法
run loop@{
    (1..7).forEach {
        if (it == 3) {
            return@loop
        }
        Log.d("TAG", "Num: $it")
    }
}
  

七、数据类(data) 密封类(sealed)枚举类(enum)

数据类


1.数据类以data字段来表示是一个数据类,类似java中的javabean,用于保存数据
2.数据类至少包含一个主构函数,其不能继承其他类,但可以实现其他接口
3.当你创建一个数据类的时候,编译器会从构造函数中提取如下四个函数
  1)equals: 用于比较两个数据对象是否相等
  2)toString
  3)一组componentN函数,这里的N与主构造函数中声明的属性数量是相同的,其对应第N个数据
  4)copy:完整复制某个数据对象

data class DataClass constructor(var name: String, var age: Int)


data class DataClass constructor(var name: String, var age: Int){
    public fun printMessage(){
    }
}

使用的时候和普通类一样创建即可, data class比class 多实现了 toString()、hashCode()、equals()、copy()、componentN()

var dataClas = DataClass("张三")
dataClas.toString();
println("数据类的component2方法 " + dataClas.component2());

// 输出为:
// 张三
// 数据类的component2方法 10

这里的toString不是Any类中的,是编译器为DataClass提取的函数,对于数据类,我们可以使用类似ES6中的解构解析的方式来赋值变量

var data = DataClass(name=张三, age=10)
println("数据类的解构解析 $data");
// 输出为: 数据类的解构解析 DataClass(name=张三, age=10)

此时我们就可以用解构解析的方式来获取属性了

var (name, age) = dataClas
println("数据类的解构解析name $name");
// 输出为:数据类的解构解析name 张三

密封类

1 . 使用 sealed 修饰类,密封类可以有子类,但是所有的子类都必须要内嵌在密封类中,所以我们可以在when表达式中利用密封类来做穷举半段

2 . 密封类子类的子类可以放在任何位置

3 . 它不能直接实例化

sealed class SealedClass{

    data class Person(val num1 : Int, val num2 : Int) : SealedClass(){
        var number1: Int = 10;
    }

    fun eval(expr: SealedClass): String = when(expr) {
        is Person -> "Person"
    }
}

枚举类

  kotlin中枚举类和java枚举一致,通过enum关键字来定义一个枚举类,枚举类和密封了的区别是:枚举类的中的每一个枚举常量都只能存在一个实例。而密封类的子类可以存在多个实例。   如下定义一个普通的枚举类

enum class Color{
    RED,BLACK,BLUE,GREEN,WHITE
}


Color.BLACK -》 name=BLACK  ordinal=1

  定义了一个枚举类Color,其枚举值分别是RED,BLACK…,如果没有指定枚举值具体指,则其值将从0开始,依次加一

八、扩展函数

Kotlin的扩展函数是一种特殊的函数,允许在不修改原始类定义的情况下向现有类添加新的函数。 扩展函数可以像普通函数一样使用,并且可以在任何地方调用。定义一个扩展函数,可以使用以下语法:

fun ClassName.functionName(parameters...) {
    // Function body
}

----------------------------------
JAVA 
// 展示的工具类
public class DisplayUtils {
    // dp转px的方法
    public static float dp2Px(Context context, float dp) {
        Resources resources = context.getResources();
        DisplayMetrics metrics = resources.getDisplayMetrics();
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics);
    }
}

调用
float pxValue = DisplayUtils.dp2px(context,10)

----------------------------------
kotlin

fun Float.dpToPx(context: Context): Float {
    return TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP, this, context.resources.displayMetrics
    )
}
调用
val pxValue = 10F.dp2px(context)

九、协程

runBlocking 定义: runBlocking 会阻塞线程来等待自己子协程执行完, 并且对于不是子协程的作用域,也会尽量的去执行

override fun initData() {
        Log.d(TAGG,"initdata1")
        runBlocking {
            logThread("runBlocking start==  $coroutineContext")

            GlobalScope.launch {
                delay(28000)
                logThread("GlobalScope launch==  $coroutineContext")
            }

            CoroutineScope(Dispatchers.IO).launch  {
                delay(28000)
                logThread("CoroutineScope launch==  $coroutineContext")
            }
            logThread("runBlocking end==  $coroutineContext")
        }
        Log.d(TAGG, "initdata2")
    }



 15:06:25.789 14886-14886/com.example.hellokt D/LoginActivityKK: initdata1
 15:06:25.805 14886-14886/com.example.hellokt D/LoginActivityKK: runBlocking runBlocking start==  [BlockingCoroutine{Active}@2849b17, BlockingEventLoop@4813b04]:	thread main
 15:06:25.815 14886-14886/com.example.hellokt D/LoginActivityKK: runBlocking runBlocking end==  [BlockingCoroutine{Active}@2849b17, BlockingEventLoop@4813b04]:	thread main
 15:06:25.815 14886-14886/com.example.hellokt D/LoginActivityKK: initdata2
 15:06:53.817 14886-14940/com.example.hellokt D/LoginActivityKK: runBlocking CoroutineScope launch==  [StandaloneCoroutine{Active}@f685e7, Dispatchers.IO]:	thread DefaultDispatcher-worker-2
 15:06:53.817 14886-14939/com.example.hellokt D/LoginActivityKK: runBlocking GlobalScope launch==  [StandaloneCoroutine{Active}@d6d9594, Dispatchers.Default]:	thread DefaultDispatcher-worker-1

开启协程两种不同的方式

在上面代码中我们提到了,开启协程有2种方式

CoroutineScope#launch{} CoroutineScope#async{}

override fun initData() {
        runBlocking {
            val job:Job = launch {
                logThread("launch run")
            }

            val deferred:Deferred<String> = async {
                logThread("async run")
                "hello"
            }
            logThread("async result ${deferred.await()}")
        }
    }


 15:22:26.994 22906-22906/com.example.hellokt D/LoginActivityKK: runBlocking launch run:	thread main
 15:22:26.994 22906-22906/com.example.hellokt D/LoginActivityKK: runBlocking async run:	thread main
 15:22:26.995 22906-22906/com.example.hellokt D/LoginActivityKK: runBlocking async result hello:	thread main

返回的结果通过 Deferred#await() 来获取,并且调用Deferred#await()的时候,会等待async{} 执行完成之后在往下执行,就和Job#join一样,不过await()有返回结果

Job.cancel 取消协程

协程比线程好用的一点就是协程可以自己管理生命周期, 而线程则不可以

fun main() = runBlocking<Unit> {
    println("main start")

    val job = launch {
        try {
            (0..100).forEachIndexed { index, _ ->
                delay(1000)
                println("launch $index")
            }
        } catch (e: Exception) {
            println("协程被取消了 $e")
        }
    }

    // 协程执行完成监听
    job.invokeOnCompletion {
        println("协程执行完毕${it}")
    }

    delay(5500)

    // 取消协程
    job.cancel()

    println("main end")
}

运行结果:
main start
launch 0
launch 1
launch 2
launch 3
launch 4
main end
协程被取消了 kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelling}@76a4d6c
协程执行完毕kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelled}@76a4d6c

CoroutineDispatcher 协程调度器

定义: 根据名字也可以看出来, 协程调度器, 主要用来切换线程,主要有4种

Dispatchers.Main - 使用此调度程序可在 Android 主线程上运行协程。 Dispatchers.IO - 此调度程序经过了专门优化,适合在主线程之外执行磁盘或网络 I/O。示例包括使用 Room 组件、从文件中读取数据或向文件中写入数据,以及运行任何网络操作。 Dispatchers.Default - 此调度程序经过了专门优化,适合在主线程之外执行占用大量 CPU 资源的工作。用例示例包括对列表排序和解析 JSON。 Dispatchers.Unconfined-始终和父协程使用同一线程

withContext() 是用来切换线程,这里切换到主线程,但是输出的结果并没有切换到主线程 withContext{} 与launch{} 调度的区别: withContext 在原有协程上切换线程 launch 创建一个新的协程来切换线程

Applicaiton添加 System.setProperty("kotlinx.coroutines.debug", "on")查看协程号

MainScope().launch {
            launch (Dispatchers.IO ){
                logThread("AAA")
                launch {
                    logThread("BBB")
                    launch {
                        logThread("CCC")
                    }
                }
            }
        }

 7315-7372/com.example.hellokt D/LoginActivityKK: runBlocking AAA:	thread DefaultDispatcher-worker-1 @coroutine#2
 7315-7372/com.example.hellokt D/LoginActivityKK: runBlocking BBB:	thread DefaultDispatcher-worker-1 @coroutine#3
 7315-7372/com.example.hellokt D/LoginActivityKK: runBlocking CCC:	thread DefaultDispatcher-worker-1 @coroutine#4

 MainScope().launch {
            launch (Dispatchers.IO ){
                logThread("AAA")
                launch {
                    logThread("BBB")
                    launch(Dispatchers.Main) {
                        logThread("CCC")
                    }
                }
            }
        }
 8226-8262/com.example.hellokt D/LoginActivityKK: runBlocking AAA:	thread DefaultDispatcher-worker-1 @coroutine#2
 8226-8264/com.example.hellokt D/LoginActivityKK: runBlocking BBB:	thread DefaultDispatcher-worker-3 @coroutine#3
 8226-8226/com.example.hellokt D/LoginActivityKK: runBlocking CCC:	thread main @coroutine#4

MainScope().launch {
            launch (Dispatchers.IO ){
                logThread("AAA")
                launch {
                    logThread("BBB")
                    withContext(Dispatchers.Main) {
                        logThread("CCC")
                    }
                }
            }
        }

 8928-8968/com.example.hellokt D/LoginActivityKK: runBlocking AAA:	thread DefaultDispatcher-worker-1 @coroutine#2
 8928-8969/com.example.hellokt D/LoginActivityKK: runBlocking BBB:	thread DefaultDispatcher-worker-2 @coroutine#3
 8928-8928/com.example.hellokt D/LoginActivityKK: runBlocking CCC:	thread main @coroutine#3

CoroutineName 协程名字

MainScope().launch(context = CoroutineName("GWM 1")) {
            launch {
                logThread("AAA")
                launch {
                    logThread("BBB")
                    launch (context = CoroutineName("GWM 2")) {
                        logThread("CCC")
                    }
                }
            }
        }


 12776-12776/com.example.hellokt D/LoginActivityKK: runBlocking AAA:	thread main @GWM 1#2
 12776-12776/com.example.hellokt D/LoginActivityKK: runBlocking BBB:	thread main @GWM 1#3
 12776-12776/com.example.hellokt D/LoginActivityKK: runBlocking CCC:	thread main @GWM 2#4

Job配合CoroutineHandler 异常捕获

SupervisorJob() 假如有一个场景,我们需要某个子协程出现问题就出现问题,不应该影响到其他的子协程执行,那么我们就可以用 SupervisorJob()

MainScope().launch {
            val exHandler = CoroutineExceptionHandler { _, throwable ->
                logThread("catch $throwable")
            }

            val scope = CoroutineScope(Job() + Dispatchers.IO + exHandler)

            scope.launch {
                logThread("11111")
                throw KotlinNullPointerException("1 null")
            }.join()

            scope.launch {
                logThread("22222")
                throw KotlinNullPointerException("2 null")
            }.join()
        }

 20921-20968/com.example.hellokt D/LoginActivityKK: runBlocking 11111:	thread DefaultDispatcher-worker-1 @coroutine#2
 20921-20968/com.example.hellokt D/LoginActivityKK: runBlocking catch kotlin.KotlinNullPointerException: 1 null:	thread DefaultDispatcher-worker-1 @coroutine#2


  MainScope().launch {
            val exHandler = CoroutineExceptionHandler { _, throwable ->
                logThread("catch $throwable")
            }

            val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO + exHandler)

            scope.launch {
                logThread("11111")
                throw KotlinNullPointerException("1 null")
            }.join()

            scope.launch {
                logThread("22222")
                throw KotlinNullPointerException("2 null")
            }.join()
        }

 22417-22484/com.example.hellokt D/LoginActivityKK: runBlocking 11111:	thread DefaultDispatcher-worker-1 @coroutine#2
 22417-22484/com.example.hellokt D/LoginActivityKK: runBlocking catch kotlin.KotlinNullPointerException: 1 null:	thread DefaultDispatcher-worker-1 @coroutine#2
 22417-22484/com.example.hellokt D/LoginActivityKK: runBlocking 22222:	thread DefaultDispatcher-worker-1 @coroutine#3
 22417-22484/com.example.hellokt D/LoginActivityKK: runBlocking catch kotlin.KotlinNullPointerException: 2 null:	thread DefaultDispatcher-worker-1 @coroutine#3