阅读 1067

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奇思妙用,请留言,非常感谢。

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

文章分类
Android