web-dev-qa-db-ja.com

リークカナリア、RecyclerviewリークmAdapter

Leak Canaryを使用してアプリ内のリークを検出する方法を学ぶべき時がきたと判断し、いつものように、それをプロジェクトに実装してツールの使い方を本当に理解しようとしました。それを実装するのは十分に簡単でした。困難な部分は、ツールが私に投げ返しているものを読むことでした。 (新しいデータをロードしない場合でも)上下にスクロールすると、メモリマネージャーにメモリを蓄積するように見えるscrollviewがあるため、リークを追跡するのに適した候補オブジェクトであると思いました。これが結果です。

enter image description here

アプリケーションではなくv7.widget.RecyclerViewがアダプターをリークしているようです。しかし、それは正しくありません...正しいですか?

アダプターとそれを使用するクラスのコードは次のとおりです。 https://Gist.github.com/feresr/a53c7b68145d6414c40ec70b3b842f1e

完全に異なるアプリケーションで2年後に再浮上したため、この質問に対する報奨金を開始しました

34
FRR

アダプタがRecyclerViewよりも長く存続する場合は、onDestroyViewのアダプタ参照をクリアする必要があります。

@Override
public void onDestroyView() {
    recyclerView.setAdapter(null);
    super.onDestroyView();
}

それ以外の場合、アダプタは、すでにメモリ不足になっているはずのRecyclerViewへの参照を保持します。

画面が遷移アニメーションに関与している場合は、実際にこれをさらに一歩進めて、ビューがdetachedになったときにのみアダプターをクリアする必要があります。

@Override
public void onDestroyView() {
    recyclerView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
        @Override
        public void onViewAttachedToWindow(View v) {
            // no-op
        }

        @Override
        public void onViewDetachedFromWindow(View v) {
            recyclerView.setAdapter(null);
        }
    });
    super.onDestroyView();
}
32
Brian Yencho

RecyclerViewをオーバーライドすることでこれを修正できました。これは、RecyclerViewが自身をAdapterDataObservableから登録解除しないために発生します。

@Override protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (getAdapter() != null) {
        setAdapter(null);
    }
}
12
Bolein95

まず、私は このファイル を参照しています。

アプリケーションではなくv7.widget.RecyclerViewがアダプターをリークしているようです。しかし、それは正しくありません...正しいですか?

実際には、あなたのRecyclerViewをリークしているアダプタです(そして、トレースグラフとLeakCanaryアクティビティのタイトルによってかなり明確になります)。ただし、それが「親」のRecyclerViewであるか、HourlyViewHolderでネストされたものであるか、またはその両方であるかはわかりません。犯人はあなたのViewHoldersだと思います。それらを非静的内部クラスにすることで、それらを囲むアダプタークラスへの参照を明示的に提供します。これは、ホルダー内のすべてのitemViewの親がRecyclerView自体。

この問題を修正するための最初の提案は、ViewHoldersとAdapterをstatic内部クラスにすることで分離することです。そうすることで、それらはアダプターへの参照を保持しないため、contextフィールドはそれらにアクセスできなくなります。また、コンテキスト参照は渡さずに保存する必要があるため、これも良いことです。メモリリーク)。文字列を取得するためだけにコンテキストが必要な場合は、アダプターコンストラクターなどの別の場所で実行しますが、メンバーとしてコンテキストを保存しないでください。最後に、DayForecastAdapterも危険に思われます。同じインスタンスを1つすべてのHourlyViewHolderに渡します。これはバグのようです。

デザインを修正してこれらのクラスを分離することで、このメモリリークを解消できると思います

5
maciekjanusz

画像を開いて実際のリークを確認することはできませんが、RecyclerViewFragmentのローカル変数を定義し、フラグメントのretainInstanceStatetrueを設定すると、ローテーションでリークが発生する可能性があります。

FragmentretainInstanceを使用する場合、onDestroyView内のすべてのUI参照をクリアする必要があります

@Override
public void onDestroyView() {
     yourRecyclerView = null;
     super.onDestroyView();
}

ここでは、このリンクから詳細情報を見つけることができます: Iとメモリリークのある保持フラグメント

0
savepopulation