在Android版Mono上设置屏幕旋转后的图像时出现内存错误



一切都很顺利,直到我关闭了设备上的屏幕锁定,然后事情开始间歇性地出错。

我已经设法找到了这个问题,并想出了一些解决办法,但我想知道是否有避免或消除这个问题的"最佳实践"。

问题:
我有一个应用程序,它会根据应用程序状态更改图像。这些图像不是巨大的,但相当大(231k~),并作为资源存储。在几次屏幕旋转后(我用一个使用单个ImageView的项目计算了27次),加载图像失败,出现类型为"Java.Lang.OutOfMemoryError"的异常

剥离到最简单的项目,以下说明了问题:

protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
//get a reference to the ImageView
var imageView = FindViewById<ImageView>(Resource.Id.imageView1);
imageView.SetImageBitmap( Android.Graphics.BitmapFactory.DecodeResource( this.Resources, Resource.Drawable.Ready) );
}

上面的代码是我用来重现这个问题的唯一方法。

在尝试解决的同时,我扩展了这个例子,使imageView在OnDestry:中发布

protected override void OnDestroy ()
{
base.OnDestroy ();
imageView.SetImageBitmap( null );
imageView.DestroyDrawingCache();
imageView.Dispose();
}

除非我添加了我不想做的GC.Collect().,否则这没有什么区别

到目前为止,我想到的最好的解决方法是如下修改代码:

static Bitmap _ready = null;
private Bitmap GetReadyImage {
get {
if (_ready == null) {
_ready = Android.Graphics.BitmapFactory.DecodeResource (this.Resources, Resource.Drawable.Ready);
}
return _ready;
}
}
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
//get a reference to the ImageView
imageView = FindViewById<ImageView>(Resource.Id.imageView1);
imageView.SetImageBitmap( GetReadyImage );
}

这依赖于对每个位图的静态引用和每个位图的属性访问器。

我甚至可以编写一个方法,将图像存储在静态列表中,以保存为每个不同的属性/变量编写属性访问器。

我也许可以添加标志ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize |ConfigChanges.KeyboardHidden),但这会破坏我所读到的正常活动生命周期,这不是最佳实践吗?

我觉得奇怪的是,我在网上搜索过,从来没有遇到过类似的问题或例子。我想知道大多数其他人是如何处理这件事的?

任何想法或评论都将不胜感激。

我只能从真正的本地角度来处理这个问题,因为我没有直接使用Mono框架。

所描述的症状100%表示"活动"中存在内存泄漏,但代码中没有显示任何真实证据。如果你真的能用一个只包含一个活动和这四行代码的项目来解决这个问题,那么在我看来,这可能是一个框架错误,应该向Xamarin提交。您是否尝试过用纯Java创建相同的简单项目,看看在您使用的同一设备/模拟器上的结果如何?同样有趣的是,如果这个问题被本地化到特定版本的Android。我以前从未在本机应用程序项目中见过这种特殊的行为。

尴尬的部分是你的声明,强制垃圾收集会使问题消失。你是对的,你不应该这样做,但真正的内存泄漏(即未发布的引用)仍然会持续存在,即使你多次命中GC。Android在每次轮换中销毁和重新创建"活动"的模式是这样的,即使旧的"活动"存在一段时间,当内存紧张时,它(及其所有引用)也会很快被收集起来,为新实例腾出空间。如果这种情况没有发生,并且每个"活动"都在过去,即使系统触发的GC通过了,那么Mono生成的本地代码中可能存在一个卡住的引用。

有趣的是,从技术上讲,通过将Bitmap附加到从未清除的静态字段,您的解决方法实际上引入了真正的泄漏。然而,我同意,相比之下,这似乎是一个更有效的举措。一个更简单的解决方法可能是对"活动"进行编码,以手动处理配置更改(我不知道Mono是否不同,但这是通过将android:configChanges="orientation"添加到清单文件中来实现的)。这将防止在每次旋转时重新创建"活动",但如果横向和纵向布局不同,也可能需要重新加载视图层次结构。然而,即使必须这样做,Acitivity实例也将是相同的——您可以安全地保存Bitmap,而无需使用静态字段。

然而,如果您不能在原生Java项目中重现相同项目的问题,我会报告一个Mono错误。

如果没有完整的代码,很难看到,但很明显,这听起来像是内存泄漏。已知屏幕旋转(由于活动的破坏/创建)会导致这些情况。你可能想看看罗曼·盖伊的这篇文章,以及去年IO的这篇演讲

最新更新