第一章 Jetpack基础 本章节所有代码地址:https://gitee.com/evancola/jetpack-study.git
1.1 Lifecycle
LifeCycle 是一个可以感知宿主生命周期变化的组件。常见的宿主包括 Activity/Fragment、Service 和 Application。LifeCycle 会持有宿主的生命周期状态的信息,当宿主生命周期发生变化时,会通知监听宿主的观察者。
Jetpack提供了两个接口:
LifecycleOwner 被观察者
LifecycleObserver 观察者
被监听的系统组件需要去实现 LifecycleOwner 接口,观察者需要实现 LifecycleObserver 接口
使用场景
监听activity生命周期
监听service生命周期
使用ProcessLifecycle监听应用生命周期
监听activity生命周期 声明一个类实现LifecycleObserver,并重写相关生命周期方法,方法名可以随便定义,通过注解来绑定生命周期,这样这个方法就相当于生命周期的回调方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class MyObserver :LifecycleObserver { companion object { const val TAG = "MyListener" } @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun onCreate () { Log.d(TAG, "onCreate: " ) } @OnLifecycleEvent(Lifecycle.Event.ON_START) fun onStart () { Log.d(TAG, "onStart: " ) } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onResume () { Log.d(TAG, "onResume: " ) } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun onPause () { Log.d(TAG, "onPause: " ) } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onStop () { Log.d(TAG, "onStop: " ) } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun onDestroy () { Log.d(TAG, "onDestroy: " ) } }
添加观察者
1 2 3 4 5 6 7 8 class MainActivity : AppCompatActivity () { override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) lifecycle.addObserver(MyObserver()) } }
注意:默认情况下,如果是使用的androidX的activity,已经实现了LifecycleOwner 接口,如果你的项目的activity还是使用的support库,最新版的SDK,也通过supportActivity实现了LifecycleOwner接口
监听service生命周期 添加依赖:
1 implementation "androidx.lifecycle:lifecycle-service:2.2.0"
第一步:创建观察者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class MyServiceObserver :LifecycleObserver { companion object { const val TAG = "MyServiceObserver" } @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun onCreate () { Log.d(TAG, "onCreate: " ) } @OnLifecycleEvent(Lifecycle.Event.ON_START) fun onStart () { Log.d(TAG, "onStart: " ) } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun onDestroy () { Log.d(TAG, "onDestroy: " ) } }
第二步:创建service
1 2 3 4 5 6 class MyService:LifecycleService() { private val observer = MyServiceObserver() init { lifecycle.addObserver(observer) //添加观察者 } }
第三步:在activity中创建service,启动service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class LifeCycle2 : AppCompatActivity () { override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_lifecycle2) } fun stop (view: View ) { stopService(Intent(this ,MyService::class .java)) } fun start (view: View ) { startService(Intent(this ,MyService::class .java)) } }
监听应用生命周期 以前我们都是通过自己维护activity栈来监听应用生命周期,很麻烦,很low,现在使用Jetpack中的ProcessLifecycleOwner监听则非常方便
第一步:添加依赖
1 2 // optional - ProcessLifecycleOwner provides a lifecycle for the whole application process implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
第二步:编写观察者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class AppLifecycleObserver :LifecycleObserver { companion object { const val TAG = "AppLifecycleObserver" } @OnLifecycleEvent(Lifecycle.Event.ON_START) fun onAppForeground () { Log.d(TAG, "onAppForeground: APP进入前台啦" ) } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onAppBackground () { Log.d(TAG, "onAppBackground: APP进入后台啦" ) } }
第三步:在application中添加观察者
1 2 3 4 5 6 7 class App : Application () { override fun onCreate () { super .onCreate() ProcessLifecycleOwner.get ().lifecycle.addObserver(AppLifecycleObserver()) } }
1.2 ViewModel
ViewModel可以解耦model和view,用于管理和存储数据,不会受到屏幕旋转而丢失瞬时数据
引入依赖:
1 2 3 4 5 6 7 8 9 10 // ViewModel implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" //如要要使用kotlin属性委托方式生成viewModel def activity_version = "1.2.2" implementation "androidx.activity:activity-ktx:$activity_version" //fragment 如要要使用kotlin属性委托方式生成viewModel def fragment_version = "1.3.3" implementation "androidx.fragment:fragment-ktx:$fragment_version"
创建ViewModel
1 2 3 class MyViewModel:ViewModel() { var num:Int = 0 }
activity中获取ViewModel
有两种方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class ViewModelActivity : AppCompatActivity () { private lateinit var binding: ActivityViewModelBinding private val mViewModel:MyViewModel by viewModels() private lateinit var mViewModel: MyViewModel override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) binding = ActivityViewModelBinding.inflate(layoutInflater) setContentView(binding.root) mViewModel = ViewModelProvider( this , ViewModelProvider.AndroidViewModelFactory.getInstance(this .application) ).get (MyViewModel::class .java) binding.mTvText.text = mViewModel.num.toString() } fun plus (view: View ) { binding.mTvText.text = (++mViewModel.num).toString() } }
这里我们使用了ViewBinding,参考下一节
fragment中共享数据
需求:我们在activity的布局中,直接放入两个fragment,现在需要让彼此通信,共享数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class OneFragment : Fragment () { private val viewModel: SharedViewModel by activityViewModels() private lateinit var binding: VmFragmentBinding override fun onCreateView ( inflater: LayoutInflater , container: ViewGroup ?, savedInstanceState: Bundle ? ) : View? { binding = VmFragmentBinding.inflate(inflater, container, false ) return binding.root } override fun onViewCreated (view: View , savedInstanceState: Bundle ?) { super .onViewCreated(view, savedInstanceState) viewModel.num.observe(viewLifecycleOwner, Observer { binding.mTvContent.text = it.toString() }) binding.mBtnPlus.setOnClickListener { viewModel.add() } } }
注意:两个fragment获取的是同一个ViewModel,所以它们之间能共享数据,
第二个fragment代码类似,这里就不列出来了,下面来看viewModel
1 2 3 4 5 6 7 8 class SharedViewModel : ViewModel () { var num = MutableLiveData(0 ) fun add () { num.value = num.value?.plus(1 ) } }
1.3 ViewBinding
在新版的Android studio中废弃了 Kotlin Extensions,也就是直接用布局里面控件id拿到对象
启用ViewBinding
1 2 3 viewBinding { enabled = true }
获取binding
针对每个布局都会生成对应binding类
1 2 3 4 5 6 7 8 9 10 11 12 private lateinit var binding: ActivityViewModelBindingoverride fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) binding = ActivityViewModelBinding.inflate(layoutInflater) setContentView(binding.root) binding.mTvText.text = mViewModel.num.toString() }
1.4 LiveData
LiveData是一种具有生命周期感知能力的可观察的数据存储器类,它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期,这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
1.LiveData基础 创建LiveData
1 2 3 4 5 6 class NameViewModel :ViewModel () { val mCurrentName:MutableLiveData<String> by lazy { MutableLiveData<String>() } }
观察和修改LiveData
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class LiveDataActivity : AppCompatActivity () { private val nameViewModel: NameViewModel by viewModels() private lateinit var binding: ActivityLiveDataBinding override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) binding = ActivityLiveDataBinding.inflate(layoutInflater) setContentView(binding.root) nameViewModel.mCurrentName.observe(this , Observer { binding.mTvTitle.text = it }) binding.mBtnChange.setOnClickListener { nameViewModel.mCurrentName.value = "三上亚悠" } } }
2.转换LiveData之map和switchMap 什么时候需要转换?当你将livedata的数据分发给观察者之前,想对数据做一些改变,或者根据一个livedata生成另一个livedata
map作用:将一个函数作用于livedata中的数据,传播到下游(观察者)
switchmap:和map类似,也是将一个函数作用于livedata中的数据,但是函数必须返回livedata
map举例:比如有一个user类,有用户名和年龄等字段,我们在viewModel中声明LiveData<User>,但是界面中只需要用户姓名,所以可以这样做
1 2 3 4 5 6 7 8 9 data class User (var firstName: String, var lastName: String, var age: Int )class MainViewModel (countReserved: Int ) : ViewModel() { private val userLiveData = MutableLiveData<User>() val userName: LiveData<String> = Transformations.map(userLiveData) { user -> "${user.firstName} ${user.lastName} " } ... }
使用map方法,将LiveData<User> 转为 LiveData<String> 进行观察
switchMap :查看如下代码,不建议这样写,每次调用getPostalCode方法,返回的新的livedata,而界面中观察的老的livedata对象
1 2 3 4 5 6 7 class MyViewModel (private val repository: PostalCodeRepository) : ViewModel() { private fun getPostalCode (address: String ) : LiveData<String> { return repository.getPostCode(address) } }
更建议的写法(switchMap)
1 2 3 4 5 6 7 8 9 10 class MyViewModel (private val repository: PostalCodeRepository) : ViewModel() { private val addressInput = MutableLiveData<String>() val postalCode: LiveData<String> = Transformations.switchMap(addressInput) { address -> repository.getPostCode(address) } private fun setInput (address: String ) { addressInput.value = address } }
执行流程:当页面调用setInput方法时,不会发起请求或者函数调用,只会改变addressInput这个livedata的值,livedata发生变化的时候,switch会感知变化,会调用getPostCode,并将结果转为postalCode,界面中只需要去观察postalCode这个liveData
注意:
如果是没有参数,则只需要重新赋值即可,也会感知变化(其实并没变化),如下写法:
当界面中调用fetchAll方法,livedata的value重新赋值了,也会触发switchMap执行
1 2 3 4 5 6 7 8 val fetch:MutableLiveData<Any?> = MutableLiveData() val users:LiveData<List<User>> = Transformations.switchMap(fetch){ it-> repository.getAll() } fun fetchAll(){ fetch.value = fetch.value }
LiveData结合Room
待更新
LiveData结合协程
待更新
1.5 DataBinding 1. 启用dataBinding 1 2 3 dataBinding{ enabled = true }
2.布局和绑定表达式 注意:在xml布局中的根布局需改为layout
声明对象
1 2 3 4 5 <data > <variable name="user" type="com.ivan.lifecycle.demo7_databinding.User" /> </data >
绑定表达式
1 2 3 4 5 <TextView android:id="@+id/mTvFirstName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" />
数据对象
1 2 3 4 5 6 7 8 9 10 11 12 13 class UserActivity : AppCompatActivity () { private lateinit var binding: ActivityUserBinding override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) binding = ActivityUserBinding.inflate(layoutInflater) setContentView(binding.root) binding.user = User("三上" , "悠亚" ) } }
常用的表达式
1 2 3 android:text="@{String.valueOf(index + 1)}" android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}" android:transitionName='@{"image_" + id}'
??表达式:如果左边为null,则使用右边的值
1 android:text="@{user.displayName ?? user.lastName}"
集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <data> <import type="android.util.SparseArray"/> <import type="java.util.Map"/> <import type="java.util.List"/> <variable name="list" type="List<String>"/> <variable name="sparse" type="SparseArray<String>"/> <variable name="map" type="Map<String, String>"/> <variable name="index" type="int"/> <variable name="key" type="String"/> </data> … android:text="@{list[index]}" … android:text="@{sparse[index]}" … android:text="@{map[key]}"
**注意**:要使 XML 不含语法错误,您必须转义
<字符。例如:不要写成
List< String > 形式,而是必须写成List<String>
。
双引号单引号:
1 android:text='@{map["firstName"]}' 等价于 android:text="@{map[`firstName`]}"
引入资源:
1 android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
事件处理:有两种方式
方法引用:
如下有一个类MyHandlers,提供一个方法showToast
1 2 3 4 5 6 class MyHandlers { fun showToast (view:View ) { Toast.makeText(view.context, "点击了" , Toast.LENGTH_SHORT).show() } }
xml中调用showToast方法
1 2 3 4 5 6 7 <variable name="handler" type="com.ivan.lifecycle.demo7_databinding.MyHandlers" /> <TextView android:onClick="@{handler::showToast}" >
activity中进行赋值
1 binding.handler = MyHandlers()
监听器绑定
1 2 3 fun onClick2 (name:String ) { println("点击2$name " ) }
1 2 <TextView android:onClick="@{()-> handler.onClick2(user.secondName)}">
3.import导入 4.bindingAdapter 参考官方文档:https://developer.android.google.cn/topic/libraries/data-binding/binding-adapters
1.6 Room Room参考文档:https://developer.android.google.cn/training/data-storage/room?hl=zh_cn
Room数据库主要包含三个部分:
entity:实体类
dao:操作数据库的方法
database:数据库对象,抽象类
添加依赖:
1 2 3 4 5 def room_version = "2.3.0" implementation "androidx.room:room-runtime:$room_version " kapt "androidx.room:room-compiler:$room_version " implementation "androidx.room:room-ktx:$room_version "
创建实体类:
1 2 3 4 5 6 7 8 @Entity data class User ( @ColumnInfo(name = "name" ) val name: String? = null , @ColumnInfo(name = "country" ) val country: String? = null ) { @PrimaryKey(autoGenerate = true) var id: Long = 0 }
注意:
主键不要放在data class 的主构造方法中,插入数据的时候,不需要传id,由数据库自增
创建dao
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Dao interface UserDao { @Query("select * from user" ) fun getAll () :LiveData<List<User>> @Insert fun insert (user: User ) @Update() fun update (user:User ) @Delete fun delete (user: User ) @Query("delete from user" ) fun deleteAll () }
创建数据库对象(单例模式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Database(entities = [User::class], version = 1) abstract class AppDatabase : RoomDatabase () { abstract fun userDao () : UserDao companion object { private var instance: AppDatabase? = null @Synchronized fun getInstance (context: Context ) : AppDatabase { instance?.let { return it } return Room.databaseBuilder( context.applicationContext, AppDatabase::class .java, "user.db" ).build().apply { instance = this } } } }
创建repository
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class UserRepository (context: Context) { private var userDao:UserDao = AppDatabase.getInstance(context).userDao() fun insertUser (user: User ) { thread { userDao.insert(user) } } fun update (user: User ) { thread { userDao.update(user) } } fun deleteUser (user: User ) { thread { userDao.delete(user) } } fun deleteAll () { thread { userDao.deleteAll() } } fun getAll () :LiveData<List<User>>{ return userDao.getAll() } }
创建ViewModel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class UserViewModel (app: Application) : AndroidViewModel(app) { private var repository = UserRepository(app) fun insert (user: User ) { repository.insertUser(user) } fun update (user: User ) { repository.update(user) } fun deleteUser (user: User ) { repository.deleteUser(user) } fun deleteAll () { repository.deleteAll() } fun getAll () :LiveData<List<User>>{ return repository.getAll() } }
在activity中操作数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class RoomMainActivity : AppCompatActivity () { lateinit var binding:ActivityRoomMainBinding private val mViewModel:UserViewModel by viewModels() override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) binding = ActivityRoomMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.mRvList.adapter = UserListAdapter(mutableListOf()) mViewModel.getAll().observe(this , Observer { println("数据改变了:${it.size} " ) (binding.mRvList.adapter as UserListAdapter).setData(it) }) } fun insert (view: View ) { val user = User(name = "赵丽颖" ,country = "中国" ) mViewModel.insert(user) } fun update (view: View ) { val user = User(name = "科比" ,country = "美国" ).apply { id = 4 } mViewModel.update(user) } fun delete (view: View ) { val user = User().apply { id = 4 } mViewModel.deleteUser(user) } fun clear (view: View ) { mViewModel.deleteAll() } }
说明
:当新增,修改,删除了表中的数据,LiveData都能观察到数据的变化 说明,最新代码已改,主要参考livedata中的switchMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private val refreshLiveData:MutableLiveData<Any?> = MutableLiveData() val users:LiveData<List<User>> = Transformations.switchMap(refreshLiveData){ it-> repository.getAll() } fun fetchAll(){ refreshLiveData.value = refreshLiveData.value } mViewModel.users.observe(this, Observer { println("users数据改变了:${it.size}") (binding.mRvList.adapter as UserListAdapter).setData(it) }) //一进入界面主动获取数据,点击的时候也调用这个方法 mViewModel.fetchAll()
1.7WorkManager 1.添加依赖 1 2 3 4 5 6 def work_version = "2.5.0" implementation "androidx.work:work-runtime:$work_version" implementation "androidx.work:work-runtime-ktx:$work_version"
2.基本用法
定义一个后台任务,并实现具体的任务逻辑
配置该后台任务的运行条件和约束信息,并构建后台任务请求
将该后台任务请求传入WorkManager的enqueue()方法中,系统会在合适的时间运行
3.实际案例 第一步:创建后台任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class SimpleWorker (context:Context,params:WorkerParameters):Worker(context,params) { override fun doWork () : Result { Log.d("SimpleWorker" , "do work in SimpleWorker" ) return Result.success() } }
提示
:doWork()方法要求返回一个Result对象,用于表示任务的运行结果,成功就返回Result.success(),失败就返回Result.failure(),还有一个Result.retry()方法,它其实也代表着失败,只是可以结合WorkRequest.Builder的setBackoffCriteria()方法来重新执行任务
第二三步合并如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class WorkManagerTestActivity : AppCompatActivity () { override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_work_manager_test) } fun executeTask (view: View ) { val request = OneTimeWorkRequestBuilder<SimpleWorker>().build() WorkManager.getInstance(this ).enqueue(request) } }
WorkRequest.Builder的子类:
OneTimeWorkRequest.Builder:用于构建单次运行的后台任务请求
PeriodicWorkRequest.Builder:用于构建周期性运行的后台任务请求,但是为了降低设备性能消耗,PeriodicWorkRequest.Builder构造函数中传入的运行周期间隔不能短于15分钟
代码如下:
1 val request = PeriodicWorkRequest.Builder(SimpleWorker::class .java, 15 ,TimeUnit.MINUTES).build()
4.复杂应用 1.延迟执行 setInitialDelay
1 2 3 val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java) .setInitialDelay(5, TimeUnit.MINUTES) .build()
2.添加标签,取消后台任务 1 2 3 4 5 6 7 8 9 10 11 val request = OneTimeWorkRequest.Builder(SimpleWorker::class .java) ... .addTag("simple" ) .build() WorkManager.getInstance(this ).cancelAllWorkByTag("simple" ) WorkManager.getInstance(this ).cancelWorkById(request.id) WorkManager.getInstance(this ).cancelAllWork()
3.重新执行任务 setBackoffCriteria
1 2 3 4 val request = OneTimeWorkRequest.Builder(SimpleWorker::class .java) ... .setBackoffCriteria(BackoffPolicy.LINEAR, 10 , TimeUnit.SECONDS) .build()
参数含义:第二个和第三个参数用于指定在多久之后重新执行任务,时间最短不能少于10秒钟;第一个参数表示如果任务再次失败,下次如何执行
4.链式任务 依次执行任务,前一个任务执行成功,才会继续执行下一个任务
1 2 3 4 5 6 7 8 val sync = ...val compress = ...val upload = ...WorkManager.getInstance(this ) .beginWith(sync) .then(compress) .then(upload) .enqueue()
1.8 Startup
Startup这个组件,对于我们来说,没啥用处,Startup出现的原由,现在越来越多的三方库的初始化,都简化了,不需要我们手动调用初始化,库作者将初始化放在了ContentProvider中初始化,这是为什么呢,因为ContentProvider的onCreate执行时机是在Application的attatchBaseContext()和onCreate()之间,但是ContentProvider作为四大组件之一,是很重量级的,如果这样的三方库过多,APP启动时间会很长,Startup组件的诞生就是来解决这个问题
1.9 Hilt
依赖注入
dagger1:Square公司发布的,基于Java反射
dagger2: Google fork了dagger1,基于Java注解修改后,发布了dagger2
1.10 paging 第二章 kotlin协程 Google kotlin协程文档:https://developer.android.google.cn/kotlin/coroutines
协程的特点:
轻量,单个线程上可以运行多个协程,协程支持挂起,而挂起不会阻塞正在运行协程的线程
2.1协程基础 1.添加依赖 1 2 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'//kotlin协程核心库 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'//Android上的协程扩展
2.创建协程 创建协程有多种方式
GlobalScope.launch 非阻塞式,顶级协程
runBlocking 阻塞式(协程内的代码执行完之前,会阻塞当前线程),一般只在测试环境下使用
launch:非阻塞式,这个函数必须在协程作用域中调用,会在当前协程内创建子协程,子协程特点:外层协程执行完,子协程也会结束,创建多个launch协程,是并发执行的
coroutineScope:阻塞式,挂起函数,会获得外部协程作用域,同时会阻塞外部协程
async:只能在协程作用域中调用,创建子协程,返回一个Deferred对象,如果需要获取结果,调用await方法
withContext:简化的async,可以指定线程(强制,必须指定),线程参数有三个值
Dispatchers.Default 适用于计算密集型任务
Dispatchers.IO:并发线程策略,如异步网络请求,会开启线程
Dispatchers.Main:主线程,只能用于Android
GlobalScope
1 2 3 4 5 6 7 fun main () { GlobalScope.launch { println("codes run in coroutine scope" ) } Thread.sleep(2000 ) }
runBlocking
1 2 3 4 5 6 7 8 9 fun main () { println("before runBlocking" ) runBlocking { println("协程代码执行啦" ) delay(2000 ) println("协程代码执行结束啦" ) } println("after runBlocking" ) }
执行结果:
before runBlocking 协程代码执行啦 协程代码执行结束啦 after runBlocking
可以看出,runBlocking会阻塞当前线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 fun main () { runBlocking { println("外协程执行开始" ) launch { println("launch1" ) delay(1000 ) println("launch1 finished" ) } launch { println("launch2" ) delay(1000 ) println("launch2 finished" ) } println("外协程执行完毕" ) } }
两个launch子协程是交替并发执行的
并发创建10万个协程
1 2 3 4 5 6 7 8 9 10 11 12 fun main () { val start = System.currentTimeMillis() runBlocking { repeat(100000 ) { launch { println("." ) } } } val end = System.currentTimeMillis() println("${end - start} " ) }
执行结果只耗时几百毫秒,可以看出,这10万个协程并发的执行
coroutineScope
1 2 3 4 5 6 7 8 9 10 11 fun main () { runBlocking { println("runBlocking start" ) coroutineScope { println("...start" ) delay(3000 ) println("...end" ) } println("runBlocking end" ) } }
执行结果: runBlocking start …start …end runBlocking end
3.挂起函数 前面我们看到的delay是一个挂起函数,如果我们需要自定义一个挂起函数(抽离代码),那么我们需要使用suspend,但是suspend只是标记了它是一个挂起函数,但是仅仅如此,在这个函数内,并无法获得协程作用域,比如就无法使用launch,
那么我们可以使用coroutineScope,coroutineScope也是一个挂起函数,并且它会继承外部协程作用域
1 2 3 4 5 private suspend fun printDot() = coroutineScope { println("suspend...start") delay(3000) println("suspend...end") }
调用挂起函数,printDot只能在协程中被调用,并且会阻塞外部协程
1 2 3 4 5 6 7 fun main() { runBlocking { println("runBlocking start") printDot() println("runBlocking end") } }
总结runBlocking和coroutineScope的区别?
借用郭霖的总结:coroutineScope函数只会阻塞当前协程,既不影响其他协程,也不影响任何线程,因此是不
会造成任何性能上的问题的。而runBlocking函数由于会挂起外部线程,如果你恰好又在主线
程中当中调用它的话,那么就有可能会导致界面卡死的情况,所以不太推荐在实际项目中使
用。
4.取消协程 1 2 3 4 5 6 val job = Job()val scope = CoroutineScope(job)scope.launch { } job.cancel()
当调用cancel后,会取消该协程作用域中的所有子协程
5.async await 使用async的await方法,可以获取协程执行的结果,当调用await方法时,如果async子协程内部的代码没执行完,会阻塞外部协程,本代码中,当async1调用await获取结果时,内部代码未执行完,所以async2的代码会被阻塞,所以async1和async2,它俩是一个串行执行,都执行完后,执行最后的打印语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 runBlocking { val start = System.currentTimeMillis() val async1 = async { delay(1000 ) 5 + 5 }.await() val async2 = async { delay(1000 ) 5 + 5 }.await() println("$async1 , $async2 " ) val end = System.currentTimeMillis() println("time:${end - start} " ) }
优化代码:串行改为并行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 runBlocking { val start = System.currentTimeMillis() val async1 = async { delay(1000 ) 5 + 5 }.await() val async2 = async { delay(1000 ) 5 + 5 }.await() println("$async1 , $async2 " ) val end = System.currentTimeMillis() println("time:${end - start} " ) }
6.协程简化回调 使用suspendCoroutine
来简化回调,suspendCoroutine函数必须在协程作用域或挂起函数中才能调用,它会将当前协程立即挂起,去执行个Lambda的代码,会传入个Continuation参数,调用它的resume()方法或resumeWithException()可以让协程恢复执行。
1 2 3 4 5 6 7 8 9 10 11 12 suspend fun request (address: String ) : String { return suspendCoroutine { continuation -> HttpUtil.sendHttpRequest(address, object : HttpCallbackListener { override fun onFinish (response: String ) { continuation.resume(response) } override fun onError (e: Exception ) { continuation.resumeWithException(e) } }) } }
调用request方法:
1 2 3 4 5 6 7 8 suspend fun getBaiduResponse () { try { val response = request("https://www.baidu.com/" ) } catch (e: Exception) { } }
协程优化okhttp网络请求回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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) else continuation.resumeWithException( RuntimeException("response body is null" )) } override fun onFailure (call: Call <T >, t: Throwable ) { continuation.resumeWithException(t) } }) } }
这里的await是对Call的一个扩展方法,调用时就简单多了
1 2 3 4 5 6 7 8 suspend fun getAppData () { try { val appList = ServiceCreator.create<AppService>().getAppData().await() } catch (e: Exception) { } }
2.2实战案例 1.倒计时进入首页 1 2 3 4 5 6 7 8 9 10 private val job by lazy { Job() }private val splashDuration = 3 * 1000L CoroutineScope(job).launch { delay(splashDuration) MainActivity.start(this @SplashActivity ) finish() }
别忘记在页面销毁时,取消job
1 2 3 4 override fun onDestroy () { job.cancel() super .onDestroy() }
2. 每隔一秒显示当前时间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private val job: Job by lazy { Job() }private val sdf by lazy { SimpleDateFormat("HH:mm:ss" ) }CoroutineScope(job).launch { while (true ) { val time = sdf.format(Date()) withContext(Dispatchers.Main) { mTvContent.text = "$time " } delay(1000 ) } } override fun onDestroy () { job.cancel() super .onDestroy() }
2.3综合案例:网络请求 本小节,我们将使用ViewModel,LiveData,kotlin协程,Retrofit演示一个网络请求
第一步:Retrofit单例模式封装
1 2 3 4 5 6 7 8 9 10 11 12 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 (clazz: Class <T >) : T = retrofit.create(clazz) inline fun <reified T> create () : T = create(T::class .java) }
第二步:创建网络API接口
1 2 3 4 5 6 7 interface PlaceService { @GET("v2/place?token=${SunnyWeatherApplication.TOKEN} &lang=zh_CN" ) suspend fun searchPlaces2 (@Query("query" ) query:String ) :PlaceResponse }
注意
:这里使用suspend标识其是一个挂起函数,返回值是实体类
第三步:网络层,生成动态代理对象,执行网络请求,获取网络请求结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 object SunnyWeatherNetwork { private val placeService = ServiceCreator.create<PlaceService>() private val weatherService = ServiceCreator.create<WeatherService>() suspend fun searchPlaces (query: String ) = placeService.searchPlaces(query).await() suspend fun searchPlaces2 (query: String ) = placeService.searchPlaces2(query) suspend fun getDailyWeather (lng: String , lat: String ) = weatherService.getDailyWeather(lng, lat).await() suspend fun getRealtimeWeather (lng: String , lat: String ) = weatherService.getRealtimeWeather(lng, lat).await() private 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) } else { continuation.resumeWithException(RuntimeException("response body is null" )) } } override fun onFailure (call: Call <T >, t: Throwable ) { continuation.resumeWithException(t) } }) } } }
第四步:仓库层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 object AppRepository { fun searchPlaces2 (query: String ) = fire(Dispatchers.IO) { Log.d("qwIvan" , "searchPlaces2: $query " ) val placeResponse = SunnyWeatherNetwork.searchPlaces2(query) if (placeResponse.status == "ok" ) { val places = placeResponse.places Result.success(places) } else { Result.failure(RuntimeException("response status is ${placeResponse.status} " )) } } fun refreshWeather (lng:String ,lat:String ) = fire(Dispatchers.IO){ coroutineScope { val deferredRealtime = async { SunnyWeatherNetwork.getRealtimeWeather(lng,lat) } val deferredDaily = async { SunnyWeatherNetwork.getDailyWeather(lng,lat) } val realtimeResponse = deferredRealtime.await() val dailyResponse = deferredDaily.await() if (realtimeResponse.status == "ok" && dailyResponse.status == "ok" ){ val weather = Weather(realtimeResponse.result.realtime,dailyResponse.result.daily) Result.success(weather) }else { Result.failure( RuntimeException( "realtime response status is ${realtimeResponse.status} " + "daily response status is ${dailyResponse.status} " )) } } } private fun <T> fire (context: CoroutineContext , block: suspend () -> Result <T >) = liveData(context) { var result = try { block() } catch (e: Exception) { Result.failure<T>(e) } emit(result) } }
第五步:ViewModel层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class PlaceViewModel :ViewModel () { private val searchLiveData = MutableLiveData<String>() val placeLiveData = Transformations.switchMap(searchLiveData){ it -> AppRepository.searchPlaces2(it) } val placeList = arrayListOf<Place>() fun searchPlace (query:String ) { searchLiveData.value = query } fun savePlace (place: Place ) { AppRepository.savePlace(place) } fun getSavedPlace () :Place = AppRepository.getSavedPlace() fun isPlaceSaved () = AppRepository.isPlaceSaved() }
第六步:展示界面UI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 viewModel.placeLiveData.observe(viewLifecycleOwner, Observer { val places = it.getOrNull() println("查询结束:" + (places?.size ?: 0 )) if (places != null ) { recyclerView.isVisible = true bgImageView.isVisible = false viewModel.placeList.apply { clear() addAll(places) } adapter.notifyDataSetChanged() } else { Toast.makeText(activity, "未能查询到任何地点" , Toast.LENGTH_SHORT).show() it.exceptionOrNull()?.printStackTrace() } })
第三章 其他热门库 3.1 coil
kotlin协程图片加载库,
github地址:https://github.com/*coil*-kt/*coil*/