参考:第一行代码第三版
目录
泛型实化
在java中,泛型功能是通过类型擦除功能机制来实现的,也就是说在运行时JVM是识别不出来我们在代码中指定的泛型类型的
但是在kotlin中提供了内联函数的概念,也就不存在泛型擦除的问题,因为代码在编译之后会直接使用实际的类型来替代内联函数的泛型声明
所以泛型实化,首先该函数必须是内联函数,然后在声明泛型的地方必须加上reified关键字来表示该泛型进行实化
举例代码:
inline fun <reified T> getGenericType() = T::class.java
泛型实化的应用
创建一个reified.kt文件
inline fun <reified T> startActivity(context: Context) {
val intent = Intent(context, T::class.java)
context.startActivity(intent)
}
startActivity<SecondActivity>(this)
这样会有一个问题,在intent跳转的时候怎么传参
使用以下的方法:
inline fun <reified T> startActivity(context: Context, block: Intent.() -> Unit) {
val intent = Intent(context, T::class.java)
intent.block()
context.startActivity(intent)
}
使用:
startActivity<SecondActivity>(this){
putExtra("param1","data1")
putExtra("param2",123)
}
协程
可以理解为一种轻量级的线程,让我们可以使用编程实现不同协程之间的切换,使得高并发程序的运行效率得到极大的提升;
依赖:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
开启协程的几种方式:
1.使用Global.launch
每次创建的都是一个顶层协程,这种协程当应用程序运行结束也会跟着一起结束
fun main() {
GlobalScope.launch {
println("codes run in coroutine scope")
}
Thread.sleep(1000)
}
2.使用runBlocking
同样会创建一个协程的作用域,可以保证协程作用域内的所有代码和子协程没有完全执行前一直阻塞当前线程,即可以让协程中所有代码运行完成后结束
效率较低,只应该在测试环境中使用
runBlocking {
println("hello")
}
3.创建多个协程
使用launch函数,首先launch必须在协程的作用域中才能调用,其次会在当前协程的作用域下创建子协程
子协程的特点就是如果外层协程结束了,则所有的子协程都会结束
runBlocking {
launch {
println("launch1")
delay(1000)
println("launch1 finish")
}
launch {
println("launch2")
delay(1000)
println("launch2 finish")
}
}
运行结果:
协程的并发,循环创建100000个协程
val start = System.currentTimeMillis()
runBlocking {
repeat(100000) {
launch {
println(".")
}
}
}
val end = System.currentTimeMillis()
println(end - start)
可以看到:100000个协程只用了624ms,
delay
是一个非阻塞式的挂起函数,可以让协程延迟指定时间后运行,只能在协程的作用域或其他挂起函数中使用
suspend
可以将任意函数声明为挂起函数,挂起函数之间可以相互调用,无法提供协程作用域
launch
用于创建多个协程,必须在协程作用域中才能调用(所以要依靠coroutineScope),可以用于创建子协程
coroutineScope
也是一个挂起函数,可以在任何的其他挂起函数中调用,会继承外部的协程作用域并创建一个子作用域,我们借助这个函数可以给任意挂起函数提供协程作用域
可以保证其作用域内的所有代码和子协程在全部执行完之前,会一直阻塞当前协程
fun main() {
//创建了协程作用域
runBlocking {
//创建了子协程作用域
coroutineScope {
//创建一个协程,必须在协程作用域中使用
launch {
for (i in 1..10){
println(i)
delay(1000)
}
}
}
println("coroutineScope finished")
}
println("runBlocking finished")
}
coroutineScope和runBlocking的区别
两者作用作用很类似,都可以提供协程的作用域
coroutineScope只会阻塞当前协程,不影响其他协程,也不影响其他线程
runBlocking会阻塞当前线程,如果在主线程中使用,可能会导致界面卡死的情况,所以不推荐在实际项目中使用
总结:
GlobalScope.launch和runBlocking可以在任意地方调用,coroutineScope函数可以在协程作用域或挂起函数中调用,而launch只能在协程作用域中调用;
由于GlobalScope.launch创建的是顶层协程,而顶层协程管理维护成本过高,所以实际项目中不适用
协程常见写法+取消:
val job = Job()
val scope = CoroutineScope(job)
scope.launch {
println(".")
delay(1000)
}
job.cancel()
这里的CoroutineScope是一个函数,返回一个CoroutineScope的对象,然后就可以调用它的launch函数来创建一个协程了,这里的launch创建的协程,都会被关联在Job对象的作用域下面,只需要调用一次cancel方法,就可以将同一作用域下的所有协程全部取消
获取协程执行结果
因为launch函数可以创建一个新的协程,但是不能获取执行结果,所以要使用async函数去实现
async
必须在协程作用域中才能调用,会创建一个新的子协程并返回一个Deferred对象,我们获取执行结果,只需要调用Deferred对象的await()方法即可
runBlocking {
val result = async {
5 + 5
}.await()
println(result)
}
当调用了async函数之后,代码块中的代码会立即开始执行,当调用await()方法时,如果代码块中的代码没有执行完,那么await()会将当前协程阻塞住,直到可以获得async函数的执行结果
验证:如下代码
runBlocking {
val start = System.currentTimeMillis()
val result1 = async {
delay(1000)
5 + 5
}.await()
val result2 = async {
delay(1000)
4 + 6
}.await()
println("result is ${result1 + result2}")
val end = System.currentTimeMillis()
println("cost ${end - start} ms")
}
可以看出await是在async函数代码块执行完之前会一直将当前协程阻塞的,而且可以看出async是一种串行方式运行的
改进:两个async函数同时运行来提高速度
runBlocking {
val start = System.currentTimeMillis()
val result1 = async {
delay(1000)
5 + 5
}
val result2 = async {
delay(1000)
4 + 6
}
println("result is ${result1.await() + result2.await()}")
val end = System.currentTimeMillis()
println("cost ${end - start} ms")
}
可以看到我们在需要执行结果时才调用await()方法进行获取,这样两个async函数就变成一种并行关系了
withContext
是一个挂起函数,大致可以将它理解为async函数的一种简化版写法
val result1 = withContext(Dispatchers.Default) {
delay(1000)
5 + 5
}
与
val result1 = async {
delay(1000)
5 + 5
}.await()
是等价的
不过withContext函数强制要求我们制定一个线程参数,给协程指定一个具体的运行线程
这里要指定线程的意义:因为开启了新的协程也是在主线程中,所以还是不能进行网络耗时操作,因此需要指定一个子线程
线程参数:Dispatchers.Default是一种默认低并发的线程策略,Dispatchers.IO是一种较高并发的线程策略,要执行网络请求时,应使用;Dispatchers.Main表示不会开启子线程,而是在主线程中执行代码;
suspendCoroutine
必须在协程作用域或挂起函数中才能调用
作用:接收一个Lambda表达式,将当前协程立即挂起,然后在一个普通的线程中执行Lambda表达式中的代码;Lambda表达式的参数列表上会传入一个Continuation参数,调用它的resume()方法或resumeWithException()可以让协程恢复执行;
使用:(配合Retrofit使用)
//Call的扩展函数
suspend fun <T> Call<T>.await(): T {
//使用suspendCoroutine挂起当前协程
return suspendCoroutine { continuation ->
//因为是扩展函数,所以有Call的上下文
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val body = response.body()
if (body != null) continuation.resume(body)
//如果body为null则处理异常
else continuation.resumeWithException(
RuntimeException("response body is null")
)
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
object ServiceCreator {
private const val BASE_URL = "https://api.caiyunapp.com/"
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass)
//泛型实化
inline fun <reified T> create(): T = create(T::class.java)
}
interface WeatherService {
@GET("v2.5/${SunnyWeatherApplication.TOKEN}/{lng},{lat}/realtime.json")
fun getRealtimeWeather(@Path("lng") lng: String, @Path("lat") lat: String): Call<RealtimeResponse>
@GET("v2.5/${SunnyWeatherApplication.TOKEN}/{lng},{lat}/daily.json")
fun getDailyWeather(@Path("lng") lng: String, @Path("lat") lat: String): Call<DailyResponse>
}
private val weatherService = ServiceCreator.create(WeatherService::class.java)
suspend fun getDailyWeather(lng: String, lat: String) = weatherService.getDailyWeather(lng, lat).await()