使用 CGImage.ScreenImage 循环使用 Mono Touch 时出现内存问题



我正在尝试创建一个应用程序来使用 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 ...) { } ,并确保您使用的第三方代码也这样做。

最新更新