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() {

    private lateinit var takePhotoLauncher: ActivityResultLauncher

    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() {

    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()

// Fragment中
private val mViewModel by activityViewModels()
   
// 自定义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

img

以前的做法是自定义View通过callback回调的方式将点击事件传递给Activity,在Activity中请求结束行程的接口,然后Activity中收到回调后,拿着自定义View的引用进行相应的ui展示

示例伪代码

// TestActivity
class TestActivity{
    private lateinit var endTripView : EndTripView
    private val endTripViewModel by viewModels()
    
    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() {

    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() {
    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 getApplicationScopeViewModel(@NonNull Class 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);
}

十、LiveData的转换

场景

获取用户信息的接口返回的是一个User对象,但是页面上只需要显示用户的名字UserName,这样就没必要把整个User对象抛出去。

private val userLiveData: LiveData = UserLiveData()
val userName: LiveData = 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>()
// api网络请求的结果
private val apiLiveData = StateLiveData>()
// 将上面两个结果进行合并,只有有一个更新,mediatorLiveDataLiveData就会收到
val mediatorLiveDataLiveData = MediatorLiveData>>().apply {
    this.addSource(apiLiveData) {
        this.value = it
    }
    this.addSource(dbLiveData) {
        this.value = it
    }
}

文章来源于互联网:LiveData奇思妙用总结

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/21166,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?