android - 在 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
.
我怎么知道增长来自哪里?
解决方案
在具体解决您的问题之前,我想指出Bundle
传递给setArguments()
是片段实例状态的一部分。每次销毁和重新创建片段时,都需要保留这些参数。因此,您放入其中Bundle
的任何内容都有可能在配置更改期间被打包和解包。
该活动拥有一个查看器,其中包含大约 60 个片段(但最多 350 个)。模型从活动传递到所有片段,片段中的用户操作被保存到模型中。
这听起来就像您有一个Model
所有片段共享的对象。如果是这种情况,我建议不要将模型对象作为其参数的一部分传递给每个片段Bundle
。在保存和恢复实例状态时,这样做会导致大量重复。相反,我会在你的Activity
(类似的getModel()
)中公开一个方法,然后从你的片段中调用它来检索模型实例。
另一方面,听起来你可能只是从同一个Model
对象开始,并且每个片段都可以以某种方式改变它。这意味着您必须为每个片段保存一些内容到实例状态......但您可以在此处进行优化。与其保存和恢复整个Model
对象,不如只存储差异。也就是说,如果片段#1 改变了模型name
,片段#2 改变了模型value
,那么你可以让片段#1 只保存新名称,让片段#2 只保存新值。这样做而不是保存模型对象的两个额外副本可能会节省大量资金。
推荐阅读
- database - 带有 Firebase / Firestore 的博客应用程序的数据模型
- android - 将类别列表存储在静态变量中以在另一个活动中使用
- mongodb - Spring响应式mongodb模板更新文档部分包含对象
- ms-access - 在计算数据类型中将 Year([Year]) 显示为两位数
- flutter - 如何在 Flutter 中使用 Cardview 中的 Listview?
- postgresql - docker-compose up -d build 后恢复 Postgres db.dump 文件
- interface - USB真机接口在android studio 3.1中不显示
- c# - 如何将多个图像上传到数据库中以获得相同的 ID 并显示到 gridview 中?
- javascript - 为什么在线缩小器会更改我的代码?
- android - Android studio 3.1.4 工具栏卡在角落里