I am having trouble with memory leak because of my recyclerview
, it says that View detached and has parent
in LeakCanary. I tried to set the recyclerview
to null on onDestroyView
but still nothing happens. Below is my fragment and the stacktrace of the LeakCanary:
public class CancelledOrdersFragment extends Fragment {
private RecyclerView recyclerView;
private FirebaseFirestore db = FirebaseFirestore.getInstance();
private CancelledOrdersAdapter adapter;
private OrderTransactionsModel model;
public CancelledOrdersFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View itemView = inflater.inflate(R.layout.fragment_cancelled_orders, container, false);
recyclerView = itemView.findViewById(R.id.cancelled_orders_rv);
LinearLayoutManager linearLayoutManager1 = new LinearLayoutManager(itemView.getContext());
linearLayoutManager1.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(linearLayoutManager1);
Paper.init(itemView.getContext());
String storeID = Paper.book().read("userid");
Query query = db.collection("Transactions").whereEqualTo("storeID", storeID).whereEqualTo("transactionStatus", "cancelled").orderBy("orderTimeout", Query.Direction.DESCENDING);
PagedList.Config Bconfig = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPrefetchDistance(10)
.setPageSize(8)
.build();
FirestorePagingOptions<OrderTransactionsModel> options = new FirestorePagingOptions.Builder<OrderTransactionsModel>()
.setLifecycleOwner(getActivity())
.setQuery(query, Bconfig, snapshot -> {
OrderTransactionsModel transactionsModel = snapshot.toObject(OrderTransactionsModel.class);
String transID = transactionsModel.getTransactionID();
String tranCOde = transactionsModel.getTransactionCode();
double storeTotal = transactionsModel.getStoreTotal();
HashMap<String, Object> CouponUsed = transactionsModel.getCouponUsed();
HashMap<String, Date> OrderStatus = transactionsModel.getOrderStatus();
model = new OrderTransactionsModel(transID, storeID, storeTotal, CouponUsed, OrderStatus, tranCOde);
return model;
})
.build();
adapter = new CancelledOrdersAdapter(options);
recyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
return itemView;
}
@Override
public void onDestroyView() {
super.onDestroyView();
adapter.stopListening();
recyclerView = null;
}
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
adapter.startListening();
}
┬───
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ GC Root: Local variable in native code
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ├─ android.net.ConnectivityThread instance
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Leaking: NO (PathClassLoader↓ is not leaking)
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Thread name: 'ConnectivityThread'
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ↓ ConnectivityThread.contextClassLoader
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ├─ dalvik.system.PathClassLoader instance
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Leaking: NO (InternalLeakCanary↓ is not leaking and A ClassLoader is never leaking)
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ↓ PathClassLoader.runtimeInternalObjects
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ├─ java.lang.Object[] array
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Leaking: NO (InternalLeakCanary↓ is not leaking)
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ↓ Object[].[1614]
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ├─ leakcanary.internal.InternalLeakCanary class
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Leaking: NO (OrdersActivity↓ is not leaking and a class is never leaking)
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ↓ static InternalLeakCanary.resumedActivity
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ├─ com.dreamakers.clustore.clustorestore.Activity.OrdersActivity instance
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Leaking: NO (Activity#mDestroyed is false)
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ↓ OrdersActivity.mLifecycleRegistry
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ~~~~~~~~~~~~~~~~~~
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ├─ androidx.lifecycle.LifecycleRegistry instance
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Leaking: UNKNOWN
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ↓ LifecycleRegistry.mObserverMap
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ~~~~~~~~~~~~
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ├─ androidx.arch.core.internal.FastSafeIterableMap instance
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Leaking: UNKNOWN
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ↓ FastSafeIterableMap.mEnd
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ~~~~
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ├─ androidx.arch.core.internal.SafeIterableMap$Entry instance
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Leaking: UNKNOWN
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ↓ SafeIterableMap$Entry.mKey
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ~~~~
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ├─ com.dreamakers.clustore.clustorestore.Adapter.CancelledOrdersAdapter instance
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Leaking: UNKNOWN
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ↓ CancelledOrdersAdapter.mObservable
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ~~~~~~~~~~~
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ├─ androidx.recyclerview.widget.RecyclerView$AdapterDataObservable instance
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Leaking: UNKNOWN
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ↓ RecyclerView$AdapterDataObservable.mObservers
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ~~~~~~~~~~
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ├─ java.util.ArrayList instance
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Leaking: UNKNOWN
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ↓ ArrayList.elementData
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ~~~~~~~~~~~
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ├─ java.lang.Object[] array
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Leaking: UNKNOWN
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ↓ Object[].[0]
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ~~~
2020-08-19 16:23:59.404 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ├─ androidx.recyclerview.widget.RecyclerView$RecyclerViewDataObserver instance
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Leaking: UNKNOWN
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ↓ RecyclerView$RecyclerViewDataObserver.this$0
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ~~~~~~
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ├─ androidx.recyclerview.widget.RecyclerView instance
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ Leaking: YES (View detached and has parent)
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ mContext instance of com.dreamakers.clustore.clustorestore.Activity.OrdersActivity with mDestroyed = false
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ View#mParent is set
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ View#mAttachInfo is null (view detached)
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ View.mID = R.id.cancelled_orders_rv
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ View.mWindowAttachCount = 1
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: │ ↓ RecyclerView.mParent
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: ╰→ android.widget.FrameLayout instance
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: Leaking: YES (ObjectWatcher was watching this because com.dreamakers.clustore.clustorestore.Activity.CancelledOrdersFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks))
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: key = 1bb796b4-4c87-4320-b90a-a348a14e616e
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: watchDurationMillis = 8633
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: retainedDurationMillis = 3631
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: mContext instance of com.dreamakers.clustore.clustorestore.Activity.OrdersActivity with mDestroyed = false
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: View#mParent is null
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: View#mAttachInfo is null (view detached)
2020-08-19 16:23:59.405 11647-13298/com.dreamakers.clustore.clustorestore D/LeakCanary: View.mWindowAttachCount = 1
The leaktrace shows that OrdersActivity is alive (not destroyed, all good) and its lifecycle registry has a mObserverMap which contains CancelledOrdersAdapter.
Based on the code, it seems that the lifecycle of CancelledOrdersAdapter and the R.id.cancelled_orders_rv RecyclerView should be aligned.
However, when the recycler view is detached, CancelledOrdersAdapter is held in memory because it registers as an observer to the lifecycle of the activity (which isn't destroyed at that point). The fix is likely to have CancelledOrdersAdapter register as an observer to the lifecycle of the fragment view instead, ie Fragment.viewLifecycleOwner