我的应用启动时带有一个视图控制器和一个由按钮和子视图组成的简单视图。当用户触摸按钮时,子视图会填充滚动视图,显示列标题、行标题和电子表格的单元格。为了绘制单元格,我使用 CGBitmapContext 来绘制单元格,生成图像,然后将图像放入显示单元格的滚动视图中包含的图像视图中。
当我在 iPad 上运行该应用程序时,它可以很好地显示单元格,并且滚动视图让用户可以毫无问题地在电子表格中滚动。如果用户第二次触摸该按钮,电子表格将重新绘制并继续正常工作,但是,如果用户第三次触摸该按钮,则应用程序崩溃。应用程序输出窗口中没有异常信息显示。
我的第一个想法是连续的按钮按下耗尽了所有可用内存,所以我覆盖了视图控制器中的 DidReceiveMemoryWarning 方法并使用断点来确认该方法没有被调用。我的下一个想法是 CGBitmapContext 没有被释放,并寻找与 Objective C 的 CGContextRelease() 函数等效的 Monotouch。我能找到的最接近的是我调用的 CGBitmapContext 实例方法 Dispose(),但没有解决问题。
为了释放尽可能多的内存(以防我以某种方式耗尽内存而没有触发警告),我尝试在每次完成使用 CGBitmapContext 时强制进行垃圾收集。这使问题变得更糟。现在程序会在第一次显示电子表格后立即崩溃。这让我想知道垃圾收集器是否以某种方式收集了在屏幕上继续显示图形所必需的东西。
我将不胜感激有关进一步调查这些崩溃原因的途径的任何建议。我已经包含了 SpreadsheetView 类的源代码。相关的方法是 DrawSpreadsheet(),当按钮被触摸时调用。
感谢您在此问题上提供的帮助。
斯蒂芬·阿什利
public class SpreadsheetView : UIView
{
public ISpreadsheetMessenger spreadsheetMessenger = null;
public UIScrollView cellsScrollView = null;
public UIImageView cellsImageView = null;
public SpreadsheetView(RectangleF frame) : base()
{
Frame = frame;
BackgroundColor = Constants.backgroundBlack;
AutosizesSubviews = true;
}
public void DrawSpreadsheet()
{
UInt16 RowHeaderWidth = spreadsheetMessenger.RowHeaderWidth;
UInt16 RowHeaderHeight = spreadsheetMessenger.RowHeaderHeight;
UInt16 RowCount = spreadsheetMessenger.RowCount;
UInt16 ColumnHeaderWidth = spreadsheetMessenger.ColumnHeaderWidth;
UInt16 ColumnHeaderHeight = spreadsheetMessenger.ColumnHeaderHeight;
UInt16 ColumnCount = spreadsheetMessenger.ColumnCount;
// Add the corner
UIImageView cornerView = new UIImageView(new RectangleF(0f, 0f,
RowHeaderWidth, ColumnHeaderHeight));
cornerView.BackgroundColor = Constants.headingColor;
CGColorSpace cornerColorSpace = null;
CGBitmapContext cornerContext = null;
IntPtr buffer = Marshal.AllocHGlobal(RowHeaderWidth * ColumnHeaderHeight * 4);
if (buffer == IntPtr.Zero)
throw new OutOfMemoryException("Out of memory.");
try
{
cornerColorSpace = CGColorSpace.CreateDeviceRGB();
cornerContext = new CGBitmapContext
(buffer, RowHeaderWidth, ColumnHeaderHeight, 8, 4 * RowHeaderWidth,
cornerColorSpace, CGImageAlphaInfo.PremultipliedFirst);
cornerContext.SetFillColorWithColor(Constants.headingColor.CGColor);
cornerContext.FillRect(new RectangleF(0f, 0f, RowHeaderWidth, ColumnHeaderHeight));
cornerView.Image = UIImage.FromImage(cornerContext.ToImage());
}
finally
{
Marshal.FreeHGlobal(buffer);
if (cornerContext != null)
{
cornerContext.Dispose();
cornerContext = null;
}
if (cornerColorSpace != null)
{
cornerColorSpace.Dispose();
cornerColorSpace = null;
}
}
cornerView.Image = DrawBottomRightCorner(cornerView.Image);
AddSubview(cornerView);
// Add the cellsScrollView
cellsScrollView = new UIScrollView
(new RectangleF(RowHeaderWidth, ColumnHeaderHeight,
Frame.Width - RowHeaderWidth,
Frame.Height - ColumnHeaderHeight));
cellsScrollView.ContentSize = new SizeF
(ColumnCount * ColumnHeaderWidth,
RowCount * RowHeaderHeight);
Size iContentSize = new Size((int)cellsScrollView.ContentSize.Width,
(int)cellsScrollView.ContentSize.Height);
cellsScrollView.BackgroundColor = UIColor.Black;
AddSubview(cellsScrollView);
CGColorSpace colorSpace = null;
CGBitmapContext context = null;
CGGradient gradient = null;
UIImage image = null;
int bytesPerRow = 4 * iContentSize.Width;
int byteCount = bytesPerRow * iContentSize.Height;
buffer = Marshal.AllocHGlobal(byteCount);
if (buffer == IntPtr.Zero)
throw new OutOfMemoryException("Out of memory.");
try
{
colorSpace = CGColorSpace.CreateDeviceRGB();
context = new CGBitmapContext
(buffer, iContentSize.Width,
iContentSize.Height, 8, 4 * iContentSize.Width,
colorSpace, CGImageAlphaInfo.PremultipliedFirst);
float[] components = new float[]
{.75f, .75f, .75f, 1f,
.25f, .25f, .25f, 1f};
float[] locations = new float[]{0f, 1f};
gradient = new CGGradient(colorSpace, components, locations);
PointF startPoint = new PointF(0f, (float)iContentSize.Height);
PointF endPoint = new PointF((float)iContentSize.Width, 0f);
context.DrawLinearGradient(gradient, startPoint, endPoint, 0);
context.SetLineWidth(Constants.lineWidth);
context.BeginPath();
for (UInt16 i = 1; i <= RowCount; i++)
{
context.MoveTo
(0f, iContentSize.Height - i * RowHeaderHeight + (Constants.lineWidth/2));
context.AddLineToPoint((float)iContentSize.Width,
iContentSize.Height - i * RowHeaderHeight + (Constants.lineWidth/2));
}
for (UInt16 j = 1; j <= ColumnCount; j++)
{
context.MoveTo((float)j * ColumnHeaderWidth - Constants.lineWidth/2,
(float)iContentSize.Height);
context.AddLineToPoint((float)j * ColumnHeaderWidth - Constants.lineWidth/2, 0f);
}
context.StrokePath();
image = UIImage.FromImage(context.ToImage());
}
finally
{
Marshal.FreeHGlobal(buffer);
if (gradient != null)
{
gradient.Dispose();
gradient = null;
}
if (context != null)
{
context.Dispose();
context = null;
}
if (colorSpace != null)
{
colorSpace.Dispose();
colorSpace = null;
}
// GC.Collect();
//GC.WaitForPendingFinalizers();
}
UIImage finalImage = ActivateCell(1, 1, image);
finalImage = ActivateCell(0, 0, finalImage);
cellsImageView = new UIImageView(finalImage);
cellsImageView.Frame = new RectangleF(0f, 0f,
iContentSize.Width, iContentSize.Height);
cellsScrollView.AddSubview(cellsImageView);
}
private UIImage ActivateCell(UInt16 column, UInt16 row, UIImage backgroundImage)
{
UInt16 ColumnHeaderWidth = (UInt16)spreadsheetMessenger.ColumnHeaderWidth;
UInt16 RowHeaderHeight = (UInt16)spreadsheetMessenger.RowHeaderHeight;
CGColorSpace cellColorSpace = null;
CGBitmapContext cellContext = null;
UIImage cellImage = null;
IntPtr buffer = Marshal.AllocHGlobal(4 * ColumnHeaderWidth * RowHeaderHeight);
if (buffer == IntPtr.Zero)
throw new OutOfMemoryException("Out of memory: ActivateCell()");
try
{
cellColorSpace = CGColorSpace.CreateDeviceRGB();
// Create a bitmap the size of a cell
cellContext = new CGBitmapContext
(buffer, ColumnHeaderWidth, RowHeaderHeight, 8,
4 * ColumnHeaderWidth, cellColorSpace, CGImageAlphaInfo.PremultipliedFirst);
// Paint it white
cellContext.SetFillColorWithColor(UIColor.White.CGColor);
cellContext.FillRect(new RectangleF(0f, 0f, ColumnHeaderWidth, RowHeaderHeight));
// Convert it to an image
cellImage = UIImage.FromImage(cellContext.ToImage());
}
finally
{
Marshal.FreeHGlobal(buffer);
if (cellContext != null)
{
cellContext.Dispose();
cellContext = null;
}
if (cellColorSpace != null)
{
cellColorSpace.Dispose();
cellColorSpace = null;
}
// GC.Collect();
//GC.WaitForPendingFinalizers();
}
// Draw the border on the cell image
cellImage = DrawBottomRightCorner(cellImage);
CGColorSpace colorSpace = null;
CGBitmapContext context = null;
Size iContentSize = new Size((int)backgroundImage.Size.Width,
(int)backgroundImage.Size.Height);
buffer = Marshal.AllocHGlobal(4 * iContentSize.Width * iContentSize.Height);
if (buffer == IntPtr.Zero)
throw new OutOfMemoryException("Out of memory: ActivateCell().");
try
{
colorSpace = CGColorSpace.CreateDeviceRGB();
// Set up a bitmap context the size of the whole grid
context = new CGBitmapContext
(buffer, iContentSize.Width,
iContentSize.Height, 8, 4 * iContentSize.Width,
colorSpace, CGImageAlphaInfo.PremultipliedFirst);
// Draw the original grid into the bitmap
context.DrawImage(new RectangleF(0f, 0f, iContentSize.Width, iContentSize.Height),
backgroundImage.CGImage);
// Draw the cell image into the bitmap
context.DrawImage(new RectangleF(column * ColumnHeaderWidth,
iContentSize.Height - (row + 1) * RowHeaderHeight,
ColumnHeaderWidth, RowHeaderHeight),
cellImage.CGImage);
// Convert the bitmap back to an image
backgroundImage = UIImage.FromImage(context.ToImage());
}
finally
{
Marshal.FreeHGlobal(buffer);
if (context != null)
{
context.Dispose();
context = null;
}
if (colorSpace != null)
{
colorSpace.Dispose();
colorSpace = null;
}
// GC.Collect();
//GC.WaitForPendingFinalizers();
}
return backgroundImage;
}
private UIImage DrawBottomRightCorner(UIImage image)
{
int width = (int)image.Size.Width;
int height = (int)image.Size.Height;
float lineWidth = Constants.lineWidth;
CGColorSpace colorSpace = null;
CGBitmapContext context = null;
UIImage returnImage = null;
IntPtr buffer = Marshal.AllocHGlobal(4 * width * height);
if (buffer == IntPtr.Zero)
throw new OutOfMemoryException("Out of memory: DrawBottomRightCorner().");
try
{
colorSpace = CGColorSpace.CreateDeviceRGB();
context = new CGBitmapContext
(buffer, width, height, 8, 4 * width, colorSpace,
CGImageAlphaInfo.PremultipliedFirst);
context.DrawImage(new RectangleF(0f, 0f, width, height),
image.CGImage);
context.BeginPath();
context.MoveTo(0f, (int)(lineWidth/2f));
context.AddLineToPoint(width - (int)(lineWidth/2f), (int)(lineWidth/2f));
context.AddLineToPoint(width - (int)(lineWidth/2f), height);
context.SetLineWidth(Constants.lineWidth);
context.SetStrokeColorWithColor(UIColor.Black.CGColor);
context.StrokePath();
returnImage = UIImage.FromImage(context.ToImage());
}
finally
{
Marshal.FreeHGlobal(buffer);
if (context != null){
context.Dispose();
context = null;}
if (colorSpace != null){
colorSpace.Dispose();
colorSpace = null;}
// GC.Collect();
//GC.WaitForPendingFinalizers();
}
return returnImage;
}
}