美文网首页
LiveData奇思妙用总结

LiveData奇思妙用总结

作者: 要早点睡 | 来源:发表于2021-12-07 14:05 被阅读0次

    前言

    • 本文不涉及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/Andr…

    鸣谢

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

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

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

    相关视频Android中高级进阶之MVVM与JetPack: LiveData&lifecycle/databinding/页面开发项目实战

    原文链接: https://juejin.cn/post/6996862698000498725

    相关文章

      网友评论

          本文标题:LiveData奇思妙用总结

          本文链接:https://www.haomeiwen.com/subject/getaxrtx.html