【问题标题】:Observable field usage with two way binding (Removing property change listener)使用两种方式绑定的可观察字段用法(删除属性更改侦听器)
【发布时间】:2020-08-31 06:09:04
【问题描述】:

我在 ViewModel 中使用 Observable 字段。当 Observable 字段更新时,我会更改 UI 可见性。

这可以通过

来完成
object : Observable.OnPropertyChangedCallback() {
                override fun onPropertyChanged(sender: Observable?, propertyId: Int) {

                }

            }

删除 ondestroy 中的回调。

@{}这样使用双向绑定直接映射成XML。

现在的问题是,如果使用双向绑定,我该如何移除监听器?我知道 Livedata 可以替代它。

【问题讨论】:

    标签: android android-databinding android-architecture-components


    【解决方案1】:

    我不确定您说的是哪种内存泄漏。

    Java中的内存泄漏发生在一个对象存在很长时间并且它包含对不应再使用的其他对象的强引用,因此应该被GC销毁,但由于该强引用而仍然存在。

    在 Android 中,内存泄漏通常发生在某些持久对象存储对 Activity(或在某些情况下为 Fragment)的强引用时。 android 中的所有其他内存泄漏都没有那么大的影响(位图除外 - 但这是一个完全不同的话题)

    所以让我们返回到使用ObservableField 的数据绑定及其在ViewModel 内的回调或通过@={} 的双向数据绑定。在大多数情况下在这两种情况下都不会发生内存泄漏。要了解原因 - 您需要了解 Android 框架如何与 UI 一起运行,并且现在还需要了解视图数据绑定是否有效。那么当您通过ObservableField 和回调或@={} 创建回调时会发生什么

    当你写作时

        val someField: ObservabaleField = ObservableFiled<String>("someText")
        val someCallback = object : Observable.OnPropertyChangedCallback() {
                    override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
    
                    }
    
        }
        someField.addOnPropertyChangedCallback(someCallback)
    
        // and in the layout
        android:text="@={viewModel.someField}"
    

    在生成的文件中它会做这样的事情

        androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, viewModelSomeFieldGet);
    
        @Override
        protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
            switch (localFieldId) {
                case 0 :
                    //...
                    return onChangeViewModelSomeOtherStuff(object, fieldId);
                case 1 :
                    return onChangeViewModelSomeField((androidx.databinding.ObservableField<java.lang.String>) object, fieldId);
            }
            return false;
        }
    
    

    正如您所见,没有context 也没有activityfragment 泄漏,因为在任何地方都没有对它们的强引用。在您的ViewModel 中也没有提到contextactivityfragment(我希望!)。此外,它以相反的方式工作 - ui 在绑定实现中存储指向ViewModel 的链接,因此我们的ViewModel 可能会泄漏。这是后置情况,因为 Activity 或 Fragment 的 UI 通常会连同其 ActivityBindingImplFragmentBindingImpl 绑定一起被破坏,但是......

    确保您有手动清除引用的方法:在 Activity'onDestroy 或 Fragment'onDestroyView 调用中

    clearFindViewByIdCache()
    binding.unbind()
    binding = null
    // if you store view link in your viewModel(which is bad and may cause leaks) this is the perfect place to nullify it
    viewModel.view = null
    

    还可以使用AutoClearedValue处理绑定自动清除

    实际用法可能看起来像(如果你不关心它的类型)

    override var binding: ViewDataBinding? by autoCleared()// that is all - no need of onDestroy or onDestroyView
    

    编辑

    如果您想手动取消注册 ObservableFields 中的所有回调,您可以这样做。最好的方法是使用ViewModelonCleared() 方法。你应该打电话给observableField.removeOnPropertyChangedCallback(callback) 来处理这些东西。考虑到ObservableField 和上面的回调声明,它看起来像这样:

    class MyViewModel: ViewModel{
       //ObservableField and callback declarations
       ...
       override void onCleared(){
           someField.removeOnPropertyChangedCallback(someCallback)
       }
    
    }
    

    编辑结束

    我刚才描述的这一切确保在使用ObservableFields 和查看数据绑定时不会出现内存泄漏。这一切都与正确的实施有关。当然你可以用泄漏来实现它,但你可以不用泄漏来实现它。

    如果仍有不清楚的地方发表评论 - 我会尝试扩展答案。

    更多关于 Fragment 依赖泄漏的信息here

    希望对你有帮助。

    【讨论】:

    • 感谢您的信息。实际上我的问题似乎是正确的,并没有真正关注内存泄漏(将编辑问题)。我问的是 onPropertyChange 的监听器,它被加起来了 .on destroy 我们已经删除了它。但是在两种方式绑定的情况下,我们使用 @{ } 。我希望 impl 类做一些事情,比如添加属性更改侦听器。如何删除监听器。是否会删除调用 autoCleared()/?
    • 您不必删除它 - 它不会导致泄漏。但如果你愿意,你可以。在ViewModelonCleared() 方法中进行此操作的最佳位置。致电someField.removeOnPropertyChangedCallback(someCallback)。我会把这个添加到答案中
    • autoCleared,在这种情况下,只处理视图绑定。
    • 我在下面添加了同样的问题。
    • 您可以使用 LiveData 进行更多控制。关于 ObservableFields,我给你的解决方案几乎是你能做的。关于多个请求 - 这不是删除侦听器的问题,而不是一般的架构问题。
    【解决方案2】:

    removeOnPropertyChangedCallback 永远不会被调用?

    这实际上是 最终并定期被数据绑定框架调用 清理已收集的听众。然而,很可能 您的 ViewModel 仍然会注册一些回调 销毁,这没关系。数据绑定框架使用弱 观察者的参考,并不要求他们是 在 ViewModel 被销毁之前取消注册。这不会导致任何 内存泄漏。

    话虽如此,如果您快速旋转手机,一次旋转几次 行,同时在同一屏幕上。你会注意到 ObservableViewModel.addOnPropertyChangedCallBack 被调用了几个 次,如果您查看源代码 android.databinding.ViewDataBinding,你会看到观察者计数 每次都会上升。

    这就是定期删除的用武之地。如果您长时间使用该应用 足够了,旋转几次,并设置一个断点 ObservableViewModel.removeOnPropertyChangedCallback。你会看到 它会定期调用以清理旧的观察者,如果你查找 调用堆栈,您可以找到有关其来源的更多详细信息, 它是如何触发的,等等。

    您可以通过以下方式跟踪更多信息:https://caster.io/lessons/android-mvvm-pattern-with-architecture-component-viewmodels

    希望对你有帮助!!

    【讨论】:

    • 那有什么办法呢?手动删除??因为如果注册的监听器被调用 2 或 3 次。可能是我发送 2 或 3 个网络请求吧?
    • 哦..根据上面的解释,这意味着数据绑定将采用弱引用并在您的活动/片段调用被破坏时自动删除/销毁。关于手动删除,我认为您可以创建一个BindingAdapter,并在您想要发布的任何时候设置null。在网络通话中,为避免多次通话,您可以使用RxJava/RxAndroid并处理Disposable。我认为这是一个很好的做法。
    【解决方案3】:

    您可以使用 ViewModel 类中的 removeOnPropertyChangedCallback 函数来做到这一点。以下是您的 ViewModel 的外观:

    abstract class ObservableViewModel(app: Application): AndroidViewModel(app), Observable {
    
    @delegate:Transient
    private val mCallBacks: PropertyChangeRegistry by lazy { PropertyChangeRegistry() }
    
    override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
        mCallBacks.add(callback)
    }
    
    override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
        mCallBacks.remove(callback)
    }
    
    fun notifyChange() {
        mCallBacks.notifyChange(this, 0)
    }
    
    fun notifyChange(viewId:Int){
        mCallBacks.notifyChange(this, viewId)
    }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-12-23
      • 2018-04-06
      • 2014-12-31
      • 1970-01-01
      • 2017-08-09
      • 1970-01-01
      • 2015-05-13
      • 1970-01-01
      • 2021-11-22
      相关资源
      最近更新 更多