【问题标题】:Reducing memory usage with UIImagePickerController使用 UIImagePickerController 减少内存使用
【发布时间】:2014-01-19 00:34:32
【问题描述】:

在我的应用中,用户可以使用 UIImagePickerController 拍摄多张图片,然后这些图片会一张一张地显示在视图中。

我在内存管理方面遇到了一些问题。随着当今手机上的摄像头以百万像素的速度迅速增长,从 UIImagePickerController 返回的 UIImage 会占用大量内存。在我的 iPhone 4S 上,UIImages 大约 5MB;我很难想象它们在新的和未来的模型上会是什么样子。

我的一个朋友说,处理 UIImage 的最佳方法是立即将它们保存到我的应用程序文档目录中的 JPEG 文件中,并尽快释放原始 UIImage。所以这就是我一直在尝试做的。不幸的是,即使在将 UIImage 保存为 JPEG 并且在我的代码中没有留下对它的引用之后,它也不会被垃圾收集。

这是我的代码的相关部分。我正在使用 ARC。

// Entry point: UIImagePickerController delegate method
-(void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

    // Process the image.  The method returns a pathname.
    NSString* path = [self processImage:[info objectForKey:UIImagePickerControllerOriginalImage]];

    // Add the image to the view
    [self addImage:path];
}

-(NSString*) processImage:(UIImage*)image {

    // Get a file path
    NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString* documentsDirectory = [paths objectAtIndex:0];
    NSString* filename = [self makeImageFilename]; // implementation omitted
    NSString* imagePath = [documentsDirectory stringByAppendingPathComponent:filename];

    // Get the image data (blocking; around 1 second)
    NSData* imageData = UIImageJPEGRepresentation(image, 0.1);

    // Write the data to a file
    [imageData writeToFile:imagePath atomically:YES];

    // Upload the image (non-blocking)
    [self uploadImage:imageData withFilename:filename];

    return imagePath;
}

-(void) uploadImage:(NSData*)imageData withFilename:(NSString*)filename {
    // this sends the upload job (implementation omitted) to a thread
    // pool, which in this case is managed by PhoneGap
    [self.commandDelegate runInBackground:^{
        [self doUploadImage:imageData withFilename:filename];
    }];
}

-(void) addImage:(NSString*)path {
    // implementation omitted: make a UIImageView (set bounds, etc).  Save it
    // in the variable iv.

    iv.image = [UIImage imageWithContentsOfFile:path];
    [iv setNeedsDisplay];
    NSLog(@"Displaying image named %@", path);
    self.imageCount++;
}

请注意processImage 方法是如何引用 UIImage 的,但它仅将它用于一件事:制作该图像的 NSData* 表示。那么,processImage方法完成后,UIImage应该会从内存中释放出来吧?

我可以做些什么来减少我的应用程序的内存使用量?

更新

我现在意识到分配分析器的屏幕截图将有助于解释这个问题。

【问题讨论】:

  • UIImageView 需要多大的图像?根据上面的代码,图像被保存为 jpeg(节省磁盘空间),然后在addImage 方法中重新加载。再次加载图像时,图像会与将其保存为 jpeg 之前一样多。如果可能,请在将其保存到磁盘之前重新缩放到适当的大小。
  • 确实我正在为 UIImageView 重新加载图像,但 JPEG 表示只有原始大小的 10-15% 左右,所以我仍然应该看到内存减少,正确的?根据 Instruments 的说法,内存使用量在上升后从未下降。
  • @vote539 - 虽然压缩后的 JPEG 只有 10-15% 的大小,但当您重新加载它以在 imageView 中显示时,它需要解压缩,并且内存要求与任何其他图像。根据经验,您可以将其估计为像素 x 颜色 x 每种颜色的位深度。

标签: ios memory-management uiimage automatic-ref-counting uiimagepickercontroller


【解决方案1】:

processImage 方法不是你的问题。

我们可以将你的图片保存代码移植到Apple's PhotoPicker demo app中测试一下

方便的是,Apple 的示例项目与您的非常相似,具有在计时器上重复拍照的方法。在示例中,图像没有保存到文件系统,而是在内存中累积。它带有以下警告:

/* Start the timer to take a photo every 1.5 seconds.
CAUTION: for the purpose of this sample, we will continue to take pictures indefinitely.
Be aware we will run out of memory quickly. You must decide the proper threshold number of photos allowed to take from the camera.
One solution to avoid memory constraints is to save each taken photo to disk rather than keeping all of them in memory.
In low memory situations sometimes our "didReceiveMemoryWarning" method will be called in which case we can recover some memory and keep the app running.
*/

将您的方法添加到 Apple 的代码中,我们可以解决这个问题。

imagePicker 委托方法如下所示:

- (void)imagePickerController:(UIImagePickerController *)picker 
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *image = [info valueForKey:UIImagePickerControllerOriginalImage];

    [self.capturedImages removeAllObjects];   // (1)
    [self.imagePaths addObject:[self processImage:image]]; //(2)

    [self.capturedImages addObject:image];

    if ([self.cameraTimer isValid])
    {
        return;
    }
    [self finishAndUpdate]; //(3)
}

(1) - 我们的补充,用于在每个图像捕获事件上刷新实时内存
(2) - 我们的补充,将图像保存到文件系统并构建文件系统路径列表。
(3) - 对于我们的测试,我们使用 cameraTimer 来拍摄重复图像,因此不会调用 finishAndUpdate

我已经按原样使用了您的 processImage: 方法,如下所示:
[self uploadImage:imageData withFilename:filename];
注释掉了。

我还添加了一个小的 makeImageFileName 方法:

static int imageName = 0;

-(NSString*)makeImageFilename {
    imageName++;
    return [NSString stringWithFormat:@"%d.jpg",imageName];
}

这些是我对 Apple 代码所做的唯一项。

这里是苹果原代码的内存占用(cameraTimer运行时不带(1)和(2))

在捕获约 40 张图像后,内存攀升至约 140MB

这是添加的内存占用(cameraTimer 使用 (1) 和 (2) 运行)

文件保存方法正在修复内存问题:内存是平坦的,每个图像捕获的峰值约为 30MB。

这些测试是在 iPhone5S 上运行的。未压缩的图像为 3264 x 2448 像素,应约为 24mB(24 位 RGB)。 Jpeg 压缩(文件系统)大小范围在 250kB(0.1 质量,根据您的代码)到 1-2mB(0.7 质量)到 ~6mB(1.0 质量)之间。

在对您的问题的评论中,您建议重新加载的图像将从该压缩中受益。情况并非如此:当图像加载到内存中时,它必须首先解压缩。它的内存占用大约等于像素 x 颜色 x 每种颜色的位深度 - 无论图像在磁盘上的存储方式如何。正如jrturton 所指出的,这至少表明您应该避免以比显示所需分辨率更高的分辨率加载图像。假设您有一个 832 x 640 的全屏(视网膜)imageView,如果您的用户无法放大,那么您正在浪费内存加载比该图像更大的图像。这是 ~1.6mB 的实时内存占用,对您的 24mMB 原始文件有很大的改进(但这是您的主要问题的题外话)。

由于processImage 似乎不是你记忆问题的原因,你应该看看其他可能性:

1/ 您没有内存问题。您如何分析应用程序?
2/ addImageuploadImage 之一正在保留内存。尝试依次注释每个内容以确定哪个。
3/ 问题出在其他地方(PhoneGap 管理的东西?)

关于那些内存峰值,这些是由图像到数据 jpeg 压缩线引起的:
NSData* imageData = UIImageJPEGRepresentation(image, 0.1);

在底层,即 ImageIO,在使用 ImagePickerController 时可能是不可避免的。请参阅此处:Most memory efficient way to save a photo to disk on iPhone? 如果您切换到 AVFoundation,您可以将图像作为未转换的 NSData 获得,这样您就可以避免尖峰。

【讨论】:

  • 您还应该在上面包含您对 OP 期望低质量 JPEG 在添加到 imageView 时使用更少内存的评论 - 正如您所说,这是不正确的。从设备后置摄像头提供的分辨率图像对于图像视图来说太大(可缩放的全尺寸视图除外),应使用调整大小的版本。
  • 感谢您的详细回复!我已经下载了 PhotoPicker 项目,目前正在研究细节以查看是否可以查明问题。我会在完成整个过程后尽快回来。 :-)
  • 请参阅上面我在 OP 中附加了 Instruments 屏幕截图的位置。似乎应用程序的“整体”分配随着拍摄的照片数量线性增加,但在拍摄第一张照片后“净”分配保持不变。如您所见,应用程序会在净分配保持不变的时期内继续收到内存警告。但是,我想净分配保持不变的事实意味着 UIImage 正在按预期释放,这与您的可能性 1 相对应。非常感谢您帮助解释这一点!
  • @vote539 - 'overall' 是自应用启动以来的总分配数。它与当前的内存占用无关。您的内存使用量看起来很平坦,大约 3.5mb 的实时字节无需担心。我无法想象内存警告来自哪里,但 PhoneGap 可能与它有关。也许您还可以考虑减小用于显示的图像的大小 - 您不需要全分辨率的相机图像。
猜你喜欢
  • 2013-05-21
  • 2011-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-01
  • 2021-06-30
相关资源
最近更新 更多