LiveData奇思妙用总结
前言
-
本文不涉及LiveData的基本使用方式。
-
阅读本文之前,强推推荐先看官方文档 LiveData的概览,官方文档写的非常好,并且很详细。
-
本文是一篇总结文,自己的一些使用结总结以及网上的学习归纳。
一、LiveData结合ActivityResult
对 Activity Results Api不怎么了解的,可以先看下官方文档:
developer.android.com/training/ba…
1.1 调用系统相机
场景
调用系统相机,获取拍照后返回的照片
示例代码
// MainActivity.kt
private var takePhotoLiveData: TakePhotoLiveData = TakePhotoLiveData(activityResultRegistry, "key")
// 点击拍照按钮
mBinding.btTakePhoto.setOnClickListener {
takePhotoLiveData.takePhoto()
}
// 拍照返回的照片
takePhotoLiveData.observe(this) { bitmap ->
mBinding.imageView.setImageBitmap(bitmap)
}
几行代码搞定调用系统相机并且返回拍照后的图片。
封装示例
class TakePhotoLiveData(private val registry: ActivityResultRegistry, private val key: String) :
LiveData<Bitmap>() {
private lateinit var takePhotoLauncher: ActivityResultLauncher<Void?>
override fun onActive() {
takePhotoLauncher = registry.register(key, ActivityResultContracts.TakePicturePreview()) { result ->
value = result
}
}
override fun onInactive() = takePhotoLauncher.unregister()
fun takePhoto() = takePhotoLauncher.launch(null)
}
同理,请求权限也可以类似封装:
1.2 请求权限
场景
请求系统权限,例如GPS定位
示例代码
private var requestPermissionLiveData = RequestPermissionLiveData(activityResultRegistry, "key")
mBinding.btRequestPermission.setOnClickListener {
requestPermissionLiveData.requestPermission(Manifest.permission.RECORD_AUDIO)
}
requestPermissionLiveData.observe(this) { isGranted ->
toast("权限RECORD_AUDIO请求结果 $isGranted")
}
封装的代码跟上面类似,就不列出来了。
二、LiveData实现全局定时器
场景
一个全局计数器,Activity销毁时,计时器停止,不会导致内存泄露,Activity激活时,计时器开始,自动获取最新的计时。
示例代码
// 开启计时器
TimerGlobalLiveData.get().startTimer()
// 停止计时器
TimerGlobalLiveData.get().cancelTimer()
// 全局监听
TimerGlobalLiveData.get().observe(this) {
Log.i(TAG, "GlobalTimer value: == $it")
}
封装示例
class TimerGlobalLiveData : LiveData<Int>() {
private val handler: Handler = Handler(Looper.getMainLooper())
private val timerRunnable = object : Runnable {
override fun run() {
postValue(count++)
handler.postDelayed(this, 1000)
}
}
fun startTimer() {
count = 0
handler.postDelayed(timerRunnable, 1000)
}
fun cancelTimer() {
handler.removeCallbacks(timerRunnable)
}
companion object {
private lateinit var sInstance: TimerGlobalLiveData
private var count = 0
@MainThread
fun get(): TimerGlobalLiveData {
sInstance = if (::sInstance.isInitialized) sInstance else TimerGlobalLiveData()
return sInstance
}
}
}
三、共享数据
场景
-
多个Fragment之间共享数据
-
Activity和Fragment共享数据
-
Activity/Fragment和自定义View共享数据
获取ViewModel实例时都用宿主Activity的引用即可。
示例代码
// Activity中
private val mViewModel by viewModels<ApiViewModel>()
// Fragment中
private val mViewModel by activityViewModels<ApiViewModel>()
// 自定义View中
fun setHost(activity: BaseActivity) {
var viewModel = ViewModelProvider(activity).get(ApiViewModel::class.java)
}
四、对于自定义View
关于自定义View,提一下我常用的方式。
通过ViewMode跟LiveData把自定义view从Activity中独立开来,自成一体,减少在Activity中到处调用自定义View的引用。
场景
Activity中有一个EndTripView自定义View,这个自定义View中有很多的小view,最右下角是一个按钮,点击按钮,调用结束行程的网络请求。
img以前的做法是自定义View通过callback回调的方式将点击事件传递给Activity,在Activity中请求结束行程的接口,然后Activity中收到回调后,拿着自定义View的引用进行相应的ui展示
示例伪代码
// TestActivity
class TestActivity{
private lateinit var endTripView : EndTripView
private val endTripViewModel by viewModels<EndTripViewModel>()
fun onCreate{
endTripView = findViewById(R.id.view_end_trip)
endTripView.setListener{
onClickEndTrip(){
endTripViewModel.endTrip()
}
}
endTripViewModel.endTripLiveData.observer(this){ isSuccess ->
if(isSuccess){
endTripView.showEndTripSuccessUi()
}else {
endTripView.showEndTripFailedUi()
}
}
}
}
从上面伪代码中可以看到:
-
操作逻辑都在Activity中,Activity中存在很多自定义View的回调,并且Activity中很多地方都有EndTripView的引用。
-
自定义EndTripView需要定义很多的回调和公开很多的操作方法。
-
如果业务很复杂,那么Activity会变得很臃肿并且不好维护。
-
并且自定义EndTripView也严重依赖Activity,如果想在其他地方用,需要copy一份代码。
优化后伪代码
// Activity中代码
fun onCreate{
endTripView = findViewById(R.id.view_end_trip)
endTripView.setHost(this)
endTripViewModel.endTripLiveData.observer(this){ isSuccess ->
// 更新Activity的其它ui操作
}
}
// 自定义View中
class EndTripView : LinearLayout{
private var endTripViewModel: EndTripViewModel? = null
fun setHost(activity: BaseActivity) {
endTripViewModel = ViewModelProvider(activity).get(EndTripViewModel::class.java)
endTripViewModel.endTripLiveData.observer(this){ isSuccess ->
if(isSuccess){
showEndTripSuccessUi()
}else {
showEndTripFailedUi()
}
}
}
private fun clickEndTrip{
endTripViewModel?.endTrip()
}
private fun showEndTripSuccessUi(){...}
private fun showEndTripFailedUi(){...}
}
把自定义View相关的逻辑封装在自定义View里面,让自定义View成为一片独立的小天地,不再依赖Activity,这样Activity中的代码就非常简单了,自定义View也可以将方法都私有,去掉一些callback回调,实现高内聚。
并且由于LiveData本身的特效,跟Activity的生命周期想关联,并且点击结束行程按钮,Activity中如果注册了相应的LiveData,也可以执行相应的操作。
这样就把跟结束行程有关的自定义View的操作和ui更新放在自定义View中,Activity有关的操作在Activity中,相互隔离开来。
如果Activity中的逻辑不复杂,这种方式看不出特别的优势,但是如果Activity中逻辑复杂代码很多,这种方式的优点就很明显了。
五、LiveData实现自动注册和取消注册
利用LiveDatake可以感受Activity生命周期的优点,在Activity销毁时自动取消注册,防止内存泄露。
场景
进入Activity时请求定位,Activity销毁时移除定位,防止内存泄露
以前的方式
// 伪代码··
class MainActiviy {
override fun onStart() {
super.onStart()
LocationManager.register(this)
}
override fun onStop() {
super.onStop()
LocationManager.unRegister(this)
}
}
示例代码
val locationLiveData = LocationLiveData()
locationLiveData.observe(this){location ->
Log.i(TAG,"$location")
}
封装示例
class LocationLiveData : LiveData<Location>() {
private var mLocationManager =
BaseApp.instance.getSystemService(LOCATION_SERVICE) as LocationManager
private var gpsLocationListener: LocationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
postValue(location)
}
override fun onProviderDisabled(provider: String) = Unit
override fun onProviderEnabled(provider: String) = Unit
override fun onStatusChanged(provider: String, status: Int, extras: Bundle) = Unit
}
override fun onActive() {
mLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, minTimeMs, minDistanceM, gpsLocationListener
)
}
override fun onInactive() {
mLocationManager.removeUpdates(gpsLocationListener)
}
}
当然,使用自定义的LifecycleObserver
是一样的
class LocationObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun startLoaction() {
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun stopLocation() {
...
}
}
myLifecycleOwner.getLifecycle().addObserver(LocationObserver())
具体见官方文档:
developer.android.com/topic/libra…
查看下LiveData的源码就知道,匿名内部类里面也是继承LifecycleObserver
。
六、LiveData 结合 BroadcastReceiver
场景
可以实现BroadcastReceiver的自动注册和取消注册,减少重复代码。
封装代码
class NetworkWatchLiveData : LiveData<NetworkInfo?>() {
private val mContext = BaseApp.instance
private val mNetworkReceiver: NetworkReceiver = NetworkReceiver()
private val mIntentFilter: IntentFilter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
override fun onActive() {
mContext.registerReceiver(mNetworkReceiver, mIntentFilter)
}
override fun onInactive() = mContext.unregisterReceiver(mNetworkReceiver)
private class NetworkReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val manager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetwork = manager.activeNetworkInfo
get().postValue(activeNetwork)
}
}
companion object {
private lateinit var sInstance: NetworkWatchLiveData
@MainThread
fun get(): NetworkWatchLiveData {
sInstance = if (::sInstance.isInitialized) sInstance else NetworkWatchLiveData()
return sInstance
}
}
}
七、LiveEventBus
场景
封装LiveData替换EventBus,实现消息总线,可以减少引入第三方库。
项目地址
实现原理
Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus
八、LiveData数据倒灌解决
发生原因
什么是LiveData数据倒灌?为什么会导致数据倒灌?
附上我以前写的一篇文章😀
解决办法
九、Application级别的ViewModel
场景
ViewModel不属于Activity或者Fragment所有,属于Application级别的
示例代码
protected <T extends ViewModel> T getApplicationScopeViewModel(@NonNull Class<T> modelClass) {
if (mApplicationProvider == null) {
mApplicationProvider = new ViewModelProvider((BaseApplication) this.getApplicationContext(),
getAppFactory(this));
}
return mApplicationProvider.get(modelClass);
}
private ViewModelProvider.Factory getAppFactory(Activity activity) {
Application application = checkApplication(activity);
return ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
项目地址
具体见KunMin大神的:
十、LiveData的转换
场景
获取用户信息的接口返回的是一个User对象,但是页面上只需要显示用户的名字UserName,这样就没必要把整个User对象抛出去。
private val userLiveData: LiveData<User> = UserLiveData()
val userName: LiveData<String> = Transformations.map(userLiveData) {
user -> "${user.name} ${user.lastName}"
}
摘自官方文档:developer.android.com/topic/libra…
此外,还有一种转换方式:Transformations.switchMap()
,具体见官方文档。
十一、合并多个LiveData数据源
场景
如果界面中有可以从本地数据库或网络更新的 LiveData 对象,则可以向 MediatorLiveData 对象添加以下源:
- 与存储在数据库中的数据关联的 LiveData 对象。
- 与从网络访问的数据关联的 LiveData 对象。
来自官方文档:developer.android.com/topic/libra…
示例代码
// 数据库来的结果
private val dbLiveData = StateLiveData<List<WxArticleBean>>()
// api网络请求的结果
private val apiLiveData = StateLiveData<List<WxArticleBean>>()
// 将上面两个结果进行合并,只有有一个更新,mediatorLiveDataLiveData就会收到
val mediatorLiveDataLiveData = MediatorLiveData<ApiResponse<List<WxArticleBean>>>().apply {
this.addSource(apiLiveData) {
this.value = it
}
this.addSource(dbLiveData) {
this.value = it
}
}
代码地址
鸣谢
本文是一片总结文,会长期不定时更新。
如果有其他的LiveData奇思妙用,请留言,非常感谢。
最后,感谢网上各路大神的无私奉献。
相关视频:Android中高级进阶之MVVM与JetPack: LiveData&lifecycle/databinding/页面开发项目实战