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 = 0handler.postDelayed(timerRunnable, 1000)}fun cancelTimer() {handler.removeCallbacks(timerRunnable)}companion object {private lateinit var sInstance: TimerGlobalLiveDataprivate var count = 0@MainThreadfun 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,最右下角是一个按钮,点击按钮,调用结束行程的网络请求。
以前的做法是自定义View通过callback回调的方式将点击事件传递给Activity,在Activity中请求结束行程的接口,然后Activity中收到回调后,拿着自定义View的引用进行相应的ui展示
示例伪代码
// TestActivity
class TestActivity{private lateinit var endTripView : EndTripViewprivate 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? = nullfun 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 LocationManagerprivate var gpsLocationListener: LocationListener = object : LocationListener {override fun onLocationChanged(location: Location) {postValue(location)}override fun onProviderDisabled(provider: String) = Unitoverride fun onProviderEnabled(provider: String) = Unitoverride 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.instanceprivate 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 ConnectivityManagerval activeNetwork = manager.activeNetworkInfoget().postValue(activeNetwork)}}companion object {private lateinit var sInstance: NetworkWatchLiveData@MainThreadfun get(): NetworkWatchLiveData {sInstance = if (::sInstance.isInitialized) sInstance else NetworkWatchLiveData()return sInstance}}
}
复制代码
七、LiveEventBus
场景
封装LiveData替换EventBus,实现消息总线,可以减少引入第三方库。
项目地址
github.com/JeremyLiao/…
实现原理
Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus
八、LiveData数据倒灌解决
发生原因
什么是LiveData数据倒灌?为什么会导致数据倒灌?
附上我以前写的一篇文章
LiveData奇思妙用总结相关推荐
- c# 扩展方法奇思妙用变态篇四:string 的翻身革命
string是各种编程语言中最基础的数据类型,长期以来受尽其它类的压迫,经常被肢解(Substring.Split).蹂躏(Join)... 而现在string要"翻身闹革命"了, ...
- c#扩展方法奇思妙用高级篇七:“树”通用遍历器
我的上一篇随笔<c#扩展方法奇思妙用高级篇六:WinForm 控件选择器>中给出了一个WinForm的选择器,其实质就是一个"树"的遍历器,但这个遍历局限于WinFor ...
- c#扩展方法奇思妙用变态篇四:string 的翻身革命
string是各种编程语言中最基础的数据类型,长期以来受尽其它类的压迫,经常被肢解(Substring.Split).蹂躏(Join)... 而现在string要"翻身闹革命"了, ...
- c# 扩展方法奇思妙用基础篇八:Distinct 扩展(转载)
转载地址:http://www.cnblogs.com/ldp615/archive/2011/08/01/distinct-entension.html 刚看了篇文章 <Linq的Distin ...
- c# 扩展方法奇思妙用高级篇一:改进 Scottgu 的 In 扩展
先看下ScottGu对In的扩展: 调用示例1: 调用示例2: 原文地址:New "Orcas" Language Feature: Extension Methods 很多介绍扩 ...
- c#扩展方法奇思妙用高级篇四:对扩展进行分组管理
从系列文章开篇到现在,已经实现的很多扩展了,但过多的扩展会给我们带来很多麻烦,试看下图: 面对这么多"泛滥"的扩展,很多人都会感到很别扭,的确有种"喧宾夺主"的 ...
- c#扩展方法奇思妙用性能篇一:扩展方法性能初测
最近写了几篇<c#扩展方法奇思妙用>的文章,一直只是讨论如何扩展.如何使用的问题,几乎没有涉及效率方面. 而大家的回复好多都在问效率如何.性能怎样,也引起了我对效率的关注,今天将初步测试的 ...
- c# 扩展方法奇思妙用变态篇一:由 Fibonacci 数列引出 “委托扩展” 及 “递推递归委托”...
先回顾一个数列的概念:按一定次序排列的一列 数 称为数列...(请参见百度百科:数列) 几个简单的数列: 1, 1, 1, 1, 1, 1, 1... //数 ...
- c#扩展方法奇思妙用高级篇八:Type类扩展
Type 类提供了大量的属性和方法,但在一些基础性开发工作中,Type类功能还有些欠缺,尤其上在处理泛型类型时,如可空类型和泛型集合类型.下面的类就针对这些地方进行扩展. 1 public ...
最新文章
- 【腾讯bugly干货分享】微信Android热补丁实践演进之路
- OpenSwitch操作系统成为Linux基金会官方项目
- ★自制社交网站等级称号
- jq在html中添加dom元素,使用jQuery添加DOM元素的最佳方法
- java byte 梳理
- 详解自然语言处理5大语义分析技术及14类应用(建议收藏)
- (31)FPGA面试题系统最高速度计算方法
- HDOJ 1036 Average is not Fast Enough!
- cvBoundingRect的用法(转)
- 模型参数太多怎么办?用谷歌高效训练库GPipe啊
- php文字游戏寻仙记,【寻仙记H5】Win服务端+小白架设教程+文字游戏+小仙亲测
- python之世界地图绘制_Python用 matplotlib 工具包来绘制世界地图
- python图片背景透明
- 【PROTEUS】使用PROTEUS与电脑串口调试助手进行通讯
- Java多线程系列--【JUC锁05】-非公平锁
- 使用canvas压缩图片
- S700K提速道岔电路故障的处理方法【铁路信号技术专栏】—转自微信公众号高速铁路信号技术交流
- C语言——关键字,define定义宏,指针,结构体
- 对数函数定义域和值域_对数函数定义域和值域 [对数函数的定义域和值域对数函数的定义域和值域]...
- 一点点的社会经济学和组织领导学