类的属性可以用关键字 var 声明为可变的,否则使用只读关键字 val 声明为不可变。
任何 Java代码东西,必须用 ? 允许为null,来接收 val actionBar : ActionBar? = supportActionBar Unit -> void Any -> Object 当前context this@xxActivity this@LoginActivity EditText 需要获取用 @= android:text="@={vm.userName}" TextView 只是显示用 @ @{vm.loginState} 点击事件用 @{()->click.startToRegister()} @JvmField 消除了变量的getter方法 节省开销 使Kotlin编译器不再对该字段生成getter/setter并将其作为公开字段 @JvmStatic 注解标识静态变量 !! 断言不会为空 :: 用于获取对象或类的引用 初始化集合 val list: List<Int>? = listOf(1, 2, 3) xml模板 <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingDefaultResource" > <data> <!-- 同学们,这里是ViewModel的绑定操作 --> <variable name="vm" type="com.mdkj.video.bridge.state.LoginViewModel" /> <!-- 同学们,这里是此布局的点击事件集 --> <variable name="click" type="com.mdkj.video.ui.page.login.LoginActivity.ClickClass" /> </data> <LinearLayout> <!-- 实际布局内容 --> </LinearLayout> </layout> 实现和继承 implements 都是 : 方式一 class HeaderInterceptor : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val requestTime = System.currentTimeMillis() val token = "666888" val original = chain.request() val requestBuilder = original.newBuilder() .addHeader("content-Type", "application/json") .addHeader("charset", "utf-8") .addHeader("token", token) val buildRequest = requestBuilder.build() val response = chain.proceed(buildRequest) Log.e(TAG, "requestTime=" + (System.currentTimeMillis() - requestTime)) return response } companion object { private val TAG = HeaderInterceptor::class.java.canonicalName } } 方式二 fun initReUrlIntercepter() : Interceptor{ val interceptor = Interceptor { chain -> val request = chain.request() val url : HttpUrl = request.url() val scheme : String = url.scheme() //............... val newUrl : String = url.toString() Log.e("initReUrlIntercepter", "newUrl=" + newUrl) val builder : Request.Builder = request.newBuilder().url(newUrl) chain.proceed(builder.build()) } return interceptor } 方式三 interceptor = Interceptor{ chain -> val request = chain.request() .newBuilder() .addHeader("content-Type", "application/json") .addHeader("charset", "utf-8") .addHeader("appkey", "666888") .build() chain.proceed(request) } 单例类的创建 私有空构造函数 class HttpRequestManager private constructor() { //单例 companion object { val instance : HttpRequestManager by lazy { HttpRequestManager() } } val apiClient : ApiService by lazy { APIClient.instance.instanceRetrofit(ApiService::class.java) } init{ //初始化 } // var responseCodeLiveData: MutableLiveData<String>? = null get() { if (field == null) { field = MutableLiveData() } return field } private set } 单例 类 object ApiClient { @JvmStatic private var apiService: ApiService? = null @JvmStatic val appApi: ApiService = apiService ?: synchronized(this) { apiService ?: retrofit.create(ApiService::class.java).also { apiService = it } } } 普通对象的声明 open class ResultData<T> { var success:Boolean = false var errorCode:Int = 0 var errorMsg:String = "" var data:T ?= null } 另一种 数据类声明 自动实现get set data class LoginRegisterResponse(val admin: Boolean, val chapterTops: List<*>, val collectIds: List<*>, val email: String ?, val icon: String?, val id: String?, val nickname: String?, val password: String?, val publicName: String?, val token: String?, val type: Int, val username: String? ) // 登录的唯一ViewModel class LoginViewModel : ViewModel() { @JvmField // @JvmField消除了变量的getter方法 val userName = MutableLiveData<String>() val userPwd = MutableLiveData<String>() val loginState = MutableLiveData<String>() init { userName.value = "" userPwd.value = "" loginState.value = "" } } private fun newClient() = { OkHttpClient().newBuilder().myApply { //日志拦截器 if (BuildConfig.DEBUG) { addInterceptor(initLogIntercepter()) } // 请求头封装 addInterceptor(headerInterceptor()) addInterceptor(initReUrlIntercepter()) // 添加连接超时时间 connectTimeout(60, TimeUnit.SECONDS) readTimeout(90, TimeUnit.SECONDS) writeTimeout(90, TimeUnit.SECONDS) }.build() } 对象的创建 private fun newClient() = OkHttpClient().newBuilder().myApply { //日志拦截器 if (BuildConfig.DEBUG) { addInterceptor(initLogIntercepter()) } // 请求头封装 addInterceptor(headerInterceptor()) addInterceptor(initReUrlIntercepter()) // 添加连接超时时间 connectTimeout(60, TimeUnit.SECONDS) readTimeout(90, TimeUnit.SECONDS) writeTimeout(90, TimeUnit.SECONDS) }.build() private val TAG = headerInterceptor::class.java.canonicalName 内置函数 let apply run also takeIf with // 在主线程(Dispatchers.Main)执行 launch(Dispatchers.Main){} data 实现get set object 单例对象 class 普通类 tools工具集 android:visibility="gone" tools:visibility="visible"
接口实现
interface ICallback <T>{ fun OnSucces(objects: T) fun onFailed(code : Int, msg : String) } fun getOKHttp(a: String?, callback: ICallback<*>) {} getOKHttp("", object : ICallback<Any> { override fun OnSucces(objects: Any) { } override fun onFailed(code: Int, msg: String) { } })
vararg 可变参数
fun vars(vararg v:Int){ for(vt in v){ print(vt) } } // 测试 fun main(args: Array<String>) { vars(1,2,3,4,5) // 输出12345 } public int add(int... array) { int count = 0; for (int i: array) { count += i; } return count; } fun add(vararg array: Int): Int { var count = 0 //for (i in array) { // count += i //} array.forEach { count += it } return count }
区间 1..4
for (i in 1..4) print(i) // 输出“1234” // 使用 step 指定步长 for (i in 1..4 step 2) print(i) // 输出“13” for (i in 4 downTo 1 step 2) print(i) // 输出“42” // 使用 until 函数排除结束元素 for (i in 1 until 10) { // i in [1, 10) 排除了 10 println(i) }
位操作符
shl(bits) – 左移位 (Java’s <<) shr(bits) – 右移位 (Java’s >>) ushr(bits) – 无符号右移位 (Java’s >>>) and(bits) – 与 or(bits) – 或 xor(bits) – 异或 inv() – 反向
字符
和 Java 不一样,Kotlin 中的 Char 不能直接和数字操作,Char 必需是单引号 ' 包含起来的。比如普通字符 '0','a' fun decimalDigitValue(c: Char): Int { if (c !in '0'..'9') throw IllegalArgumentException("Out of range") return c.toInt() - '0'.toInt() // 显式转换为数字 }
静态常量
class MainActivity : AppCompatActivity() { companion object { const val TEXT: String = "" } } const val TEXT: String = "" class MainActivity : AppCompatActivity() { }
Unit 跟 void 一样效果
权限修饰符
修饰符 | 作用 |
---|---|
public | 所有类可见 |
protected | 子类可见 |
default | 同一包下的类可见 |
private | 仅对自己类可见 |
Kotlin 的写法
修饰符 | 作用 |
---|---|
public | 所有类可见 |
internal | 同 module 下的类可见 |
protected | 子类可见 |
private | 仅对自己类可见 |
instanceof 比较类型 is
if (!("2" instanceof String)) { } if ("2" !is String) { }
比较对象 ===
// 比较两个对象内容是否一样 if (object1.equals(object2)) { } // 比较两个对象是否是同一个 if (object5 == object6) { } // 比较两个对象内容是否一样 if (object1 == object2) { } // 比较两个对象是否是同一个 if (object5 === object6) { }
数组 intArrayOf
除了类Array,还有ByteArray, ShortArray, IntArray val x: IntArray = intArrayOf(1, 2, 3) x[0] = x[1] + x[2] val array1 = intArrayOf(1, 2, 3) val array2 = floatArrayOf(1f, 2f, 3f) val array3 = arrayListOf("1", "2", "3")
三元操作符 ? :
val c = if (condition) a else b
When 表达式
when (x) { 1 -> print("x == 1") 2 -> print("x == 2") else -> { // 注意这个块 print("x 不是 1 ,也不是 2") } } 一个值在(in)或者不在(!in)一个区间或者集合中 when (x) { in 1..10 -> print("x is in the range") in validNumbers -> print("x is valid") 5, 7 -> println(count) !in 10..20 -> print("x is outside the range") else -> print("none of the above") } fun hasPrefix(x: Any) = when(x) { is String -> x.startsWith("prefix") else -> false } when { x.isOdd() -> print("x is odd") x.isEven() -> print("x is even") else -> print("x is funny") } when 中使用 in 运算符来判断集合内是否包含某实例 fun main(args: Array<String>) { val items = setOf("apple", "banana", "kiwi") when { "orange" in items -> println("juicy") "apple" in items -> println("apple is fine too") } }
For 循环
for (item: Int in ints) { // …… } for (i in array.indices) { print(array[i]) } 或者你可以用库函数 withIndex: for ((index, value) in array.withIndex()) { println("the element at $index is $value") }
角标循环
val array = arrayListOf("1", "2", "3") 方式一 for (i in IntRange(0, array.size - 1)) { println(array[i]) } 方式二 for (i in 0..array.size - 1) { println(array[i]) } 方式三 for (i in 0 until array.size) { println(array[i]) } 方式四 常用 for (text in array) { println(text) }
lateinit 关键字
lateinit var subject: TestSubject //延迟初始化
volatile 关键字
@Volatile var instance: ThreadPoolManager
synchronized 关键字
@Synchronized fun getInstance(): ThreadPoolManager? { } fun getInstance(): ThreadPoolManager? { synchronized(ThreadPoolManager::class.java, { }) }
open 关键字
需要注意的是:Kotlin 的类和方法默认是不能被继承和重写的,如果想要被继承或者重写必须要使用 open 关键字进行修饰
constructor 主构造器 init
class Person constructor(firstName: String) { init { //优先执行 println("FirstName is $firstName") } }
init 静态代码块
class MainActivity : AppCompatActivity() { companion object { var number = 0 init { number = 1 } } }
次构造函数
如果类有主构造函数,每个次构造函数都要,或直接或间接通过另一个次构造函数代理主构造函数。 在同一个类中代理另一个构造函数使用 this 关键字: class Person(val name: String) { constructor (name: String, age:Int) : this(name) { // 初始化... } }
constructor 空的私有主构造函数
如果一个非抽象类没有声明构造函数(主构造函数或次构造函数),它会产生一个没有参数的构造函数。构造函数是 public 。 如果你不想你的类有公共的构造函数,你就得声明一个空的主构造函数: class DontCreateMe private constructor () { } // 第一种写法 class MyView : View { constructor(context: Context) : this(context, null) { } constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) { } constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { } } // 第二种写法 class MyView : View { constructor(context: Context) : this(context, null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) init { 优先初始化 } } // 第三种写法 class RegexEditText @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { init { } } // 如果只有一种构造函数的还可以这样写 class MyView(context: Context) : View(context) { init { } }
私有化 set 方法
class Person { var name: String? = null private set var age: Int = 0 private set }
私有化 get 方法
class Person { private var name: String? = null private var age: Int = 0 }
匿名内部类
post(object : Runnable { override fun run() { } })
枚举
enum class Sex (var type: Int) { MAN(1), WOMAN(2) }
抽象类
abstract class BaseActivity : AppCompatActivity(), Runnable { abstract fun init() }
嵌套类
class Outer { // 外部类 private val bar: Int = 1 class Nested { // 嵌套类 fun foo() = 2 } } fun main(args: Array<String>) { val demo = Outer.Nested().foo() // 调用格式:外部类.嵌套类.嵌套类方法/属性 println(demo) // == 2 }
inner 内部类
class Outer { private val bar: Int = 1 var v = "成员属性" /**嵌套内部类**/ inner class Inner { fun foo() = bar // 访问外部类成员 fun innerTest() { var o = this@Outer //获取外部类的成员变量 println("内部类可以引用外部类的成员,例如:" + o.v) } } } fun main(args: Array<String>) { val demo = Outer().Inner().foo() println(demo) // 1 val demo2 = Outer().Inner().innerTest() println(demo2) // 内部类可以引用外部类的成员,例如:成员属性 }
this@Outer
为了消歧义,要访问来自外部作用域的 this,我们使用this@label,其中 @label 是一个 代指 this 来源的标签。同上 this@MainActivity
类的修饰符
abstract // 抽象类 final // 类不可继承,默认属性 enum // 枚举类 open // 类可继承,类默认是final的 annotation // 注解类 private // 仅在同一个文件中可见 protected // 同一个文件中或子类可见 public // 所有调用的地方都可见 internal // 同一个模块中可见
Kotlin 继承 :
如果一个类要被继承,可以使用 open 关键字进行修饰 open class Person(var name : String, var age : Int){// 基类 } class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) { }
子类没有主构造函数
class Student : Person { constructor(ctx: Context) : super(ctx) { } constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) { } }
重写 : override
在基类中,使用fun声明函数时,此函数默认为final修饰,不能被子类重写。如果允许子类重写该函数, 那么就要手动添加 open 修饰它, 子类重写方法使用 override 关键词: /**用户基类**/ open class Person{ open fun study(){ // 允许子类重写 println("我毕业了") } } /**子类继承 Person 类**/ class Student : Person() { override fun study(){ // 重写方法 println("我在读大学") } } fun main(args: Array<String>) { val s = Student() s.study(); }
Kotlin 扩展
扩展函数可以在已有类中添加新的方法,不会对原类做修改,扩展函数定义形式: class User(var name:String) /**扩展函数**/ fun User.Print(){ print("用户名 $name") } fun main(arg:Array<String>){ var user = User("Runoob") user.Print() } 若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数 class C { fun foo() { println("成员函数") } } fun C.foo() { println("扩展函数") } fun main(arg:Array<String>){ var c = C() c.foo() } 输出结果为:成员函数
伴生对象的扩展
class MyClass { companion object { } // 将被称为 "Companion" } fun MyClass.Companion.foo() { println("伴随对象的扩展函数") } val MyClass.Companion.no: Int get() = 10 fun main(args: Array<String>) { println("no:${MyClass.no}") MyClass.foo() } 实例执行输出结果为: no:10 伴随对象的扩展函数
T 泛型约束
对泛型的类型上限进行约束。 fun <T : Comparable<T>> sort(list: List<T>) { // …… } 对于多个上界约束条件,可以用 where 子句: fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String> where T : CharSequence, T : Comparable<T> { return list.filter { it > threshold }.map { it.toString() } }
型变--消费者 in, 生产者 out
Kotlin 中没有通配符类型,它有两个其他的东西:声明处型变(declaration-site variance) 与类型投影(type projections) 声明处型变 声明处的类型变异使用协变注解修饰符:in、out,消费者 in, 生产者 out 使用 out 使得一个类型参数协变,协变类型参数只能用作输出,可以作为返回值类型但是无法作为入参的类型: // 定义一个支持协变的类 class Runoob<out A>(val a: A) { fun foo(): A { return a } } fun main(args: Array<String>) { var strCo: Runoob<String> = Runoob("a") var anyCo: Runoob<Any> = Runoob<Any>("b") anyCo = strCo println(anyCo.foo()) // 输出 a } in 使得一个类型参数逆变,逆变类型参数只能用作输入,可以作为入参的类型但是无法作为返回值的类型: // 定义一个支持逆变的类 class Runoob<in A>(a: A) { fun foo(a: A) { } } fun main(args: Array<String>) { var strDCo = Runoob("a") var anyDCo = Runoob<Any>("b") strDCo = anyDCo }
object 对象声明,获得单例
Kotlin 使用 object 关键字来声明一个对象。 Kotlin 中我们可以方便的通过对象声明来获得一个单例。 object Site { var url:String = "" val name: String = "菜鸟教程" } fun main(args: Array<String>) { var s1 = Site var s2 = Site s1.url = "www.runoob.com" println(s1.url) println(s2.url) }
companion 伴生对象,类内部的对象声明
类内部的对象声明可以用 companion 关键字标记,这样它就与外部类关联在一起, 我们就可以直接通过外部类访问到对象的内部元素 class MyClass { companion object Factory { fun create(): MyClass = MyClass() } } val instance = MyClass.create() // 访问到对象的内部元素 我们可以省略掉该对象的对象名,然后使用 Companion 替代需要声明的对象名: class MyClass { companion object { fun load() {} } } val x = MyClass.Companion
使用伴生对象,生成,类静态方法
MyClass.load()
object 对象单例的生成
object Utils{ private var sMainHandler: Handler ?= null private val mainHandler:Handler? private get(){ if(mainHandler == null){ synchronized(HandlerUtils::class.java) { if(sMainHandler == null) { sMainHandler = Handler(Looper.getMainLooper()) } } } return sMainHandler } } 单例类的创建 私有空构造函数 class HttpRequestManager private constructor() { //单例 companion object { val instance : HttpRequestManager by lazy { HttpRequestManager() } } val apiClient : ApiService by lazy { APIClient.instance.instanceRetrofit(ApiService::class.java) } init{ //初始化 } // var responseCodeLiveData: MutableLiveData<String>? = null get() { if (field == null) { field = MutableLiveData() } return field } private set } 单例 类 object ApiClient { @JvmStatic private var apiService: ApiService? = null @JvmStatic val appApi: ApiService = apiService ?: synchronized(this) { apiService ?: retrofit.create(ApiService::class.java).also { apiService = it } } }
方法支持添加默认参数
在 Java 方法上,我们可能会为了扩展某个方法而进行多次重载 public void toast(String text) { toast(this, text, Toast.LENGTH_SHORT); } public void toast(Context context, String text) { toast(context, text, Toast.LENGTH_SHORT); } public void toast(Context context, String text, int time) { Toast.makeText(context, text, time).show(); } toast("弹个吐司"); toast(this, "弹个吐司"); toast(this, "弹个吐司", Toast.LENGTH_LONG); 默认参数 fun toast(context : Context = this, text : String, time : Int = Toast.LENGTH_SHORT) { Toast.makeText(context, text, time).show() } toast(text = "弹个吐司") toast(this, "弹个吐司") toast(this, "弹个吐司", Toast.LENGTH_LONG)
对象表达式和对象声明之间的语义差异
对象表达式和对象声明之间有一个重要的语义差别:
-
对象表达式是在使用他们的地方立即执行的
-
对象声明是在第一次被访问到时延迟初始化的
-
伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配
类委托 by interface
类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的 以下实例中派生类 Derived 继承了接口 Base 所有方法,并且委托一个传入的 Base 类的对象来执行这些方法 // 创建接口 interface Base { fun print() } // 实现此接口的被委托的类 class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } // 通过关键字 by 建立委托类 class Derived(b: Base) : Base by b fun main(args: Array<String>) { val b = BaseImpl(10) Derived(b).print() // 输出 10 } 在 Derived 声明中,by 子句表示,将 b 保存在 Derived 的对象实例内部, 而且编译器将会生成继承自 Base 接口的所有方法, 并将调用转发给 b
懒委托 by lazy
private val viewPager: ViewPager? by lazy { findViewById(R.id.vp_home_pager) } LazyThreadSafetyMode.PUBLICATION:并发模式 LazyThreadSafetyMode.NONE:普通模式 val temp: String by lazy(LazyThreadSafetyMode.NONE) { println("测试变量初始化了") return@lazy "666666" }
可观察属性 Observable
observable 可以用于实现观察者模式。 Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler)。 在属性赋值后会执行事件的响应器(handler),它有三个参数:被赋值的属性、旧值和新值: import kotlin.properties.Delegates class User { var name: String by Delegates.observable("初始值") { prop, old, new -> println("旧值:$old -> 新值:$new") } } fun main(args: Array<String>) { val user = User() user.name = "第一次赋值" user.name = "第二次赋值" } 执行输出结果: 旧值:初始值 -> 新值:第一次赋值 旧值:第一次赋值 -> 新值:第二次赋值
DataBinding layout基础布局
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingDefaultResource" > <data> <!-- ViewModel的绑定操作 --> <variable name="vm" type="com.mdkj.video.ui.page.login.viewmodel.LoginVMState" /> <!-- 此布局的点击事件集 --> <variable name="click" type="com.mdkj.video.ui.page.login.LoginActivity.ClickClass" /> </data> 原布局。。。 <LinearLayout/> </layout>
类方法扩展
fun String.handle() : String { return this + "Android轮子哥" } // 需要注意,handle 方法在哪个类中被定义,这种扩展只能在那个类里面才能使用 print("HJQ = ".handle())
inline 内联函数
/** * 这个就是我们今天的主角:内联函数了,用 inline 关键字来修饰 内联函数就是在编译的时候将所有调用 inline 函数的代码直接替换成方法里面的代码 不加 inline 会导致多生成一个内部类,这个是 lambda 函数多出来的类,并且里面的示例还是静态,这无疑会增加内存消耗,另外这样还有另外一个好处,就是能少一层方法栈的调用。 */ private inline fun showToast(message: String) { Toas tUtils.show(message) }
noinline 禁止内联
其实这个关键字不是修饰在方法上面的,而是修饰 在 lambda 参数上面的, 假设一个 inline 函数上面有多个 lambda 参数,那么我只想对某个 lambda 参数内联, 其他 lambda 参数不内联的情况下,就可以使用这个关键字来对不需要进行内联的 lambda 参数进行修饰,大体用法如下: private inline fun showToast(function1: () -> Unit, noinline function2: () -> Unit, message: String) { function1.invoke() function2.invoke() ToastUtils.show(message) }
sealed 密封类
先说枚举类的几个弊端,第一个枚举值赋值是固定的(一旦赋值之后就不可变), 第二个枚举值的类型是固定的(类型只能是自己),密封类的出现正是为了解决这两个问题, 具体用法如下: sealed class Result { // 定义请求成功 data class SUCCESS(val data: String) : Result() // 定义请求失败 data class FAIL(val throwable: Throwable) : Result() } val result = if (AppConfig.isDebug()) { Result.SUCCESS("模拟后台返回数据") } else Result.FAIL(IllegalStateException("模拟请求失败了")) when (result) { is Result.SUCCESS -> { println(result.data) } is Result.FAIL -> { println(result.throwable) } } 密封类的特点如下: 1.密封类使用关键字 sealed 进行声明。 2.密封类的构造函数默认是私有的,因此无法直接实例化密封类。 3.密封类的子类必须嵌套在密封类的声明中,并且是密封类的直接子类。 4.使用密封类时,通常通过 when 表达式来处理不同子类的情况,确保对所有可能的子类进行处理。 密封类在编写代码时可以提供更好的类型安全性,因为编译器会检查对密封类的所有子类是否都被处理。密封类适合用于表示有限的状态或类型,以确保代码的完整性和可靠性
高阶函数
let、with、run、apply、also 五个常用函数
let 函数
返回:最有一行
-
在函数块内可以通过 it 指代该对象。返回值为函数块的最后一行或指定 return 表达式
fun main() { val result = "Android轮子哥".let { println(it.length) 1000 } println(result) } 最常用的场景就是使用let函数处理需要针对一个可 null 的对象统一做判空处理 又或者是需要去明确一个变量所处特定的作用域范围内可以使用 videoPlayer?.setVideoView(activity.course_video_view) videoPlayer?.setControllerView(activity.course_video_controller_view) videoPlayer?.setCurtainView(activity.course_video_curtain_view) videoPlayer?.let { it.setVideoView(activity.course_video_view) it.setControllerView(activity.course_video_controller_view) it.setCurtainView(activity.course_video_curtain_view) }
with 函数
可以访问对象公有属性和方法
适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法即可, 经常用于 Android 中 `RecyclerView.onBinderViewHolder` 中, 数据 model 的属性映射到 UI 上 override fun onBindViewHolder(holder: ViewHolder, position: Int){ val item = getItem(position)?: return with(item){ holder.nameView.text = "姓名:$name" holder.ageView.text = "年龄:$age" } }
run 函数
返回:可以访问公有的属性和方法,返回最后一行的值或者指定的 return 的表达式
-
实际上可以说是 let 和 with 两个函数的结合体,run 函数只接收一个 lambda 函数为参数,以闭包形式返回,返回值为最后一行的值或者指定的 return 的表达式
var person = Person("Android轮子哥", 100) var result = person.run { println("$name + $age") 1000 } println(result) 适用于 let,with 函数任何场景。因为 run 函数是let,with两个函数结合体, 准确来说它弥补了 let 函数在函数体内必须使用 it 参数替代对象, 在 run 函数中可以像 with 函数一样可以省略,直接访问实例的公有属性和方法, 另一方面它弥补了 with 函数传入对象判空问题,在 run 函数中可以像let 函数一样做判空处理, 这里还是借助 onBindViewHolder 案例进行简化 override fun onBindViewHolder(holder: ViewHolder, position: Int){ val item = getItem(position)?: return item?.run { holder.nameView.text = "姓名:$name" holder.ageView.text = "年龄:$age" } }
apply 函数
返回:修改后的对象
从结构上来看 apply 函数和 run 函数很像,唯一不同点就是它们各自返回的值不一样, run 函数是以闭包形式返回最后一行代码的值, 而 apply 函数的返回的是传入对象的本身 val person = Person("Android轮子哥", 100).apply { name = "HJQ" age = 50 } mRootView = View.inflate(activity, R.layout.example_view, null) mRootView.tv_cancel.paint.isFakeBoldText = true mRootView.tv_confirm.paint.isFakeBoldText = true mRootView.seek_bar.max = 10 mRootView.seek_bar.progress = 0 演变一 mRootView = View.inflate(activity, R.layout.example_view, null).apply { tv_cancel.paint.isFakeBoldText = true tv_confirm.paint.isFakeBoldText = true seek_bar.max = 10 seek_bar.progress = 0 } 多层级判空问题 if (sectionMetaData == null || sectionMetaData.questionnaire == null || sectionMetaData.section == null) { return; } if (sectionMetaData.questionnaire.userProject != null) { renderAnalysis(); return; } if (sectionMetaData.section != null && !sectionMetaData.section.sectionArticles.isEmpty()) { fetchQuestionData(); return; } 演变后 sectionMetaData?.apply { // sectionMetaData 对象不为空的时候操作sectionMetaData }?.questionnaire?.apply { // questionnaire 对象不为空的时候操作questionnaire }?.section?.apply { // section 对象不为空的时候操作section }?.sectionArticle?.apply { // sectionArticle 对象不为空的时候操作sectionArticle }
also 函数
返回:传入对象的本身
返回的是传入对象的本身,一般可用于多个高阶函数链式调用 fun main() { val result = "Android轮子哥".also { println(it.length) } println(result) // 打印:Android轮子哥 }
运算符重载
运算符 | 调用方法 |
---|---|
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
运算符 | 调用方法 |
---|---|
a++ | a.inc() |
a-- | a.dec() |
运算符 | 调用方法 |
---|---|
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b), a.mod(b) (deprecated) |
a..b | a.rangeTo(b) |
运算符 | 调用方法 |
---|---|
a in b | b.contains(a) |
a !in b | !b.contains(a) |
运算符 | 调用方法 |
---|---|
a[i] | a.get(i) |
a[i, j] | a.get(i, j) |
a[i_1, ..., i_n] | a.get(i_1, ..., i_n) |
a[i] = b | a.set(i, b) |
a[i, j] = b | a.set(i, j, b) |
a[i_1, ..., i_n] = b | a.set(i_1, ..., i_n, b) |
运算符 | 调用方法 |
---|---|
a() | a.invoke() |
a(i) | a.invoke(i) |
a(i, j) | a.invoke(i, j) |
a(i_1, ..., i_n) | a.invoke(i_1, ..., i_n) |
运算符 | 调用方法 |
---|---|
a += b | a.plusAssign(b) |
a -= b | a.minusAssign(b) |
a *= b | a.timesAssign(b) |
a /= b | a.divAssign(b) |
a %= b | a.remAssign(b), a.modAssign(b) (deprecated) |
运算符 | 调用方法 |
---|---|
a == b | a?.equals(b) ?: (b === null) |
a != b | !(a?.equals(b) ?: (b === null)) |
运算符 | 调用方法 |
---|---|
a > b | a.compareTo(b) > 0 |
a < b | a.compareTo(b) < 0 |
a >= b | a.compareTo(b) >= 0 |
a <= b | a.compareTo(b) <= 0 |
协程方式
GlobalScope(Dispatchers.Main).launch { 全局作用域 默认是异步线程 viewModelScope.launch 默认是主线程 == (Dispatchers.Main) Dispatchers.IO // 在主线程(Dispatchers.Main)执行 launch(Dispatchers.Main){} withContext(Dispatchers.Main) {} CoroutineScope(Dispatchers.Main).launch { // 👈 在 UI 线程开始 val image = withContext(Dispatchers.IO) { // 👈 切换到 IO 线程,并在执行完成后切回 UI 线程 getImage(imageId) // 👈 将会运行在 IO 线程 } avatarIv.setImageBitmap(image) // 👈 回到 UI 线程更新 UI } 协程就是切线程; 挂起就是可以自动切回来的切线程; 挂起的非阻塞式指的是它能用看起来阻塞的代码写出非阻塞的操作,就这么简单 如果是在 LifecycleOwner 的子类(AppCompatActivity 和 Fragment 都是它的子类)中使用, 这样写出来的协程会在 Lifecycle 派发 destroy 事件的时候 cancel 掉 class TestActivity : AppCompatActivity() { fun test() { lifecycleScope.launch { } } } class TestViewModel : ViewModel() { fun test() { viewModelScope.launch() { } } } 如果我是在 Lifecycle 或者 ViewModel 之外的地方使用协程,又担心内存泄漏,那么该怎么办呢? 可以在合适的时机手动调用 cancel 方法,这样就可以取消 println("测试开始 " + (Thread.currentThread() == Looper.getMainLooper().thread)) val job = GlobalScope.launch() { try { println("测试延迟开始 " + (Thread.currentThread() == Looper.getMainLooper().thread)) delay(20000) println("测试延迟结束") delay(20000) println("测试延迟结束") } catch (e: CancellationException) { // 在这里可以添加一个 try catch 来捕获取消的动作 // 另外需要注意的是,如果在协程体内发生 CancellationException 异常 // 会被协程内部自动消化掉,并不会导致应用程序崩溃的 // 所以一般情况下不需要捕获该异常,除非需要手动释放资源 println("测试协程被取消了") } } println("测试结束") // 手动取消协程 job.cancel()
suspend 关键字
-
协程可以被认为是轻量级线程,是 Kotlin 对线程和 Handler 的 API 的一种封装,是一种优雅处理异步任务的解决方案,协程可以在不同的线程来回切换,这样就可以让代码通过编写的顺序来执行,并且不会阻塞当前线程,省去了在各种耗时操作写回调的情况。
-
当代码执行到有 suspend 关键字修饰的方法上,会先挂起当前线程的执行,需要注意的是这里的挂起是非阻塞式的(也就是不会阻塞当前线程情况下),然后就会去先执行带有 suspend 修饰的方法上,当这个方法执行完成后,会让刚刚挂起的线程继续往下执行,这样我们看到的代码顺序就是代码执行的顺序
-
另外需要注意的是 suspend 本身不会起到一种线程挂起或者线程切换的效果,那么它真正的作用是什么呢?其实它更多的是一种提醒,表示这是一个耗时方法,不能直接执行,需要把我放到协程中去调用,所以我们在写某个耗时方法的时候需要给它加上 suspend 关键字,这样可以有效避免我们在主线程中调用耗时操作造成应用卡顿的情况
线程和协程的区别
协程实现方式 常见的三个操作符号
runBlocking:中文意思是运行阻塞,顾名思义,会阻塞当前线程执行, runBlocking 里面的代码执行完了才会执行 runBlocking 外面的代码 launch:中文意思是启动,不会阻塞当前线程,但是会异步执行代码 async:中文意思是异步,跟 launch 相似,唯一不同的是它可以有返回值 看到这里你是否懂了,async 和 launch 还是有区别的, async 可以有返回值,通过它的 await 方法进行获取, 需要注意的是这个方法只能在协程的操作符或者被 suspend 修饰的方法中才能调用 **await 等待执行结果** println("测试开始 " + (Thread.currentThread() == Looper.getMainLooper().thread)) val async = GlobalScope.async { println("测试延迟开始 " + (Thread.currentThread() == Looper.getMainLooper().thread)) delay(20000) println("测试延迟结束") return@async "666666" } println("测试结束") println("测试返回值:" + async.await()) CoroutineScope(Dispatchers.Main).launch { // 👈 在 UI 线程开始 val image = withContext(Dispatchers.IO) { // 👈 切换到 IO 线程,并在执行完成后切回 UI 线程 getImage(imageId) // 👈 将会运行在 IO 线程 } avatarIv.setImageBitmap(image) // 👈 回到 UI 线程更新 UI } 协程就是切线程; 挂起就是可以自动切回来的切线程; 挂起的非阻塞式指的是它能用看起来阻塞的代码写出非阻塞的操作,就这么简单 协程设计的初衷是为了解决并发问题,让 「协作式多任务」 实现起来更加方便 用同步的方式实现异步的操作
协程的线程调度器
协程调度器的类型,总共有四种:
-
Dispatchers.Main:主线程调度器,人如其名,会在主线程中执行
-
Dispatchers.IO:工作线程调度器,人如其名,会在子线程中执行
-
Dispatchers.Default:默认调度器,没有设置调度器时就用这个,经过测试效果基本等同于
Dispatchers.IO
-
Dispatchers.Unconfined:无指定调度器,根据当前执行的环境而定,会在当前的线程上执行,另外有一点需要注意,由于是直接拿当前线程执行,经过实践,协程块中的代码执行过程中不会有延迟,会被立马执行,除非遇到需要协程被挂起了,才会去执行协程外的代码,这个也是跟其他类型的调度器不相同的地方
协程几个函数的用法:
job.start:启动协程,除了 lazy 模式,协程都不需要手动启动
job.cancel:取消一个协程,可以取消,但是不会立马生效,存在一定延迟
job.join:等待协程执行完毕,这是一个耗时操作,需要在协程中使用
job.cancelAndJoin:等待协程执行完毕然后再取消
withTimeout(300) { // 重复执行 5 次里面的内容 repeat(5) { i -> println("测试输出 " + i) delay(100) } } 除了 withTimeout 这个用法,还有另外一个用法,那就是 withTimeoutOrNull, 这个和 withTimeout 最大的不同的是不会超时之后不会抛 TimeoutCancellationException 给协程,而是直接返回 null, 如果没有超时则会返回协程体里面的结果,具体用法如下: GlobalScope.launch() { val result = withTimeoutOrNull(300) { // 重复执行 5 次里面的内容 repeat(5) { i -> println("测试输出 " + i) delay(100) } return@withTimeoutOrNull "执行完成了" } println("测试输出结果 " + result) }