前言

  • 本文不涉及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奇思妙用总结相关推荐

  1. c# 扩展方法奇思妙用变态篇四:string 的翻身革命

    string是各种编程语言中最基础的数据类型,长期以来受尽其它类的压迫,经常被肢解(Substring.Split).蹂躏(Join)... 而现在string要"翻身闹革命"了, ...

  2. c#扩展方法奇思妙用高级篇七:“树”通用遍历器

    我的上一篇随笔<c#扩展方法奇思妙用高级篇六:WinForm 控件选择器>中给出了一个WinForm的选择器,其实质就是一个"树"的遍历器,但这个遍历局限于WinFor ...

  3. c#扩展方法奇思妙用变态篇四:string 的翻身革命

    string是各种编程语言中最基础的数据类型,长期以来受尽其它类的压迫,经常被肢解(Substring.Split).蹂躏(Join)... 而现在string要"翻身闹革命"了, ...

  4. c# 扩展方法奇思妙用基础篇八:Distinct 扩展(转载)

    转载地址:http://www.cnblogs.com/ldp615/archive/2011/08/01/distinct-entension.html 刚看了篇文章 <Linq的Distin ...

  5. c# 扩展方法奇思妙用高级篇一:改进 Scottgu 的 In 扩展

    先看下ScottGu对In的扩展: 调用示例1: 调用示例2: 原文地址:New "Orcas" Language Feature: Extension Methods 很多介绍扩 ...

  6. c#扩展方法奇思妙用高级篇四:对扩展进行分组管理

    从系列文章开篇到现在,已经实现的很多扩展了,但过多的扩展会给我们带来很多麻烦,试看下图: 面对这么多"泛滥"的扩展,很多人都会感到很别扭,的确有种"喧宾夺主"的 ...

  7. c#扩展方法奇思妙用性能篇一:扩展方法性能初测

    最近写了几篇<c#扩展方法奇思妙用>的文章,一直只是讨论如何扩展.如何使用的问题,几乎没有涉及效率方面. 而大家的回复好多都在问效率如何.性能怎样,也引起了我对效率的关注,今天将初步测试的 ...

  8. c# 扩展方法奇思妙用变态篇一:由 Fibonacci 数列引出 “委托扩展” 及 “递推递归委托”...

    先回顾一个数列的概念:按一定次序排列的一列 数 称为数列...(请参见百度百科:数列) 几个简单的数列:       1, 1, 1, 1, 1, 1, 1...                //数 ...

  9. c#扩展方法奇思妙用高级篇八:Type类扩展

    Type 类提供了大量的属性和方法,但在一些基础性开发工作中,Type类功能还有些欠缺,尤其上在处理泛型类型时,如可空类型和泛型集合类型.下面的类就针对这些地方进行扩展.  1     public  ...

最新文章

  1. 【腾讯bugly干货分享】微信Android热补丁实践演进之路
  2. OpenSwitch操作系统成为Linux基金会官方项目
  3. ★自制社交网站等级称号
  4. jq在html中添加dom元素,使用jQuery添加DOM元素的最佳方法
  5. java byte 梳理
  6. 详解自然语言处理5大语义分析技术及14类应用(建议收藏)
  7. (31)FPGA面试题系统最高速度计算方法
  8. HDOJ 1036 Average is not Fast Enough!
  9. cvBoundingRect的用法(转)
  10. 模型参数太多怎么办?用谷歌高效训练库GPipe啊
  11. php文字游戏寻仙记,【寻仙记H5】Win服务端+小白架设教程+文字游戏+小仙亲测
  12. python之世界地图绘制_Python用 matplotlib 工具包来绘制世界地图
  13. python图片背景透明
  14. 【PROTEUS】使用PROTEUS与电脑串口调试助手进行通讯
  15. Java多线程系列--【JUC锁05】-非公平锁
  16. 使用canvas压缩图片
  17. S700K提速道岔电路故障的处理方法【铁路信号技术专栏】—转自微信公众号高速铁路信号技术交流
  18. C语言——关键字,define定义宏,指针,结构体
  19. 对数函数定义域和值域_对数函数定义域和值域 [对数函数的定义域和值域对数函数的定义域和值域]...
  20. 一点点的社会经济学和组织领导学

热门文章

  1. word2vec论文学习
  2. MYSQL查看某个数据库下所有表索引的数据量及占用空间大小
  3. Win10正式专业版系统的激活
  4. 【数据结构 C语言版】第六篇 栈、队列经典必刷面试考研题
  5. 关于视频--源自伊甸园论坛
  6. 使用testdisk恢复数据盘文件
  7. Linux Shell命令实现端口转发(端口映射)
  8. 使用 CSS 实现垂直居中的5种方法
  9. CCF 金融信息负面及主体判定
  10. rgw bucket 防盗链