使用UIImagePickerController减少内存使用量

时间:2014-01-01 11:20:22

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

在我的应用程序中,用户可以使用UIImagePickerController拍摄多个图像,然后在视图中逐个显示这些图像。

我在内存管理方面遇到了一些麻烦。随着今天手机上的摄像头以百万像素迅速上升,从UIImagePickerController返回的UIImages是记忆猪。在我的iPhone 4S上,UIImages约为5MB;我很难想象它们在新的和未来的模型上会是什么样的。

我的一位朋友说,处理UIImages的最佳方法是立即将它们保存到我应用程序文档目录中的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应该从内存中释放,对吗?

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

更新

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

Allocations of app

1 个答案:

答案 0 :(得分:7)

您的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代码添加的仅

以下是Apple原始代码的内存占用量(cameraTimer不带(1)和(2))

enter image description here

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

以下是添加的内存占用(cameraTimer与(1)和(2)一起运行)

enter image description here

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

这些测试是在iPhone5S上运行的。未压缩的图像为3264 x 2448 px,应为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获取,这样您就可以避免尖峰。