我正在尝试创建一个应用程序来使用 Zxing 的 Monotouch 和 C# 端口读取二维码,但我遇到了内存问题。当应用处理捕获的屏幕帧时,应用会收到内存警告,然后关闭。我已经删除了对Zxing的调用,以跟踪内存问题的根源,并且只需循环捕获屏幕图像即可重现该问题。
这是代码:
using System;
using System.Drawing;
using System.Collections.Generic;
using System.Threading;
using MonoTouch.UIKit;
using MonoTouch.Foundation;
using MonoTouch.CoreGraphics;
using com.google.zxing;
using com.google.zxing.common;
using System.Collections;
using MonoTouch.AudioToolbox;
using iOS_Client.Utilities;
namespace iOS_Client.Controllers
{
public class CameraOverLayView : UIView
{
private Thread _thread;
private CameraViewController _parentViewController;
private Hashtable hints;
private static com.google.zxing.MultiFormatReader _multiFormatReader = null;
private static RectangleF picFrame = new RectangleF(0, 146, 320, 157);
private static UIImage _theScreenImage = null;
public CameraOverLayView(CameraViewController parentController) : base()
{
Initialize();
_parentViewController = parentController;
}
private void Initialize()
{
}
private bool Worker()
{
Result resultb = null;
if(DeviceHardware.Version == DeviceHardware.HardwareVersion.iPhone4
|| DeviceHardware.Version == DeviceHardware.HardwareVersion.iPhone4S)
{
picFrame = new RectangleF(0, 146*2, 320*2, 157*2);
}
if(hints==null)
{
var list = new ArrayList();
list.Add (com.google.zxing.BarcodeFormat.QR_CODE);
hints = new Hashtable();
hints.Add(com.google.zxing.DecodeHintType.POSSIBLE_FORMATS, list);
hints.Add (com.google.zxing.DecodeHintType.TRY_HARDER, true);
}
if(_multiFormatReader == null)
{
_multiFormatReader = new com.google.zxing.MultiFormatReader();
}
using (var screenImage = CGImage.ScreenImage.WithImageInRect(picFrame))
{
using (_theScreenImage = UIImage.FromImage(screenImage))
{
Bitmap srcbitmap = new System.Drawing.Bitmap(_theScreenImage);
LuminanceSource source = null;
BinaryBitmap bitmap = null;
try {
source = new RGBLuminanceSource(srcbitmap, screenImage.Width, screenImage.Height);
bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
_multiFormatReader.Hints = hints;
resultb = null;
//_multiFormatReader.decodeWithState(bitmap);
if(resultb != null && resultb.Text!=null)
{
InvokeOnMainThread( () => _parentViewController.BarCodeScanned(resultb));
}
}
catch (ReaderException re)
{
//continue;
}
} catch (Exception ex) {
Console.WriteLine(ex.Message);
}
finally {
if(bitmap!=null)
bitmap = null;
if(source!=null)
source = null;
if(srcbitmap!=null)
{
srcbitmap.Dispose();
srcbitmap = null;
}
}
}
}
return resultb != null;
}
public void StartWorker()
{
if(_thread==null)
{
_thread = new Thread(()=> {
bool result = false;
while (result == false)
{
result = Worker();
Thread.Sleep (67);
}
});
}
_thread.Start();
}
public void StopWorker()
{
if(_thread!=null)
{
_thread.Abort();
_thread = null;
}
//Just in case
_multiFormatReader = null;
hints = null;
}
protected override void Dispose(bool disposing)
{
StopWorker();
base.Dispose(disposing);
}
}
}
有趣的是,我查看了 http://blog.reinforce-lab.com/2010/02/monotouchvideocapturinghowto.html,试图看看其他人如何捕获和处理视频,这段代码与我的代码相同,大约 40 秒后退出并显示内存警告。
希望二维码能在不到 40 秒的时间内被扫描,但我不确定内存是否会被释放,所以问题可能会在扫描许多代码后出现。无论哪种方式,都应该可以在没有内存问题的情况下连续捕获视频源,对吗?
这有点违反直觉,但每次调用 ScreenImage 属性时都会创建一个新的 CGImage 实例,因此您还必须对该对象调用 Dispose :
using (var img = CGImage.ScreenImage) {
using (var screenImage = img.WithImageInRect(picFrame))
{
}
}
我将添加对我有用的实际解决方案,该解决方案结合了以前答案中的信息。循环中的代码如下所示:
using (var pool = new NSAutoreleasePool ())
{
using (var img = CGImage.ScreenImage)
{
using (var screenImage = img.WithImageInRect(picFrame))
{
using (_theScreenImage = UIImage.FromImage(screenImage))
{
}
}
}
}
GC.Collect();
zxing的原始System.Drawing.Bitmap
。MonoTouch缺乏Dispose
,这使得它从未释放其分配的非托管内存。
最近的一个(从你的链接)确实在调用Dispose
时释放非托管内存(它更好)。但是,它会创建一个位图上下文(在其构造函数中),并且不会手动处理它(例如,使用using
)。所以它依靠垃圾收集器 (GC) 稍后再做......
在许多情况下,这不是一个大问题,因为 GC 最终会释放此上下文实例并回收关联的内存。但是,如果您在循环中执行此操作,则可能会在 GC 启动之前耗尽(非托管)内存。这将为您提供内存警告,iOS可以决定杀死您的应用程序(或者它可能会自行崩溃)。
但我不确定记忆是否会被释放
是的,它应该是 - 但可能没有你需要内存那么快。正确实现(和使用)IDisposable
将解决此问题。
无论哪种方式,都应该可以在没有内存问题的情况下连续捕获视频源,对吗?
是的。确保您尽快释放内存,例如使用 using (var ...) { }
,并确保您使用的第三方代码也这样做。