注册

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,实现消息总线,可以减少引入第三方库。


项目地址


github.com/JeremyLiao/…


实现原理


Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus


八、LiveData数据倒灌解决


发生原因


什么是LiveData数据倒灌?为什么会导致数据倒灌?


附上我以前写的一篇文章?


Activity销毁重建导致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大神的:


github.com/KunMinX/Jet…


十、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
}
}

代码地址


github.com/ldlywt/Fast…


鸣谢


本文是一片总结文,会长期不定时更新。


如果有其他的LiveData奇思妙用,请留言,非常感谢。


最后,感谢网上各路大神的无私奉献。


0 个评论

要回复文章请先登录注册