添加一个片段并调用 .replace() 导致添加的片段内存泄漏

Adding a fragment and calling .replace() causing a memory leak in the added fragment

我有一个带有 3 个选项卡的 BottomNavigationView。在每次单击选项卡时,它都会调用 .replace()

private void initBottomNavigation() {

        mBottomNavigation.setOnNavigationItemSelectedListener(menuItem -> {
            switch (menuItem.getItemId()) {
                case R.id.bottomnav_admin_home:                <---- Tab 1
                        getSupportFragmentManager().beginTransaction()
                                .replace(
                                        R.id.admin_fragment_container,
                                        new AdminHomeFragment(),
                                        Constants.FRAGMENT_ADMIN_HOME
                                )
                                .commit();
                    }

                    ...Tab two
                    ....Tab three

                    return true;
    ```

在我的第 3 个选项卡中,我有一个在片段容器顶部添加片段的按钮:

当我点击按钮时,它会调用它在片段容器的顶部添加另一个片段:

getParentFragmentManager().beginTransaction()
                .add(
                        R.id.admin_fragment_container,
                        new DeactivateUserFragment(),
                        Constants.FRAGMENT_ADMIN_DEACTIVATE_USER
                )
                .addToBackStack(null)
                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
                .commit();

当我在 added 片段上时,我单击调用 .replace() 的第二个选项卡,这导致我的 added 内存泄漏 片段。

我已经减少了添加的片段中的所有代码,但我不明白为什么它仍然泄漏。

添加片段的完整代码:

public class DeactivateUserFragment extends Fragment{
    private static final String TAG = "FragmentDeactivatedUser";

    @BindView(R.id.recycler_view) RecyclerView mRecycler;

    @BindView(R.id.parent_layout) ViewGroup mParentLayout;

    @BindView(R.id.progress_bar) ProgressBar mProgressBar;

    private Context                mContext;
    private SimpleListUsersAdapter mAdapter;
    private ArrayList<UserInfo>    mList = new ArrayList<>();
    private DeactivateViewModel    mViewModel;
    private AuthStateManager mAuthStateManager;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = getContext();

    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_admin_deactivate_user, container, false);
        ButterKnife.bind(this, view);



        return view;
    }

    
    @OnClick(R.id.btn_back)
    public void onBackBtnClick(){
        getParentFragmentManager().popBackStack();
    }


}

泄漏:

androidx.coordinatorlayout.widget.CoordinatorLayout instance
​     Leaking: YES (ObjectWatcher was watching this because com.example.
​     testproject.ui.admin.deactivate.DeactivateUserFragment received
​     Fragment#onDestroyView() callback (references to its views should be
​     cleared to prevent leaks))
​     Retaining 106.7 kB in 1469 objects
​     key = e50280e9-58bd-4ecd-bc1b-a2d6cc188639
​     watchDurationMillis = 108539
​     retainedDurationMillis = 103532
​     View not part of a window view hierarchy
​     View.mAttachInfo is null (view detached)
​     View.mID = R.id.parent_layout
​     View.mWindowAttachCount = 1
​     mContext instance of com.example.testproject.ui.admin.AdminActivity
​     with mDestroyed = false

编辑1:________________________________

为什么删除这三行不再导致内存泄漏?

@BindView(R.id.recycler_view) RecyclerView mRecycler;

    @BindView(R.id.parent_layout) ViewGroup mParentLayout;

    @BindView(R.id.progress_bar) ProgressBar mProgressBar;

我没有在任何代码中使用变量,只是绑定了它们。 我非常困惑这是怎么导致泄漏的

编辑 2:__________________________________

我已经尝试在 onDestroyView() 中将视图设置为 null 并解除 Butterknife 的绑定,但仍然会泄漏。

https://github.com/JakeWharton/butterknife/issues/585

 @Override
    public void onDestroyView() {
        Log.d(TAG, "onDestroyView: Called");
        unbinder.unbind();
        mRecycler = null;
        mParentLayout = null;
        mProgressBar = null;
        super.onDestroyView();
    }

我已经尝试一个一个地删除视图...进度条、回收器和 mParentLayout。直到我把3个都去掉,它才不会再漏了

我从片段中删除了 Butterknife 并且只使用 findViewById 但仍然有同样的问题

    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_admin_deactivate_user, container, false);
        unbinder=ButterKnife.bind(this, view);//initialise the unbinder here


      return view;
    }

    Override
    public void onDestroyView() {
    Log.d(TAG, "onDestroyView: Called");
  
    if(unbinder!=null){
        unbinder.unbind();
        unbinder = null;
    }
   
    super.onDestroyView();
    }

尝试将此添加到您的回收器适配器代码中:

     @Override
        public void onDestroyView() {
        recyclerView.setAdapter(null);
       super.onDestroyView();
     }

请尝试删除在 onDestroyonDestroyView 中分别在 onCreateonCreateView 中初始化的所有引用。 所以如果你有:

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = getContext();

    }

你还应该有:

    @Override
    public void onDestroy() {
        mContext = null;
        super.onDestroy();
    }

我建议您不要将上下文存储在属性中,因为您可以在 android Fragment 子类的任何地方访问方法 getContext()

上下文是一个习惯性的泄漏源。

我不知道你的其他属性到底是什么类型,但你应该保持相同的理念。

解决方法是在我的 Fragment 中为 butterknife 的 Binder 设置一个全局变量,并在 onDestroyView()

中调用 Unbind
private Unbinder unbinder;
@Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_admin_link_family, container, false);
        
        return view;
    }

我也绑定了适配器

private SimpleListUsersAdapter adapter;

Inside Adapter class


 class ViewHolder extends RecyclerView.ViewHolder {

        @BindView(R.id.action_type)
        TextView userAction;
        @BindView(R.id.date)
        TextView date;
        @BindView(R.id.amount)
        TextView amount;

        ViewHolder(@NonNull View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }

所以我还需要在 onDestroyView

中将适配器设置为 null
Fragment Class

@Override
    public void onDestroyView() {
        super.onDestroyView();
        adapter = null;
        unbinder.unbind();
}

需要两者都做才能阻止泄漏