首页 > 解决方案 > 在 onSaveInstanceState() 之后多次调用 writeToParcel() 导致内存泄漏

问题描述

在某些活动中,我必须将 MVC 模型保存为 parcelable。parcelable 是根据doc 构建的,我已经阅读了足够多的内容(但谁知道呢,我显然可能错过了一些东西)。此活动存在泄漏,但我正在努力了解其原因。SO问题ViewPager中多个片段之间的通信对象很有趣,但我的代码已经遵循答案中的指导方针。

该活动拥有一个查看器,其中包含大约 60 个片段(但最多 350 个)。模型从活动传递到所有片段,片段中的用户操作被保存到模型中。

每当我暂停我的活动时,onSaveInstanceState都会触发一次,并且在我的 parcelablewriteToParcel方法的多次触发之后立即触发。触发器的数量取决于viewpager+ 1 中加载的片段数量。因此,在活动启动时,如果我关闭并重新打开模拟器writeToParcel,如果我滑动一次,则调用 3 次(仅加载第一个和第二个片段)正确并再做一次,它被调用 4 次(第 2 个显示并加载第 3 个),如果我setExtPosition()在适配器上并转到第 10 个片段,writeToParcel则被调用 7 次(第 9、第 10 和第 11h 被加载)。

当然,如果我的用户滑动每个片段,它最终会变得丑陋TransactionTooLargeException,这将我带到这里。

这是一些代码。这里可能有大量的代码/概念改进,非常欢迎任何提示,但我的主要问题是我发现的这个肮脏的小漏洞。

在我的活动中:

@Override
public void onSaveInstanceState (Bundle outState) {
    outState.putParcelable("model", myParcelable);
    super.onSaveInstanceState(outState);
}

在我的片段中:

public static MyFragment newInstance(Model model) {
    MyFragment fragment = new MyFragment();
    Bundle args = new Bundle();
    args.putParcelable(KEY_MODEL, model);
    fragment.setArguments(args);
    return fragment;
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Bundle args = getArguments();
    mModel = args.getParcelable(KEY_MODEL);
}

在我的可包裹模型中:

@Override
public void writeToParcel(Parcel dest, int flags) {
    int startSize = dest.dataSize();
    dest.writeString(foo); //one of these string is supposed to be null
    dest.writeString(bar);
    dest.writeString(foobar);
    dest.writeByte((byte) (isMyObjectTrue ? 1 : 0));
    dest.writeList(someStringList);
    dest.writeList(someIntegerList);
    dest.writeBundle(someBundle); //I use a Bundle to save a Map<String,String>
    int endSize = dest.dataSize();
}

我在方法内部运行了调试器writeToParcel(),我惊讶地发现它startSize永远不会为 0。这正常吗?

我在整个代码中进行了搜索,putParcelable()或者任何名称中带有 parcelable 的编写方法仅在此活动和片段中调用newInstance()

我怎样才能找到这种奇怪的指数行为的原因?

PS:当然可以随意索取更多代码。

编辑

我已经实施了@Ben P. 建议的解决方案,问题得到了很大改善,但并没有完全解决。我的活动实现了一个接口,该接口现在有一个getModel()名为 in 的方法onAttach(),以及一个setUserInput(char userInput)我用来从片段更新模型的方法。片段的newInstance()方法不再保存模型。

我的片段

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    try {
        callBack = (MyInterface) context; //callBack is a class field
        mModel = callBack.getModel();     //mModel too
    } catch (ClassCastException e) {
        throw new ClassCastException(context.toString() + " must implement MyInterface");
    }
}

这将指数问题变成了线性问题,这显然更好,但仍然是一个问题。

现在,writeToParcel()只调用一次,但包裹的总大小仍然随着加载的项目数量而增长。根据endSize-startSize.

我怎么知道增长来自哪里?

标签: androidmemory-leaksandroid-viewpagerparcelable

解决方案


在具体解决您的问题之前,我想指出Bundle传递给setArguments()是片段实例状态的一部分。每次销毁和重新创建片段时,都需要保留这些参数。因此,您放入其中Bundle的任何内容都有可能在配置更改期间被打包和解包。


该活动拥有一个查看器,其中包含大约 60 个片段(但最多 350 个)。模型从活动传递到所有片段,片段中的用户操作被保存到模型中。

这听起来就像您有一个Model所有片段共享的对象。如果是这种情况,我建议不要将模型对象作为其参数的一部分传递给每个片段Bundle。在保存和恢复实例状态时,这样做会导致大量重复。相反,我会在你的Activity(类似的getModel())中公开一个方法,然后从你的片段中调用它来检索模型实例。

另一方面,听起来你可能只是同一个Model对象开始,并且每个片段都可以以某种方式改变它。这意味着您必须为每个片段保存一些内容到实例状态......但您可以在此处进行优化。与其保存和恢复整个Model对象,不如只存储差异。也就是说,如果片段#1 改变了模型name,片段#2 改变了模型value,那么你可以让片段#1 只保存新名称,让片段#2 只保存新值。这样做而不是保存模型对象的两个额外副本可能会节省大量资金。


推荐阅读