开发者

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?

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜