调用Drive API下载图片内存占用异常过高如何解决
调用Google Drive API获取图片时内存占用异常过高
问题表现
- 加载18张图片时总内存占用达950MB,但单张图片原始文件大小仅1.5MB
- 内存占用异常截图:

- 当前使用的下载功能实现代码:
private let service: GTLRDriveService init(service: GTLRDriveService) { self.service = service } func download(_ fileItem: GTLRDrive_File, onCompleted: @escaping (Data?, Error?) -> ()) { guard let fileID = fileItem.identifier else { return onCompleted(nil, nil) } print(fileItem.json) self.service.executeQuery(GTLRDriveQuery_FilesGet.queryForMedia(withFileId: fileID)) { (ticket, file, error) in if let error = error { return onCompleted(nil, error) } guard let data = (file as? GTLRDataObject)?.data else { return onCompleted(nil, nil) } print(data) onCompleted(data, nil) } }
问题根因
内存占用过高和图片原始压缩文件大小无直接关联,核心原因有三点:
- 当前下载逻辑直接将接口返回的完整媒体数据全量加载到内存中持有,没有做流式下载、本地落盘处理
- 图片在UI渲染前会被系统解码为位图格式,位图内存占用按分辨率计算而非压缩文件大小:单张1.5MB的4000*3000分辨率RGBA图片,解码后位图大小为
4000 * 3000 * 4Byte ≈ 48MB,18张累计占用接近900MB,和观测到的950MB占用完全吻合 - 代码中
print(fileItem.json)、print(data)会输出完整JSON结构与二进制数据描述,触发额外的内存拷贝,进一步推高内存峰值
修复方案
1. 改用文件流式下载,避免全量数据驻留内存
GTLRDriveService原生支持直接将下载的媒体数据写入本地文件,不需要把完整二进制数据加载到内存,修改下载逻辑如下:
func download(_ fileItem: GTLRDrive_File, onCompleted: @escaping (URL?, Error?) -> ()) { guard let fileID = fileItem.identifier else { return onCompleted(nil, nil) } let query = GTLRDriveQuery_FilesGet.queryForMedia(withFileId: fileID) // 指定下载文件的本地临时存储路径 let tempFileURL = FileManager.default.temporaryDirectory.appendingPathComponent(fileID) query.destinationFileURL = tempFileURL // 配置分块下载大小,避免单块缓存过大 query.serviceProperties = [ "fetchMemoryChunkSize": 1024 * 256 // 单块256KB ] self.service.executeQuery(query) { (ticket, _, error) in if let error = error { return onCompleted(nil, error) } // 直接返回本地文件路径,不持有全量二进制数据 onCompleted(tempFileURL, nil) } }
2. 图片显示前按实际渲染尺寸降采样
拿到本地图片路径后,不要直接加载原始分辨率图片,根据显示控件的实际尺寸做降采样处理,单张图片内存占用可降至几MB级别:
func downsampleImage(at fileURL: URL, targetSize: CGSize, scale: CGFloat = UIScreen.main.scale) -> UIImage? { let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary guard let imageSource = CGImageSourceCreateWithURL(fileURL as CFURL, imageSourceOptions) else { return nil } let maxDimensionInPixels = max(targetSize.width, targetSize.height) * scale let downsampleOptions = [ kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceShouldCacheImmediately: true, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels ] as CFDictionary guard let downsampledCGImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { return nil } return UIImage(cgImage: downsampledCGImage) }
3. 移除不必要的全量日志打印
删除print(fileItem.json)、print(data)这类输出全量对象、二进制内容的日志,避免控制台打印时触发额外的内存拷贝。
内容的提问来源于stack exchange,提问作者Marat Saliakhetdinov
相关产品推荐
相关产品推荐

