协程
协程可以简单的理解成一种轻量级的线程,不过它不需要依靠操作系统调度,仅在编程语言的层面就能实现不同协程之间的切换。
fun main() {
GlobalScope.launch {
println("codes run in coroutine scope")
}
Thread.sleep(1000)
}
GlobalScope.launch
函数可以创建一个协程的作用域。
要加上Thread.sleep()
才能正常输出,因为GlobalScope.launch
每次创建的都是顶层协程,这种协程当应用程序运行结束时也会跟着一起结束,代码块还没来得及执行,所以要让程序延迟一下再结束。
GlobalScope.launch {
println("codes run in coroutine scope")
delay(1500)
println("finished")
}
Thread.sleep(1000)
示例代码不会输出第二句。
delay()
函数可以让当前协程指定时间后再运行,它是个非阻塞式挂起函数,只会挂起当前协程。delay()
函数只能在协程的作用域或其他挂起函数中调用。
要让程序在协程中的代码都运行完了再结束,需要使用runBlocking
函数。
runBlocking {
println("codes run in coroutine scope")
delay(1500)
println("finished")
}
runBlocking
函数可以保证在协程作用域内的代码没有执行完一直阻塞当前线程。需要注意runBlocking
函数通常只应该在测试环境下使用,在正式环境中使用容易产生一些性能上的问题。
声明挂起函数关键字suspend
当需要把协程作用域中的代码提取到一个单独的函数中,就没法调用像delay()
这样的挂起函数,因此Kotlin提供了suspend
关键字,用来声明挂起函数,挂起函数之间是可以互相调用的。
suspend fun printDot() {
println(".")
delay(1000)
}
这样就可以在printDot()函数中调用delay()函数了。
coroutineScope
当想在自己写的挂起函数中创建子协程,可以使用coroutineScope函数,它的特点是会继承外部的协程的作用域并创建一个子协程。
suspend fun printDot() = coroutineScope {
launch {
println(".")
delay(1000)
}
}
coroutineScope
函数和runBlocking
相同之处在于都可以保证其作用域内的所有代码和子协程全部执行完之前,外部协程会一直被挂起。
两者不同点是coroutineScope
函数只会阻塞当前协程,既不影响其他协程,也不影响任何线程,因此不会造成任何性能上的问题。
实际项目中常用的写法:
val job = Job()
val scope = CoroutineScope(job)
scope.launch {
//逻辑代码
}
job.cancel()
Job
类型就是launch
函数返回类型,我们创建一个Job
对象,把它传入CoroutineScope()
函数中创建一个CoroutineScope
对象,然后所有用这个对象创建的协程都会被关联在Job
对象的作用域下,这样只要调用一次cancel()方法,就可以将同一作用域内的协程全部取消。
协程中返回结果async和withContext()
launch
函数可以创建一个新的协程,它只能执行一段逻辑,却不能获取执行的结果,返回值永远是一个Job
对象。
async
要想创建一个协程并且获取执行结果可以使用async
函数
runBlocking {
val result = async {
5 + 5
}.await()
println(result)
}
当在调用到async
函数之后,代码块中的代码就会立刻开始执行,当调用await()
方法时,如果代码块中的代码还没执行完,那么await()
方法就会将当前协程阻塞住,直到可以获取async
函数的执行结果。
withContext()
withContext()
是一个挂起函数,大体可以将它理解成async
函数的一种简化版本写法。
runBlocking {
val result = withContext(Dispatchers.Default) {
5 + 5
}
println(result)
}
调用withContext()
函数之后,会立即执行代码块中的代码,同时将外部协程挂起。当代码块中的代码全部执行完之后,会将最后一行的执行结果作为返回值。
withContext()
函数强制要求我们指定一个线程参数,有3种值:
- 低并发策略
Dispatchers.Default
- 高并发策略
Dispatchers.IO
- 主线程
Dispatchers.Main
suspendCoroutine
Retrofit传统写法
val appService = retrofit.create(AppService::class.java)
appService.getAppData().enqueue(object : Callback<List<App>> {
override fun onResponse(call: Call<List<App>>, response: Response<List<App>>) {
//处理数据
}
override fun onFailure(call: Call<List<App>>, t: Throwable) {
t.printStackTrace()
}
})
suspendCoroutine
函数可以将传统的回调机制写法大幅简化。
suspendCoroutine
函数必须在协程作用域或挂起函数中才能调用,它接收一个Lambda表达式参数,主要作用是将当前协程立即挂起,然后在一个普通的线程中执行Lambda表达式中的代码。Lambda表达式的参数列表上会传入一个Continuation
参数,调用它的resume()
方法或resumeWithException()
可以让协程恢复执行。
定义一个await()
函数:
suspend fun <T> Call<T>.await(): T {
return suspendCoroutine { continuation ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val body = response.body()
if (body != null)
continuation.resume(body)
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
使用了suspendCoroutine函数来挂起当前协程,Lambda表达式中的代码则会在普通线程中执行,如果请求成功就调用Continuation的resume()方法恢复被挂起的协程,失败的话resumeWithException()恢复被挂起的协程并传入具体的异常原因。
使用:
suspend fun getAppdata() {
try {
val appList = ServiceCreator.create<AppService>().getAppData().await()
} catch (e: Exception) {
//对异常处理
}
}
Jetpack
Jetpack是一个开发组件工具集,它的主要目的是帮助我们编写出更加简洁的代码,并简化我们的开发过程。
ViewModel
ViewModel的一个重要作用就是可以帮助Activity分担一部分工作,它是专门用于存放与界面相关的数据的。它的生命周期比Activity还要长。
我们不可以直接创建Viewmodel
实例,而一定要通过ViewModelProvider
来获取ViewModel
的实例,是为了防止Activity的onCreate()
方法每次执行的时候都创建一个新的实例。
lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
}
ViewModelProvider.Factory
如果要向ViewModel传递参数需要创建一个类实现ViewModelProvider.Factory
接口
class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MainViewModel(countReserved) as T
}
}
使用:
viewModel = ViewModelProvider(this, MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
Lifecycles
Lifecycles组件可以让任何一个类都能轻松感知到Activity的生命周期,同时又不需要在Activity中编写大量的逻辑处理。
class MyObserver(val lifecycle: Lifecycle) : LifecycleObserver {
}
使用:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycle.addObserver(MyObserver(lifecycle))
}
LiveData
LiveData是Jetpack提供的一种响应式编程组件,它可以包含任何类型的数据,并在数据发生变化的时候通知给观察者。用数据驱动的思想替换了常规接口回调的方式。
class MainViewModel(countReserved: Int) : ViewModel() {
var counter = MutableLiveData<Int>()
init {
counter.value = countReserved
}
fun plusOne() {
val count = counter.value ?: 0
counter.value = count + 1
}
fun clear() {
counter.value = 0
}
}
使用:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding.btJtPlus.setOnClickListener {
viewModel.plusOne()
}
binding.btJtClear.setOnClickListener {
viewModel.clear()
}
viewModel.counter.observe(this) { count ->
binding.tvJtText.text = count.toString()
}
}
任何LiveData对象都可以调用它的observe()方法来观察数据的变化,当数据发生变化时,就会回调到这里进行必要的操作。
以上写法其实是不规范的LiveData用法,主要的问题就在于我们将counter这个可变的LiveData暴露给了外部。
Android官方推荐写法:
class MainViewModel(countReserved: Int) : ViewModel() {
val counter: LiveData<Int>
get() = _counter
private val _counter = MutableLiveData<Int>()
init {
_counter.value = countReserved
}
fun plusOne() {
val count = _counter.value ?: 0
_counter.value = count + 1
}
fun clear() {
_counter.value = 0
}
}
这里先将原来的counter变量改名为_counter变量,并给它加上private修饰符,这样_counter变量对于外部就是不可见的了。
map和switchMap
map()
map()
个方法的作用是将实际包含数据的LiveData和仅用于观察数据的LiveData进行转换。
示例:有个User类,只需要用到用户的姓名,不需要年龄
data class User(var firstName: String, var lastName: String, var age: Int)
class MainViewModel(): ViewModel() {
private val userLiveData = MutableLiveData<User>()
val userName: LiveData<String> = Transformations.map(userLiveData) { user ->
"${user.firstName} ${user.lastName}"
}
}
这里我们调用了Transformations
的map()
方法来对LiveData的数据类型进行转换。map()
方法接收两个参数:第一个参数是原始的LiveData对象;第二个参数是一个转换函数,我们在转换函数里编写具体的转换逻辑即可。
switchMap()
当ViewModel中的某个LiveData对象是调用另外的方法获取的,就要用到switchMap()
来转换成内部的。
外部单例类获取LiveData:
object Repository {
fun getUser(userId: String): LiveData<User> {
val liveData = MutableLiveData<User>()
liveData.value = User(userId, userId, 0)
return liveData
}
}
这样获取到的LiveData实例每次都是新的,不能观察到数据的变化。
class MainViewModel(): ViewModel() {
private val userLiveData = MutableLiveData<String>()
val user: LiveData<User> = Transformations.switchMap(userLiveData) { userId ->
Repository.getUser(userId)
}
fun getUser(userId: String) {
userLiveData.value = userId
}
}
这里我们定义了一个新的userIdLiveData对象,用来观察userId的数据变化,然后调用了Transformations
的switchMap()
方法,用来对另一个可观察的LiveData对象进行转换。
使用:
binding.btGetUser.setOnClickListener {
val userId = (0..1000).random().toString()
mainViewModel.getUser(userId)
}
mainViewModel.user.observe(this, Observer {
binding.tvJtText.text = it.firstName
})
Room
为了更好的使用数据库功能,Android官方推出了一个ORM框架,就是Room。
Room的整体结构。它主要由Entity、Dao和Database这3部分组成。
- Entity。用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张对应的表,并
且表中的列是根据实体类中的字段自动生成的。 - Dao。Dao是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际
编程的时候,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互即可。 - Database。用于定义数据库中的关键信息,包括数据库的版本号、包含哪些实体类以及提
供Dao层的访问实例。
WorkManager
WorkManager很适合用于处理一些要求定时执行的任务,它可以根据操作系统的版本自动选择底层是使用AlarmManager实现还是JobScheduler实现,从而降低了我们的使用成本。
WorkManager和Service并不相同,也没有直接的联系。Service是Android系统的四大组件之一,它在没有被销毁的情况下是一直保持在后台运行的。而WorkManager只是一个处理定时任务的工具,它可以保证即使在应用退出甚至手机重启的情况下,之前注册的任务仍然将会得到执行,因此WorkManager很适合用于执行一些定期和服务器进行交互的任务,比如周期性地同步数据,等等。
MVVM架构
MVVM(Model-View-ViewModel)是一种高级项目架构模式。
由3部分组成:
- Model是数据模型部分;
- View是界面展示部分;
- ViewModel可以理解成一个连接数据模型和界面展示的桥梁;
从而实现让业务逻辑和界面展示分离的程序结构设计。
- UI控制层包含了我们平时写的Activity、Fragment、布局文件等与界面相关的东西。
- ViewModel层用于持有和UI元素相关的数据,以保证这些数据在屏幕旋转时不会丢失,并且还要提供接口给UI控制层调用以及和仓库层进行通信。
- 仓库层要做的主要工作是判断调用方请求的数据应该是从本地数据源中获取还是从网络数据源中获取,并将获取到的数据返回给调用方。
图中所有的箭头都是单向的,比方说UI控制层指向了ViewModel层,表示UI控制层会持有ViewModel层的引用,但是反过来ViewModel层却不能持有UI控制层的引用,还有引用也不能跨层持有。