2

我正在尝试使用 Monotouch 和 Zxing 的 C# 端口创建一个应用程序来读取 QR 码,但我遇到了内存问题。当应用程序处理捕获的屏幕帧时,应用程序会收到内存警告,然后关闭。我已经删除了对 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 岁后退出带有内存警告的秒数。

希望 QR 码将在 40 秒内被扫描,但我不确定内存是否会被释放,因此在扫描许多代码后可能会出现问题。无论哪种方式,都应该可以连续捕获视频馈送而不会出现内存问题,对吗?

4

3 回答 3

3

这有点违反直觉,但 ScreenImage 属性会在您每次调用它时创建一个新的 CGImage 实例,因此您也必须在该对象上调用 Dispose:

using (var img = CGImage.ScreenImage) {
    using (var screenImage = img.WithImageInRect(picFrame))
    {
    }
}
于 2012-05-30T21:39:50.797 回答
2

我将添加对我有用的实际解决方案,它结合了以前答案中的信息。循环内的代码如下所示:

using (var pool = new NSAutoreleasePool ())
{
    using (var img = CGImage.ScreenImage)
    {       
        using (var screenImage = img.WithImageInRect(picFrame))
        {
            using (_theScreenImage = UIImage.FromImage(screenImage))
            {
            }
        }
    }
}

GC.Collect();
于 2012-05-31T09:27:38.257 回答
1

System.Drawing.Bitmapzxing.MonoTouch的原始版本缺少Dispose它,因此它永远不会释放它分配的非托管内存。

较新的(来自您的链接)在Dispose调用时确实释放了非托管内存(更好)。然而,它创建了一个位图上下文(在它的构造函数中)并且不会手动处理它(例如使用 a using)。所以它依赖垃圾收集器(GC)稍后来做......

在许多情况下,这不是一个大问题,因为 GC 最终会释放这个上下文实例并回收相关的内存。但是,如果您在循环中执行此操作,则可能会在 GC 启动之前耗尽(非托管)内存。这会给您带来内存警告,iOS 可以决定终止您的应用程序(或者它可能会自行崩溃) .

但我不确定内存是否会被释放

是的,它应该是——但可能不如你需要的内存那么快。正确实施(和使用)IDisposable将解决这个问题。

无论哪种方式,都应该可以连续捕获视频馈送而不会出现内存问题,对吗?

是的。确保您尽快释放内存,例如使用using (var ...) { },并确保您使用的第 3 方代码也这样做。

于 2012-05-30T15:05:51.320 回答