web-dev-qa-db-ja.com

双方向バインディングによる監視可能なフィールドの使用法(プロパティ変更リスナーの削除)

ViewModelでObservableフィールドを使用しています。 Observableフィールドが更新されたら、UIの可視性を変更します。

これは、

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

                }

            }

ondestroyでコールバックを削除します。

または

双方向バインディングを使用して、@{}のようなXMLに直接マッピングします。

ここで問題は、双方向バインディングを使用している場合にリスナーを削除するにはどうすればよいですか?私はLivedataがこれの代わりになることを知っています。

4
Rockin

どのメモリリークを話しているのかわかりません。

Javaでのメモリリークは、1つのオブジェクトが長期間存在し、それがもう使用されるべきではない他のオブジェクトへの強い参照を含んでいる場合に発生します。したがって、GCによって破棄される必要がありますが、そのため引き続き存在します強い参照。

Android具体的には、メモリリークは通常、長時間持続するオブジェクトがアクティビティ(または場合によってはフラグメント)への強い参照を格納する場合に発生します。他のすべてのメモリリークはAndroid =それほどインパクトはありません(ビットマップを含むものを除いて-しかし、それは完全に異なるトピックです)

それでは、ObservableFieldViewModel内のコールバック、または_@={}_による双方向データバインディングを使用したデータバインディングに戻りましょう。ほとんどの場合、両方の場合でメモリリークは発生しません。理由を理解するには、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;
    }

_

ご覧のように、contextactivityfragmentもどこにも格納されていないため、リークはありません。 contextにもactivityfragmentまたはViewModelへの参照はありません(私は希望します!)。さらに、それは逆に機能します-uiは、バインディング実装のViewModelへのリンクを格納するため、ViewModelがリークする可能性があります。アクティビティまたはフラグメントのUIは通常、ActivityBindingImplまたはFragmentBindingImplバインディングとともに破棄されるため、これはリアケースですが...

参照をクリアする手動の方法があることを確認するには: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を使用してデータバインディングを表示するときにメモリリークが発生しないことが保証されます。それはすべて正しい実装についてです。もちろんリーク付きで実装することもできますが、リークなしで実装することもできます。

それでも不明な点がある場合はコメントしてください-答えを広げようと思います。

フラグメント依存のリークについてもう少し詳しく ここ

それが役に立てば幸い。

1
Pavlo Ostasha

これは、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)
}
}
1
SONU SOURAV

removeOnPropertyChangedCallbackが呼び出されることはありませんか?

これは実際には、最終的に、定期的に、データバインディングフレームワークによって呼び出され、収集されたリスナーをクリーンアップします。ただし、ViewModelが破棄された場合でも、ViewModelにはいくつかのコールバックが登録されている可能性が高く、これは問題ありません。データバインディングフレームワークはオブザーバーに弱参照を使用し、ViewModelが破棄される前にそれらを登録解除する必要はありません。これによってメモリリークが発生することはありません。

そうは言っても、同じ画面上で、電話をすばやく数回続けて回転させると、 ObservableViewModel.addOnPropertyChangedCallBackが数回呼び出され、Android.databinding.ViewDataBindingのソース内を見ると、オブザーバーカウントが毎回増加していることがわかります。

ここで定期的な削除が行われます。アプリを十分に長く使用する場合は、数回ローテーションして、ObservableViewModel.removeOnPropertyChangedCallbackにブレークポイントを設定します。古いオブザーバーをクリーンアップするために定期的に呼び出されることがわかります。コールスタックを調べると、それがどこから発生したのか、どのようにトリガーされたのかなどの詳細がわかります。

詳細は https://caster.io/lessons/Android-mvvm-pattern-with-architecture-component-viewmodels で追跡できます。

これがあなたを助けることを願っています!!

1
leo