在 onSaveInstanceState() 之后多次调用 writeToParcel() 导致内存泄漏
Memory leak with writeToParcel() called multiple times after onSaveInstanceState()
在某些 activity 中,我必须将 MVC 模型保存为一个 parcelable。 Parcelable 是根据 the doc, which I've read more than enough (but who knows, I obviously could have missed something). There is a leak in this activity, but I'm struggling to understand its cause. The SO question 构建的,这很有趣,但我的代码已经遵循了答案中的指导方针。
activity 拥有一个 viewpager,里面有大约 60 个片段(但最多 350 个)。模型从 activity 传递到所有片段,片段中的用户操作被保存到模型中。
每当我暂停我的 activity 时,onSaveInstanceState
就会被触发一次,并且在多次触发我的 parcelable 的 writeToParcel
方法之后立即触发。触发器的数量取决于 viewpager
+ 1 中加载的片段数量。因此在 activity 启动时,如果我关闭模拟器并重新打开, writeToParcel
被调用 3 次(只加载第一个和第二个片段),如果我向右滑动一次然后再做一次,它会被调用 4 次(第 2 个正在显示,第 3 个被加载),如果我在适配器上 setExtPosition()
并转到第 10 个片段,writeToParcel
被调用 7 次(加载第 9、10 和 11h)。
当然,如果我的用户滑动每个片段,它最终会变得丑陋 TransactionTooLargeException
,这让我来到这里。
这是一些代码。这里可能有大量 code/concept 改进,非常欢迎任何提示,但我的主要问题是我发现的这个肮脏的小漏洞。
在我的 activity:
@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 的任何编写方法仅在此 activity 和片段 newInstance()
.[=32= 中被调用]
如何找到这种奇怪的指数行为的原因?
PS:当然欢迎索取更多代码
编辑
我已经按照@Ben P.建议的解决方案进行了实施,问题已经改善了很多,但还没有完全解决。我的 activity 实现了一个接口,该接口现在有一个在 onAttach()
中调用的 getModel()
方法,以及一个 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
测量,我的模型在包裹内占用了大约 3kb(+/-10%,取决于输入的数量)。
我怎么知道增长来自哪里?
在具体讨论您的问题之前,我想指出传递给 setArguments()
的 Bundle
是片段实例状态的一部分。每次销毁和重新创建片段时,都需要保留这些参数。因此,您放入其中的任何内容 Bundle
都有可能在配置更改期间被打包和解包。
The activity own a viewpager with around 60 fragments inside (but up to 350). The model is passed from the activity to all fragments, and user actions in the fragments are saved into the model.
这听起来像是所有片段共享的一个 Model
对象。如果是这种情况,我建议 不要 将模型对象作为其参数的一部分传递给每个片段 Bundle
。这样做会在保存和恢复实例状态时造成巨大的重复。相反,我会在你的 Activity
中公开一个方法(类似于 getModel()
),然后从你的片段中调用它来检索模型实例。
另一方面,这听起来也像是您只是 开始 使用相同的 Model
对象,并且每个片段都可以以某种方式改变它.这意味着您 do 必须为每个片段保存一些内容到实例状态……但您可以在此处进行优化。与其保存和恢复整个 Model
对象,不如只存储差异。也就是说,如果片段 #1 更改了模型的 name
,而片段 #2 更改了模型的 value
,那么您可以让片段 #1 仅保存新名称,让片段 #2 仅保存新名称价值。这样做而不是保存模型对象的两个额外副本可能会节省大量资金。
在某些 activity 中,我必须将 MVC 模型保存为一个 parcelable。 Parcelable 是根据 the doc, which I've read more than enough (but who knows, I obviously could have missed something). There is a leak in this activity, but I'm struggling to understand its cause. The SO question
activity 拥有一个 viewpager,里面有大约 60 个片段(但最多 350 个)。模型从 activity 传递到所有片段,片段中的用户操作被保存到模型中。
每当我暂停我的 activity 时,onSaveInstanceState
就会被触发一次,并且在多次触发我的 parcelable 的 writeToParcel
方法之后立即触发。触发器的数量取决于 viewpager
+ 1 中加载的片段数量。因此在 activity 启动时,如果我关闭模拟器并重新打开, writeToParcel
被调用 3 次(只加载第一个和第二个片段),如果我向右滑动一次然后再做一次,它会被调用 4 次(第 2 个正在显示,第 3 个被加载),如果我在适配器上 setExtPosition()
并转到第 10 个片段,writeToParcel
被调用 7 次(加载第 9、10 和 11h)。
当然,如果我的用户滑动每个片段,它最终会变得丑陋 TransactionTooLargeException
,这让我来到这里。
这是一些代码。这里可能有大量 code/concept 改进,非常欢迎任何提示,但我的主要问题是我发现的这个肮脏的小漏洞。
在我的 activity:
@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 的任何编写方法仅在此 activity 和片段 newInstance()
.[=32= 中被调用]
如何找到这种奇怪的指数行为的原因?
PS:当然欢迎索取更多代码
编辑
我已经按照@Ben P.建议的解决方案进行了实施,问题已经改善了很多,但还没有完全解决。我的 activity 实现了一个接口,该接口现在有一个在 onAttach()
中调用的 getModel()
方法,以及一个 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
测量,我的模型在包裹内占用了大约 3kb(+/-10%,取决于输入的数量)。
我怎么知道增长来自哪里?
在具体讨论您的问题之前,我想指出传递给 setArguments()
的 Bundle
是片段实例状态的一部分。每次销毁和重新创建片段时,都需要保留这些参数。因此,您放入其中的任何内容 Bundle
都有可能在配置更改期间被打包和解包。
The activity own a viewpager with around 60 fragments inside (but up to 350). The model is passed from the activity to all fragments, and user actions in the fragments are saved into the model.
这听起来像是所有片段共享的一个 Model
对象。如果是这种情况,我建议 不要 将模型对象作为其参数的一部分传递给每个片段 Bundle
。这样做会在保存和恢复实例状态时造成巨大的重复。相反,我会在你的 Activity
中公开一个方法(类似于 getModel()
),然后从你的片段中调用它来检索模型实例。
另一方面,这听起来也像是您只是 开始 使用相同的 Model
对象,并且每个片段都可以以某种方式改变它.这意味着您 do 必须为每个片段保存一些内容到实例状态……但您可以在此处进行优化。与其保存和恢复整个 Model
对象,不如只存储差异。也就是说,如果片段 #1 更改了模型的 name
,而片段 #2 更改了模型的 value
,那么您可以让片段 #1 仅保存新名称,让片段 #2 仅保存新名称价值。这样做而不是保存模型对象的两个额外副本可能会节省大量资金。