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?
 
         加载中,请稍侯......
 加载中,请稍侯......
      
精彩评论