【问题标题】:Trying to detect ActionMode memory leak试图检测 ActionMode 内存泄漏
【发布时间】:2018-11-12 12:05:39
【问题描述】:

我几天来一直试图找到 ActionMode 内存泄漏的来源,但没有运气。我有一个包含多个片段的活动,当我离开具有 ActionMode 的片段(同时自动取消它)时,LeakCanary 检测到内存泄漏。

我已经在destroy() 上取消了ActionMode 和ActionMode.Callback,甚至尝试在onDestroyActionMode() 上这样做。

这是我的 LeakCanary 截图:

https://i.imgur.com/RUbdqj3.png

我希望有人指出我正确的方向。

附:我怀疑它与 ActionMode.Callback 有关。虽然,我找不到任何破坏它的回调方法。我使用 startSupportActionMode(mActionModeCallback) 启动 ActionMode。我也试图找到一种方法来从中删除 mActionModeCallback,但没有方法。

这是我完整的 ActionMode 代码:

private ActionMode mActionMode;
private ActionMode.Callback mActionModeCallback;

public void startCAB()
{
    if (mActionMode == null)
        mActionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(mActionModeCallback);
}


private void buildActionModeCallBack()
{
    mActionModeCallback = new ActionMode.Callback() {
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            MenuInflater inflater = mode.getMenuInflater();
            inflater.inflate(R.menu.menu_cab, menu);
            return true;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            return false;
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            switch (item.getItemId()) {
                ... Some Code ...
            }
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {
            mActionMode = null;
    mActionModeCallback = null; // Tried with and without this.
        }
    };
}

public void finishActionMode()
{
    mActionMode.finish();
}

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

包含片段的父 Activity:

@Override
public void onTabUnselected(TabLayout.Tab tab)
{
    clearCAB();
}

private void clearCAB()
{
    int index = mPagerAdapter.getCurrentFragmentIndex();
    FragmentOne fragmentOne = (FragmentOne) mPagerAdapter.instantiateItem(mViewPager, index);
    fragmentOne.finishActionMode();
}

【问题讨论】:

  • 您是否尝试过调用mActionMode.finish() 而不是将引用显式设置为null
  • 是的,我已经从我的父活动中添加了代码,它显示了我在滑动时完成 CAB 的位置。
  • 在我的应用程序中遇到了同样的问题,该应用程序使用片段内的列表视图,托管在视图寻呼机中。
  • 没有看到这个问题,我正在查看源代码,因为它看起来很可疑,我无法取消设置 ActionMode.Callback。一旦你调用AppCompatActivity.startSupportActionMode(mActionModeCallback)ActionMode.Callback 就永远不会在你调用ActionMode.finish 后被取消引用,因此回调总是有一个强引用。一种解决方案是制作一个与应用程序具有相同生命周期的回调具体实现,然后自己在自定义回调实现中设置/取消设置变量,这样谁的片段就不会泄露 - 这是一个aosp错误。
  • 在这篇文章发布一年半之后,我确实遇到了这个问题。你找到解决办法了吗?

标签: java android


【解决方案1】:

根据我的经验,如果您的ActionMode.Callback 对象使用匿名内部类,可能会导致您的片段内存泄漏。

也许你可以创建一个新类并实现ActionMode.Callback,然后用它来输入startSupportActionMode()参数:

public class YourFragment extends skip implements skip, ActionMode.Callback {

    private ActionMode mActionMode;

    public void startCAB()
    {
        if (mActionMode == null)
            mActionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(new SafeActionModeCallback(this));
    }

    public void finishActionMode()
    {
        mActionMode.finish();
    }

    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.menu_cab, menu);
        return true;
    }

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false;
    }

    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        switch (item.getItemId()) {
            // ... Some Code ...
        }
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {
        mActionMode = null;
    }
}

SafeActionModeCallback:

public class SafeActionModeCallback implements ActionMode.Callback {

    // you can also use the WeakReference
    private ActionMode.Callback callback;

    public SafeActionModeCallback(ActionMode.Callback callback) {
        this.callback = callback;
    }

    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        return callback.onCreateActionMode(mode, menu);
    }

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return callback.onPrepareActionMode(mode, menu);
    }

    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        return callback.onActionItemClicked(mode, item);
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {
        callback.onDestroyActionMode(mode);
        callback = null;
    }
}

【讨论】:

  • 我尝试了类似的方法,我没有使用 ActionMode.Callback 变量,而是使用了 lambdas (kotlin),但这不起作用 - 片段仍然泄漏
  • 绝对对我有用,泄漏消失了。
【解决方案2】:

似乎活动中的 ActionMode 引用了片段的布局,这导致了内存泄漏并阻止了片段被 GC'ed。我找不到删除引用的方法。

在我的用例中,我在激活 Activity 的 ActionMode 的片段内使用 ListView(通过 listener.setMultiChoiceModeListener)。

我的 hacky 解决方案:在片段的 onDestroyView 中,从布局中删除 listView(或激活 ActionMode 的任何视图)并删除列表视图的所有侦听器。我为它做了一个 kotlin 扩展方法:

fun ListView.removeViewAndClearListeners() {
    setMultiChoiceModeListener(null)
    setOnScrollListener(null)
    onItemClickListener = null

    (parent as? ViewGroup)?.removeView(this)
}

这样做之后,泄漏就消失了。

【讨论】:

  • 抱歉,回复晚了,因为我没有注意到 cmets。完全激活是什么意思?
  • 已激活意味着操作模式是“激活的”并在菜单中显示上下文按钮
  • 好的,我如何找到执行该操作的视图?我应该检查哪一行代码?
  • 对我来说这很明显,因为我将操作模式附加到列表视图。如果您不知道,也许您可​​以通过编程或附加断点找到它。
  • 你用什么线来准确地附加它?感谢您的帮助。
【解决方案3】:

我仍然想知道您为什么要依赖 ActionMode.Callback。我有一个应用程序,我应该在长按时创建一个自定义菜单,但我在这个问题上浪费了将近 2 个月:

ActionModeCallback does not work

我不确定您是否意识到这一点,ActionMode 回调几乎不能在所有设备上运行。经过大量研究,我了解到,过于关注电池消耗和优化的设备不会让您的后台服务和一些回调按预期工作。

尝试在 MI 或 Oppo/Vivo 设备上测试您的代码。它会直接跳转到onDestroyActionMode,而不是调用onActionItemClicked

【讨论】:

    猜你喜欢
    • 2012-07-16
    • 1970-01-01
    • 1970-01-01
    • 2013-03-10
    • 2012-01-22
    • 2015-02-28
    相关资源
    最近更新 更多