Kotlin

作者: 放羊娃华振 | 来源:发表于2022-09-20 09:49 被阅读0次

    一、为什么要优先使用 Kotlin 进行 Android 开发?

    我们查看了直接来自与会开发者、我们的客户顾问委员会 (CAB)、Google Developers 专家 (GDE) 的反馈,以及我们通过开发者调研获得的反馈。许多开发者已喜欢上使用 Kotlin,且提供更多 Kotlin 支持的呼声很高。下面介绍了开发者喜欢用 Kotlin 编写代码的原因:

    • 富有表现力且简洁:您可以使用更少的代码实现更多的功能。表达自己的想法,少编写样板代码。在使用 Kotlin 的专业开发者中,有 67% 的人反映其工作效率有所提高。
    • 更安全的代码:Kotlin 有许多语言功能,可帮助您避免 null 指针异常等常见编程错误。包含 Kotlin 代码的 Android 应用发生崩溃的可能性降低了 20%。
    • 可互操作:您可以在 Kotlin 代码中调用 Java 代码,或者在 Java 代码中调用 Kotlin 代码。Kotlin 可完全与 Java 编程语言互操作,因此您可以根据需要在项目中添加任意数量的 Kotlin 代码。
    • 结构化并发:Kotlin 协程让异步代码像阻塞代码一样易于使用。协程可大幅简化后台任务管理,例如网络调用、本地数据访问等任务的管理。

    二、Kotlin常用知识

    1.变量声明

    Kotlin 使用两个不同的关键字(即 val [不可修改] 和 var[可修改])来声明变量:

        //可变变量赋值
        var str: String = "str1"
    
        //变量可赋空值
        var str2: String? = null
    
        //延迟赋值使用lateinit
        lateinit var str3: String
    
        //val类似java的final关键字
        val str4: String = "final str"
    
        //下面是自动类型推断的写法
        var str5 = "str5"
        var int6 = 5
    
    2.数据类型
    • kotlin与java的基本数据类型对比


      kotlin与java的基本数据类型对比
    • 数组
        val arr1 = arrayOf("a", "b", "c")
        for (i in arr1.indices) {
            println("地${i}个元素是${arr1[i]}")
        }
        arr1.set(2, "f")
    
        for ((index, value) in arr1.withIndex()) {
            println("地${index}个元素是${value}")
        }
    
    • 集合 Set/MutableSet
     //不能调用add方法
        var set1 = setOf<String>("1", "2", "3")
        //取值
        var v1: String = set1.elementAtOrElse<String>(0, defaultValue = { "0" })
    
        //可以调用add方法
        var mutaset2 = mutableSetOf<String>("1", "2", "3")
        mutaset2.add("4")
        //取值
        var v2: String? = mutaset2.elementAtOrElse<String>(1, { "0" })
        var v3: String? = mutaset2.elementAtOrElse<String>(1) { "0" }
    
        val size = set1.size
        println("set size:${size}")
    
        //遍历方式1
        for (item in mutaset2) {
            print(item)
            print(" ")
        }
        println()
    
        //遍历方式二
        val iterator = mutaset2.iterator()
        while (iterator.hasNext()) {
            print(iterator.next())
            print(" ")
        }
        println()
    
        //遍历方式三
        mutaset2.forEach {
            print(it)
            print(" ")
        }
        println()
    

    MutableSet常用方法,更多方法可以点击MutableSet 方法

    属性 描述
    fun add(element: E): Boolean 将给定元素添加到集合中
    fun addAll(elements: Collection< E>): Boolean 将给定集合的所有元素添加到当前集合中
    fun clear() 将删除此集合中的所有元素
    fun iterator(): MutableIterator< E> 它返回此对象元素的迭代器
    fun remove(element: E): Boolean 如果指定元素存在于集合中,将从此集合中删除单个指定元素
    fun removeAll(elements: Collection< E>): Boolean 会删除集合中指定的当前集合中的所有元素
    fun retainAll(elements: Collection< E>): Boolean 仅保留当前集合中存在于指定集合中的那些元素
    fun contains(element: E): Boolean 检查当前集合中是否包含的指定元素
    fun containsAll(elements: Collection< E>): Boolean 检查当前集合中是否存在指定集合的所有元素
    fun isEmpty(): Boolean 如果集合为空(不包含任何元素),则返回true,否则返回false
    fun Iterable.any(): Boolean 如果集合包含至少一个元素,则返回true
    fun Iterable.any(predicate: (T) -> Boolean): Boolean 如果至少元素与给定的谓词匹配,则返回true
    fun Iterable.distinct(): List 返回一个列表,其中只包含给定集合中的不同元素
    fun Iterable.drop(n: Int): List 返回一个列表,其中包含除前n个元素之外的所有元素
    fun Iterable.elementAt(index: Int): T 返回给定索引处的元素,或者如果集合中不存在给定索引则抛出IndexOutOfBoundException
    fun Iterable.elementAtOrElse(index: Int, defaultValue: (Int) -> T): T 如果索引在当前集合中超出范围,它将返回给定索引处的元素或调用defaultValue函数的结果
    fun <T : Comparable> Iterable.max(): T? 返回最大的元素,如果集合中没有元素,则返回null
    fun <T : Comparable> Iterable.min(): T? 返回最小的元素,如果集合中没有元素,则返回null
    fun MutableCollection.remove(element: T): Boolean 如果它存在于当前集合中,它将删除单个指定元素
    fun MutableCollection.removeAll(elements: Collection): Boolean 删除了包含在指定集合中的当前集合的所有元素。
    fun MutableCollection.retainAll(elements: Collection): Boolean 保留当前集合中包含在指定集合中的所有元素
    fun Iterable.reversed(): List 以相反的顺序返回元素
    • 队列 List/MutableList
     // list不能调用add方法,不能修改
        var list1 = listOf<Int>(1, 2, 3, 4)
        var v5 = list1.get(0)
        var v6 = list1[0]
    
        var list2 = mutableListOf<Int>(1, 2, 3, 4)
        //设置元素
        list2.set(1, 5)
        list2[1] = 5
        //添加元素
        list2.add(7)
        var v7 = list2[0]
        println("list size:${list2.size}")
    
        //遍历方式1
        list2.forEach {
            print(it)
            print(" ")
        }
        println()
    
        //遍历方式2
        val iterator = list2.iterator()
        while (iterator.hasNext()){
            print(iterator.next())
            print(" ")
        }
        println()
    
        //遍历方式三
        for(item  in list2){
          print("${item} ")
        }
        println()
    
    //遍历方式四 获取index
        fun forTest() {
            val list = mutableListOf(1, 2, 3, 4, 5)
            for (index in 0 until list.size) {
                println("index=${index}")
            }
    //遍历方式五 获取index
    fun forTest() {
            val list = mutableListOf(1, 2, 3, 4, 5)
            for (index in 0 .. list.size) {
                println("index=${index}")
            }
        }
    //遍历方式六 获取index
        fun forTest() {
            val list = mutableListOf(1, 2, 3, 4, 5)
            for ((index, value) in list.withIndex()) {
                println("index=${index}")
                println("value=${value}")
            }
        }
    
    
    • 映射 Map/MutableMap
        val map1 = mapOf("key1" to 1, "key2" to 2)
        val entries = map1.entries
        for (item in entries) {
            println("key为${item.key}的值是${item.value}")
        }
        println()
    
        val map2 = mutableMapOf<String, Int>("key1" to 1, "key2" to 2)
        map2.put("ke3",3)
        for((k,v) in map2){
            println("key为${k}的值是${v}")
        }
        println()
    
        map2.remove("key2")
        val iterator = map2.iterator()
        while(iterator.hasNext()){
            val next = iterator.next()
            println("key为${next.key}的值是${next.value}")
        }
    
    3.Kotlin 条件语句
    
        //这块主要看看基础语法:https://kotlinlang.org/docs/basic-syntax.html
        //    比较测试一
        var name = ""
        if (name.equals("")) {
            println("name is null")
        }
    
        //    比较测试二
        val x = 10
        val y = 9
        if (x in 1..y + 1) {
            println("fits in range")
        }
    
        //    比较测试三
        val list = listOf("a", "b", "c")
    
        if (-1 !in 0..list.lastIndex) {
            println("-1 is out of range")
        }
        if (list.size !in list.indices) {
            println("list size is out of valid list indices range, too")
        }
    
        //    kotln中使用when代理switch,参考:https://kotlinlang.org/docs/basic-syntax.html#when-expression
        var key = 3
        when (key) {
            1 -> println("打印了1")
            2 -> println("打印了2")
            3 -> println("打印了3")
        }
    
    
    4.Kotlin的表达式

    语句没有值,总是包围着它代码块中的顶层元素,表达式有值,能作为另一个表达式的一部分。举个例子:
    java代码:

    public int max(int a, int b) {
        if (a > b) {
            return a;
        } else {
            return b;
        }
    }
    

    Kotlin代码:(Kotlin中,if是表达式,不是语句,可以直接return if表达式)

    fun max(a: Int, b: Int): Int {
        return if (a > b) a else b
    }
    

    其余常用的表达式可以参考:
    https://www.jianshu.com/p/2a1755457add/

    5.Kotlin String模板

    主要是怎么输出String的各种数据格式:

    val i = 10
    println("i = $i") // Prints "i = 10"
    
    val s = "abc"
    println("$s.length is ${s.length}") // Prints "abc.length is 3"
    

    更多细节可以参考:https://kotlinlang.org/docs/strings.html#string-templates

    6.Kotlin函数
    • 普通函数
    fun double(a: Int): Int {
        return a * 2
    }
    //没有返回值,Unit可以不写,等同于fun printMethod(txt: String)
    fun printMethod(txt: String): Unit {
        println(txt)
    }
    
    • 函数中的参数可以具有默认值
    fun method1(x: Int, useCache: Boolean = false, str: String = "defaultStr") {
        println("x:${x}")
        println("useCache:${useCache}")
        println("str:${str}")
    }
    
    //调用的时候可以使用method1(10), 其余参数都是使用默认值
    method1(10)
    //调用的时候使用 method1(x = 20),就是指定给哪个参数赋值
     method1(x = 20)
    
    • 当我们继承与父类的一个成员函数的时候,不能修改其参数默认值
    open class A {
        open fun method(i: Int = 10) { /*……*/ }
    }
    
    class B : A() {
        override fun method(i: Int) { /*……*/ }  // 不能有默认值
    }
    
    • 可变的函数参数(当一个参数是是可变的时候,使用vararg关键字)
    fun getList(vararg strs: String):List<String>{
    
        var list =ArrayList<String>()
        for(item in strs) {
            list.add(item)
        }
        return list;
    
    }
    

    泛型实现方式:

    //这里的T代表泛型,总是泛型总是出现在函数名的前面
    fun <T> asList(vararg ts: T): List<T> {
        val result = ArrayList<T>()
        for (t in ts) // ts is an Array
            result.add(t)
        return result
    }
    //调用的时候,使用逗号分隔
    val list = asList(1,2,3)
    //当我们已经有一个数组类型的参数的时候,我们可以用扩展符(*)来作为参数传入
    val arr = arrayof(1,2,3)
    val list = asList(4,5,*arr)
    
    • 泛型函数
      out指明了他是一个可读的,但不是可写的;in 指明了他是一个可写,但不可读的;类似java中的super和extend,指定了一个类型的下界和上界
    interface Source<out T> {
        fun nextT(): T
    }
    
    fun demo(strs: Source<String>) {
        val objects: Source<Any> = strs // 这个没问题,因为 T 是一个 out-参数
        // ……
    }
    
    interface Comparable<in T> {
        operator fun compareTo(other: T): Int
    }
    
    fun demo(x: Comparable<Number>) {
        x.compareTo(1.0) // 1.0 拥有类型 Double,它是 Number 的子类型
        // 因此,我们可以将 x 赋给类型为 Comparable <Double> 的变量
        val y: Comparable<Double> = x // OK!
    }
    
    • 内联函数
      内联函数会将方法直接写在调用的地方,简而言之就是内联函数等于将代码块,copy到调用的地方。
    • noinline关键字
      noinline: 让原本的内联函数变为不是内联的,保留 数据 特征
    • crossinline
      非局部返回标记,为了不让lamba表达式直接返回内联函数,所做的标记
      相关知识点:我们都知道,kotlin中,如果一个函数中,存在一个lambda表达式,在该lambda中不支持直接通过return退出该函数的,只能通过return@XXXinterface这种方式
    • Lambada
      可参考文章
    • kotlin之闭包、匿名方法、lambda
      参考kotlin之闭包、匿名方法、lambda
    7.Kotlin类型转换
    • 使用is或者!is在运行时检测对象是否符合给定类型。
    • 使用as后者as?进行类型转换,后者返回的是空安全类型。
      var str:String ="123"
        println(  str is String )
        println(  str !is String )
    
        val value = str as CharSequence
        println(value)
    
    8.kotlin 注解
    • @Target: 目标元素类型
    • @Retention: 存储类型
    • @Repeatable:单个元素上是否可以应用多个
    • @MustBeDocumented: 标记注解是公开API的一部分,会被包含到生成的公开的API文档中。
    @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
            AnnotationTarget.TYPE_PARAMETER, AnnotationTarget.VALUE_PARAMETER,
            AnnotationTarget.EXPRESSION)
    @Retention(AnnotationRetention.SOURCE)
    @MustBeDocumented
    annotation class TestAnnotationTarget
    
    //声明无参注解
    annotation class AnnotationTest
    
    //使用有参注解
    @AnnotationTest
    fun testMethod() {
        println()
    }
    
    //声明有参注解
    annotation class AnnotationTest1(val str: String)
    
    //使用有参注解
    @AnnotationTest1(str = "aaa")
    fun testMethod1() {
        println()
    }
    
    9.kotlin协程
    //测试协程的使用
        private fun runCoroutines() {
    
            GlobalScope.launch(Dispatchers.Main) {
                val data = getNetData()
                val processData = procesData(data)
                Log.i(TAG, processData)
            }
        }
    
        //处理网络请求返回的数据
        private suspend fun procesData(data: String?): String? {
            val dd = data
            return withContext(Dispatchers.IO) {
                Log.i(TAG, "数据是:${dd}")
                Log.i(TAG, "开始处理数据了")
                delay(1000)
                Log.i(TAG, "成功处理数据了")
                dd?.split("-")
                        ?.map { it.capitalize() }
                        ?.reduce { acc, s -> acc + s }
    
            }
        }
    
        //模拟网络请求获取数据
        private suspend fun getNetData(): String {
    
            return withContext(Dispatchers.IO) {
                Log.i(TAG, "开始获取数据了")
                delay(2000)
                Log.i(TAG, "成功获取数据了")
                "hello-abc"
            }
    
        }
    

    几个开启协程Scope的区别

    1. CoroutineScope
      他会跟踪所有协程。同样他还可以取消由它所启动的所有协程。
    2. GlobalScope
      声明周期是process级别的。即使Activty与Fragment已经被销毁,协程仍然在运行。
    3. MainScope
      通常在 在Activity中使用。 在onDestory 要记得手动销毁掉。
    4. viewModelScope
      只能在ViewModel中使用。绑定viewModel的生命周期
    5. lifecycleScope
      只能在Activity、Fragment中使用。并且会和Activity、Fragment生命周期进行绑定。
      参考一个在Activity中使用的例子:https://blog.csdn.net/mp624183768/article/details/125139784
    10.kotlin之let、with、run、apply、also
    方法 上下文对象 返回值 扩展函数 使用场景
    let it lambda 结果 在非空对象上执行一个 lambda或者在局部域中引入一个变量
    with this lambda 结果 在一个对象上组合函数调用
    run this lambda 结果 对象配置并计算结果
    apply this 上下文对象 对象配置
    also it 上下文对象 额外的效果
    非扩展函数run lambda 结果 在需要表达式的地方运行语句

    表格里的上下文对象中,this 是可以省略的, it 是隐含的默认参数名,可以显式地指定为其他名字。

    • run、with 和 apply 的上下文对象是 this ,可以省略掉 this 单词,因此主要用于操作对象成员(例如调用对象的方法或使用其属性)的时候
    • let 和 also 的上下文对象是 it , 适用于将此对象作为方法调用参数传入
    • apply 和 also 返回上下文对象,可用于链式调用和返回上下文对象的函数的返回语句
    • let、run 和 with 返回 lambda 结果,可以在将结果分配给变量时使用
       var str: String? = "hello world"
    
        //let 链式调用中最后一行是返回值会带到下个调用中。
        str?.let { it ->
            println("第一次收到let值:${it}")
            "第一次处理let${it}"
        }.let {
            println("第二次收到let值:${it}")
        }
    
        println("let 处理后:${str}")
    
        println("-------------------------------")
    
        //with  最后一行是返回值有作用
        // https://www.jianshu.com/p/272372acc00c
        //写法一
        var withStr: String = with(str, {
            println("收到with的值:${this}")
            "返回with的值:${this}"
        })
        println("==withStr 处理后==>${withStr}")
    
        with(str) {
            println("长度是:${this?.length}")
        }
    
        println("with 处理后:${str}")
    
        println("-------------------------------")
    
        //run 最后一行是返回值有作用
        str.run {
            println("收到run的值:${this}")
            "run1"
        }.run {
            println("收到run的值:${this}")
        }
    
        var runStr: String = str.run {
            "run  -------------${this}"
        }
        println("run 处理后:${runStr}")
    
        println("-------------------------------")
    
        //apply 最后一行是返回值没作用
        str.apply {
            println("收到apply的值1:${this}")
            "11111"
        }.apply {
            println("收到apply的值2:${this}")
            "2222"
        }.apply {
            println("收到apply的值3:${this}")
        }
    
        var applyStr: String? = str.apply {
            println("收到apply的值:${this}")
    //        "apply-${this}"
        }
        println("apply 处理后:${applyStr}")
    
        println("-------------------------------")
        //also 最后一行是返回值没作用
        str.also {
            println("收到also的值1:${it}")
    //        "ddddd"
        }.also {
            println("收到also的值2:${it}")
        }
    //打印日志
    /**
    第一次收到let值:hello world
    第二次收到let值:第一次处理lethello world
    let 处理后:hello world
    -------------------------------
    收到with的值:hello world
    ==withStr 处理后==>返回with的值:hello world
    长度是:11
    with 处理后:hello world
    -------------------------------
    收到run的值:hello world
    收到run的值:run1
    run 处理后:run  -------------hello world
    -------------------------------
    收到apply的值1:hello world
    收到apply的值2:hello world
    收到apply的值3:hello world
    收到apply的值:hello world
    apply 处理后:hello world
    -------------------------------
    收到also的值1:hello world
    收到also的值2:hello world
    */
    

    有篇文章写的不错可以看看:https://www.jianshu.com/p/ba89322cf92d

    11.kotlin之takeIf、takeUnless
        println("-------------------------------")
        //takeIf 当代码块的返回false,对象为空链式调用终止
        var name:String="放羊娃"
        name.takeIf {
            it.length>4
        }?.let {
            println("测试takeif:${it}")
        }
    
        println("-------------------------------")
        //takeUnless 当代码块的返回true,对象为空链式调用终止
        name.takeUnless {
            it.length>4
        }.let {
            println("测试takeUnless:${it}")
        }
    
    

    三、常用的写法

    Activity示例:

    class KotlinTestActivity : AppCompatActivity() , View.OnClickListener {
        override fun onClick(v: View?) {
        }
    }
    

    单例示例:

    class SingleDemo {
        companion object {
            val instance: SingleDemo by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SingleDemo() }
        }
    
        fun doTask(){
            println("调用到我了")
        }
    }
    
    fun main() {
        SingleDemo.instance.doTask()
    }
    

    在推荐其余的单例写法:https://www.jianshu.com/p/2497f6a5a461

    Kotlin工具类Util的示例:

    //需要构建MyAppUtil对象的方式
    open class MyAppUtil {
        fun getImei(): String? {
            return "imei"
        }
    }
    //调用
    var util=  MyAppUtil()
    util.getImei()
    
    //类似java的static的形式
    object MyAppUtil2{
        fun getAnroidId():String?{
            return "android id"
        }
    }
    //调用
    MyAppUtil2.getAnroidId()
    

    Kotlin利用Gson解析json

    //数据实体
    data class Entity(
         val nickname: String,
        @SerializedName("xname") 
        var name: String
    )
    
    //调用解析方法
    val json = Gson().fromJson(result, Entity::class.java)
    

    Kotlin构造函数可以参考

    //@JvmOverloads 加了这个注解就相当生成了一个、两个、三个参数的构造函数了
    class CustomTextView @JvmOverloads constructor(
            context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ) : AppCompatTextView(context, attrs, defStyleAttr) {
    
        init {
            //设置ui啥的
        }
    }
    
    //不用@JvmOverloads 可以自己添加一个和两个的构造方法
    class CustomTextView constructor(
            context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ) : AppCompatTextView(context, attrs, defStyleAttr) {
    
        constructor(context: Context) : this(context, null, 0) {
            //todo something
        }
    
        constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) {
            //todo something
        }
    
        init {
            //todo something
        }
    
    }
    

    //Kotlin方法返回对象

    data class User(val name: String?, val age: Int) {
    
    }
    
    fun getUser(name: String?, age: Int): User {
        return User(name, age)
    }
    
    fun main() {
        val user = getUser("阿美", 19)
        print(user.toString())
    }
    

    Kotlin解构解构参考

    data class User(val name: String?, val age: Int) {
    
    }
    
    fun getUser(name: String?, age: Int): User {
        return User(name, age)
    }
    
    fun main() {
        val user = getUser("阿美", 19)
        print(user.toString())
    
        var (a,b)=user
        print("${a},${b}")
    }
    

    Kotlin闭包的写法:

    
    fun main() {
    
        var result= test("22", ::getWord2)
        println("final result==>${result}")
    }
    
    fun getWord2(name: String, age: Int): String {
        println("getword2 收到参数: ${name} -${age}")
        return "getword2 返回的参数 ${name} -${age}";
    }
    
    fun test(name: String, getWord: (String, Int) -> String) {
        println("test String ==>${name}")
        var age = 100
        println("test getword:")
        println(getWord(name, age))
    }
    
    //打印结果
    /**
    test String ==>22
    test getword:
    getword2 收到参数: 22 -100
    getword2 返回的参数 22 -100
    final result==>kotlin.Unit
    */
    

    kotlin匿名函数

    fun main() {
    
        println(blessFunction())
        println(blessFunction2())
        println(blessFunction3())
        println(blessFunction4("hhhhhh"))
    
    }
    
    var blessFunction: () -> String = {
    
        "bless function return value"
    }
    
    var blessFunction2: () -> Unit = {
    
        println("blessFunction2")
    }
    
    var blessFunction3 = {
    
        "blessFunction3"
    }
    
    var blessFunction4: (str: String) -> String = {
        "bless function return value-${it}"
    }
    //打印结果
    /**
    bless function return value
    blessFunction2
    kotlin.Unit
    blessFunction3
    bless function return value-hhhhhh
    Function0<java.lang.String>
    */
    

    Kotlin空合并操作符号

        var str:String?=null
        var name: String =str ?:"ddd-----------"
        println(name)
    

    Kotlin先决条件函数


    image.png

    四、实用代码

    1.获取sdkROM大小
    //ROM内存大小,返回 64G/128G/256G/512G
        private fun getTotalRom(): String {
            val dataDir = Environment.getDataDirectory();
            val stat = StatFs(dataDir.path)
            val blockSize:Long = stat.blockSizeLong
            val totalBlocks:Long = stat.blockCountLong
            val size:Long = totalBlocks * blockSize
            val GB:Long = 1024 * 1024 * 1024
            val deviceRomMemoryMap = arrayOf(
                2 * GB,
                4 * GB,
                8 * GB,
                16 * GB,
                32 * GB,
                64 * GB,
                128 * GB,
                256 * GB,
                512 * GB,
                1024 * GB,
                2048 * GB
            )
            val displayRomSize = arrayOf(
                "2GB",
                "4GB",
                "8GB",
                "16GB",
                "32GB",
                "64GB",
                "128GB",
                "256GB",
                "512GB",
                "1024GB",
                "2048GB"
            )
    
            var resultJ = 0
            for (i in deviceRomMemoryMap.indices) {
                resultJ = i
                if (size <= deviceRomMemoryMap[i]) {
                    break
                }
                if (i == deviceRomMemoryMap.size) {
                    resultJ = i - 1
                }
            }
            return displayRomSize[resultJ]
        }
    

    五、参考文档

    https://developer.android.google.cn/kotlin/learn
    https://kotlinlang.org/docs/basic-syntax.html

    相关文章

      网友评论

          本文标题:Kotlin

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