How to handle an interface leak from leak canary?
I keep getting this leak in the canary about the interface from SubroutineParentItemAdapter Class wherein used in Subroutine Fragment. I do not understand how SubroutineParentItemAdapter.mOnClickListener is leaking and a way to fix it.
┬───
│ GC Root: Input or output parameters in native code
│
├─ dalvik.system.PathClassLoader in开发者_开发技巧stance
│ Leaking: NO (SubroutineParentItemAdapter↓ is not leaking and A ClassLoader
│ is never leaking)
│ ↓ ClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│ Leaking: NO (SubroutineParentItemAdapter↓ is not leaking)
│ ↓ Object[1914]
├─ com.habitdev.sprout.ui.menu.subroutine.adapter.SubroutineParentItemAdapter
│ class
│ Leaking: NO (a class is never leaking)
│ ↓ static SubroutineParentItemAdapter.mOnClickListener
╰→ com.habitdev.sprout.ui.menu.subroutine.SubroutineFragment instance
Leaking: YES (ObjectWatcher was watching this because com.habitdev.sprout.
ui.menu.subroutine.SubroutineFragment received Fragment#onDestroy()
callback. Conflicts with Fragment#mFragmentManager is not null)
Retaining 1.2 kB in 34 objects
key = 7e3f4168-59c3-4b4e-89f8-b7c2112ed785
watchDurationMillis = 7398
retainedDurationMillis = 2398
Subroutine.Class
public class SubroutineFragment extends Fragment implements SubroutineParentItemAdapter.OnClickListener, SubroutineModifyFragment.onClickBackPress {
private FragmentSubroutineBinding binding;
protected SubroutineModifyFragment subroutineModifyFragment = new SubroutineModifyFragment();
public SubroutineFragment() {
subroutineModifyFragment.setmOnClickBackPress(this);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentSubroutineBinding.inflate(inflater, container, false);
setRecyclerViewAdapter();
onBackPress();
return binding.getRoot();
}
/**
* Initialized and sets adapter for recyclerView
*/
private void setRecyclerViewAdapter() {
HabitWithSubroutinesViewModel habitWithSubroutinesViewModel = new ViewModelProvider(requireActivity()).get(HabitWithSubroutinesViewModel.class);
binding.subroutineRecyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
SubroutineParentItemAdapter parentAdapterItem = new SubroutineParentItemAdapter();
parentAdapterItem.setHabitsOnReform(habitWithSubroutinesViewModel.getAllHabitOnReform());
setEmptyRVBackground(parentAdapterItem);
parentAdapterItem.setmOnClickListener(this);
parentAdapterItem.setSubroutineLifecycleOwner(getViewLifecycleOwner());
parentAdapterItem.setHabitWithSubroutinesViewModel(habitWithSubroutinesViewModel);
binding.subroutineRecyclerView.setAdapter(parentAdapterItem);
habitWithSubroutinesViewModel.getAllHabitOnReformLiveData().observe(getViewLifecycleOwner(), habits -> {
parentAdapterItem.setHabitsOnReform(habits);
setEmptyRVBackground(parentAdapterItem);
});
}
/**
* Displays No subroutine on reform
*
* @param adapter SubroutineParentItemAdapter
*/
private void setEmptyRVBackground(SubroutineParentItemAdapter adapter) {
if (adapter.getItemCount() > 0) {
binding.subroutineEmptyLottieRecyclerView.setVisibility(View.INVISIBLE);
binding.subroutineEmptyLbl.setVisibility(View.INVISIBLE);
} else {
binding.subroutineEmptyLottieRecyclerView.setVisibility(View.VISIBLE);
binding.subroutineEmptyLbl.setVisibility(View.VISIBLE);
}
}
/**
* Interface from SubroutineParentItemAdapter
*
* @param habit Habit on Modify
*/
@Override
public void onModifySubroutine(Habits habit) {
subroutineModifyFragment.setHabit(habit);
getChildFragmentManager().beginTransaction()
.addToBackStack(SubroutineFragment.this.getTag())
.add(binding.subroutineFrameLayout.getId(), subroutineModifyFragment)
.commit();
binding.subroutineContainer.setVisibility(View.GONE);
}
/**
* Interface from SubroutineModify Fragment
*/
@Override
public void returnSubroutineFragment() {
getChildFragmentManager()
.beginTransaction()
.remove(subroutineModifyFragment)
.commit();
binding.subroutineContainer.setVisibility(View.VISIBLE);
}
/**
* Handle on BackPress
*/
private void onBackPress() {
OnBackPressedCallback callback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
//Do something
}
};
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), callback);
}
/**
* On Fragment Destroy
*/
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
Subroutine Parent Item Adapter Class
public class SubroutineParentItemAdapter extends RecyclerView.Adapter<SubroutineParentItemAdapter.ParentItemViewHolder> {
private List<Habits> habitsOnReform;
protected HabitWithSubroutinesViewModel habitWithSubroutinesViewModel;
protected LifecycleOwner subroutineLifecycleOwner;
public interface OnClickListener {
void onModifySubroutine(Habits habit);
}
private static OnClickListener mOnClickListener;
public void setmOnClickListener(OnClickListener mOnClickListener) {
SubroutineParentItemAdapter.mOnClickListener = mOnClickListener;
}
public void setHabitWithSubroutinesViewModel(HabitWithSubroutinesViewModel habitWithSubroutinesViewModel) {
this.habitWithSubroutinesViewModel = habitWithSubroutinesViewModel;
}
public void setSubroutineLifecycleOwner(LifecycleOwner subroutineLifecycleOwner) {
this.subroutineLifecycleOwner = subroutineLifecycleOwner;
}
public SubroutineParentItemAdapter() {}
@NonNull
@Override
public ParentItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ParentItemViewHolder(
LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_subroutine_parent_item, parent, false)
);
}
@Override
public void onBindViewHolder(@NonNull ParentItemViewHolder holder, int position) {
long uid = holder.bindData(habitsOnReform.get(position), mOnClickListener);
LayoutAnimationController animationController = AnimationUtils.loadLayoutAnimation(holder.childRecycleView.getContext(), R.anim.layout_animation_fall);
holder.childRecycleView.setLayoutAnimation(animationController);
List<Subroutines> habitWithSubroutines;
habitWithSubroutines = habitWithSubroutinesViewModel.getAllSubroutinesOfHabit(uid);
SubroutineChildItemAdapter childAdapterItem = new SubroutineChildItemAdapter(habitWithSubroutines);
holder.childRecycleView.setAdapter(childAdapterItem);
childAdapterItem.setHabitWithSubroutines(habitWithSubroutines);
habitWithSubroutinesViewModel.getAllSubroutinesOnReformHabitLiveData(uid).observe(subroutineLifecycleOwner, childAdapterItem::setHabitWithSubroutines);
setItemTouchHelper(holder, childAdapterItem);
}
private void setItemTouchHelper(SubroutineParentItemAdapter.ParentItemViewHolder holder, SubroutineChildItemAdapter childAdapterItem) {
ItemTouchHelper itemTouchHelper;
ItemTouchHelper.Callback itemTouchHelperCallback = new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(0, ItemTouchHelper.END | ItemTouchHelper.START);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
return false;
}
@Override
public boolean isLongPressDragEnabled() {
return false;
}
@Override
public boolean isItemViewSwipeEnabled() {
return true;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
switch (direction) {
case ItemTouchHelper.END:
case ItemTouchHelper.START:
SubroutineChildItemAdapter.ChildItemViewHolder childItemViewHolder;
childItemViewHolder = (SubroutineChildItemAdapter.ChildItemViewHolder) viewHolder;
childAdapterItem.notifyItemChanged(childItemViewHolder.getAbsoluteAdapterPosition());
break;
}
}
};
itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback);
itemTouchHelper.attachToRecyclerView(holder.childRecycleView);
}
@Override
public int getItemCount() {
return habitsOnReform.size();
}
@Override
public int getItemViewType(int position) {
return super.getItemViewType(position);
}
public void setHabitsOnReform(List<Habits> habitsOnReform) {
this.habitsOnReform = habitsOnReform;
}
public static class ParentItemViewHolder extends RecyclerView.ViewHolder {
RelativeLayout itemLayout;
TextView HabitsTitle;
Button ModifySubroutine;
RecyclerView childRecycleView;
Drawable cloud, amethyst, sunflower, nephritis, bright_sky_blue, alzarin;
public ParentItemViewHolder(@NonNull View itemView) {
super(itemView);
itemLayout = itemView.findViewById(R.id.subroutine_parent_item_layout);
HabitsTitle = itemView.findViewById(R.id.subroutine_parent_item_habit_title);
ModifySubroutine = itemView.findViewById(R.id.subroutine_parent_item_modify_subroutine);
childRecycleView = itemView.findViewById(R.id.subroutine_child_recyclerview);
cloud = ContextCompat.getDrawable(itemView.getContext(), R.drawable.background_btn_cloud_selector);
amethyst = ContextCompat.getDrawable(itemView.getContext(), R.drawable.background_btn_amethyst_selector);
sunflower = ContextCompat.getDrawable(itemView.getContext(), R.drawable.background_btn_sunflower_selector);
nephritis = ContextCompat.getDrawable(itemView.getContext(), R.drawable.background_btn_nephritis_selector);
bright_sky_blue = ContextCompat.getDrawable(itemView.getContext(), R.drawable.background_btn_brightsky_blue_selector);
alzarin = ContextCompat.getDrawable(itemView.getContext(), R.drawable.background_btn_alzarin_selector);
}
@SuppressLint("ClickableViewAccessibility")
long bindData(Habits habit, OnClickListener mOnClickListener) {
if (habit.getColor().equals(AppColor.ALZARIN.getColor())) {
itemLayout.setBackground(alzarin);
} else if (habit.getColor().equals(AppColor.AMETHYST.getColor())) {
itemLayout.setBackground(amethyst);
} else if (habit.getColor().equals(AppColor.BRIGHT_SKY_BLUE.getColor())) {
itemLayout.setBackground(bright_sky_blue);
} else if (habit.getColor().equals(AppColor.NEPHRITIS.getColor())) {
itemLayout.setBackground(nephritis);
} else if (habit.getColor().equals(AppColor.SUNFLOWER.getColor())) {
itemLayout.setBackground(sunflower);
} else {
itemLayout.setBackground(cloud);
}
HabitsTitle.setText(habit.getHabit());
HabitsTitle.setPadding(padding_inPx(10), padding_inPx(0), padding_inPx(10), padding_inPx(5));
itemLayout.setOnClickListener(view -> {
childRecycleView.getVisibility();
if (childRecycleView.getVisibility() == View.GONE) {
LayoutAnimationController animationController = AnimationUtils.loadLayoutAnimation(childRecycleView.getContext(), R.anim.layout_animation_fall);
childRecycleView.setLayoutAnimation(animationController);
childRecycleView.setVisibility(View.VISIBLE);
} else {
LayoutAnimationController animationController = AnimationUtils.loadLayoutAnimation(childRecycleView.getContext(), R.anim.layout_animation_up);
childRecycleView.setLayoutAnimation(animationController);
if (!childRecycleView.isAnimating()) childRecycleView.setVisibility(View.GONE);
}
});
itemLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
HabitsTitle.setPadding(padding_inPx(10), padding_inPx(5), padding_inPx(10), padding_inPx(0));
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL) {
HabitsTitle.setPadding(padding_inPx(10), padding_inPx(0), padding_inPx(10), padding_inPx(5));
}
return false;
}
});
if (habit.isModifiable()) {
ModifySubroutine.setOnClickListener(view -> {
mOnClickListener.onModifySubroutine(habit); //OnclickListener Here
});
} else {
ModifySubroutine.setVisibility(View.GONE);
}
return habit.getPk_habit_uid();
}
int padding_inPx(int dp) {
final float scale = itemLayout.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
}
/**
* Detach mOnClickListener
* @param recyclerView
*/
@Override
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
super.onDetachedFromRecyclerView(recyclerView);
if (mOnClickListener != null) {
mOnClickListener = null;
}
}
}
How do you deal with SubroutineParentItemAdapter.mOnClickListener, as Leak Canary Detected? I tried to fix this by overriding onDetachFromRecyclerView and setting mOnclickListener null, but it does not seem to work. What is the practice of making a custom interface nonleaking?
精彩评论