Leak Canaryを使用してアプリ内のリークを検出する方法を学ぶべき時がきたと判断し、いつものように、それをプロジェクトに実装してツールの使い方を本当に理解しようとしました。それを実装するのは十分に簡単でした。困難な部分は、ツールが私に投げ返しているものを読むことでした。 (新しいデータをロードしない場合でも)上下にスクロールすると、メモリマネージャーにメモリを蓄積するように見えるscrollviewがあるため、リークを追跡するのに適した候補オブジェクトであると思いました。これが結果です。
アプリケーションではなくv7.widget.RecyclerViewがアダプターをリークしているようです。しかし、それは正しくありません...正しいですか?
アダプターとそれを使用するクラスのコードは次のとおりです。 https://Gist.github.com/feresr/a53c7b68145d6414c40ec70b3b842f1e
完全に異なるアプリケーションで2年後に再浮上したため、この質問に対する報奨金を開始しました
アダプタが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();
}
RecyclerViewをオーバーライドすることでこれを修正できました。これは、RecyclerViewが自身をAdapterDataObservableから登録解除しないために発生します。
@Override protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (getAdapter() != null) {
setAdapter(null);
}
}
まず、私は このファイル を参照しています。
アプリケーションではなくv7.widget.RecyclerViewがアダプターをリークしているようです。しかし、それは正しくありません...正しいですか?
実際には、あなたのRecyclerView
をリークしているアダプタです(そして、トレースグラフとLeakCanaryアクティビティのタイトルによってかなり明確になります)。ただし、それが「親」のRecyclerViewであるか、HourlyViewHolderでネストされたものであるか、またはその両方であるかはわかりません。犯人はあなたのViewHoldersだと思います。それらを非静的内部クラスにすることで、それらを囲むアダプタークラスへの参照を明示的に提供します。これは、ホルダー内のすべてのitemView
の親がRecyclerView自体。
この問題を修正するための最初の提案は、ViewHoldersとAdapterをstatic内部クラスにすることで分離することです。そうすることで、それらはアダプターへの参照を保持しないため、contextフィールドはそれらにアクセスできなくなります。また、コンテキスト参照は渡さずに保存する必要があるため、これも良いことです。メモリリーク)。文字列を取得するためだけにコンテキストが必要な場合は、アダプターコンストラクターなどの別の場所で実行しますが、メンバーとしてコンテキストを保存しないでください。最後に、DayForecastAdapter
も危険に思われます。同じインスタンスを1つすべてのHourlyViewHolder
に渡します。これはバグのようです。
デザインを修正してこれらのクラスを分離することで、このメモリリークを解消できると思います
画像を開いて実際のリークを確認することはできませんが、RecyclerView
でFragment
のローカル変数を定義し、フラグメントのretainInstanceState
true
を設定すると、ローテーションでリークが発生する可能性があります。
Fragment
でretainInstance
を使用する場合、onDestroyView
内のすべてのUI参照をクリアする必要があります
@Override
public void onDestroyView() {
yourRecyclerView = null;
super.onDestroyView();
}
ここでは、このリンクから詳細情報を見つけることができます: Iとメモリリークのある保持フラグメント