- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我有一个已经流行多年的应用程序。
为了在离线状态下 100% 正常运行,该应用程序只需下载一次数十万张图像(每个对象 1 张)(增量更新根据需要进行处理)。
对象数据本身没有问题。
但是,最近,我们的应用程序在仅下载图像时开始崩溃,但仅限于较新的 iPad(具有充足存储空间的第三代 iPad Pro)。
图像下载过程使用 NSOperationQueue 内的 NSURLSession 下载任务。
我们开始看到能源日志指出 CPU 使用率过高,因此我们修改了参数,使用
在每个图像之间以及每批图像之间添加了一个中断[NSThread sleepForTimeInterval:someTime];
这将我们的 CPU 使用率从远高于 95%(这很公平)降低到低于 18%!
不幸的是,仅几个小时后,该应用程序在较新的 iPad 上仍然会崩溃。然而,在我们的 2016 iPad Pro 第一代上,即使下载 24 小时后,该应用程序也根本不会崩溃。
从设备中提取崩溃日志时,我们看到的是 CPU 使用率超过 50% 的时间超过 3 分钟。没有出现其他崩溃日志。
这些设备均已插入电源,并将其锁定时间设置为“从不”,以便让 iPad 保持唤醒状态并让我们的应用程序处于前台。
为了解决这个问题,我们降低了性能,基本上在每个图像之间等待 30 秒,在每批图像之间等待 2 分钟。这有效并且崩溃停止了,但是,这需要几天时间才能下载我们所有的图像。
我们正在尝试找到一种折衷方案,其中性能合理,并且应用程序不会崩溃。
然而,令我困扰的是,无论设置如何,即使在全速性能下,该应用程序也不会在旧设备上崩溃,只会在较新设备上崩溃。
传统观点认为这是不可能的。
我在这里缺少什么?
当我使用 Instruments 进行分析时,我发现应用程序在下载时的平均速度为 13%,而且批处理之间有 20 秒的间隔,因此 iPad 应该有足够的时间来进行清理。
有人有什么想法吗?请随意索取更多信息,我不确定还有什么会有帮助。
编辑 1:下载器代码如下:
//Assume the following instance variables are set up:
self.operationQueue = NSOperationQueue to download the images.
self.urlSession = NSURLSession with ephemeralSessionConfiguration, 60 second timeoutIntervalForRequest
self.conditions = NSMutableArray to house the NSConditions used below.
self.countRemaining = NSUInteger which keeps track of how many images are left to be downloaded.
//Starts the downloading process by setting up the variables needed for downloading.
-(void)startDownloading
{
//If the operation queue doesn't exist, re-create it here.
if(!self.operationQueue)
{
self.operationQueue = [[NSOperationQueue alloc] init];
[self.operationQueue addObserver:self forKeyPath:KEY_PATH options:0 context:nil];
[self.operationQueue setName:QUEUE_NAME];
[self.operationQueue setMaxConcurrentOperationCount:2];
}
//If the session is nil, re-create it here.
if(!self.urlSession)
{
self.urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]
delegate:self
delegateQueue:nil];
}
if([self.countRemaining count] == 0)
{
[self performSelectorInBackground:@selector(startDownloadForNextBatch:) withObject:nil];
self.countRemaining = 1;
}
}
//Starts each batch. Called again on observance of the operation queue's task count being 0.
-(void)startDownloadForNextBatch:
{
[NSThread sleepForTimeInterval:20.0]; // 20 second gap between batches
self.countRemaining = //Go get the count remaining from the database.
if (countRemaining > 0)
{
NSArray *imageRecordsToDownload = //Go get the next batch of URLs for the images to download from the database.
[imageRecordsToDownload enumerateObjectsUsingBlock:^(NSDictionary *imageRecord,
NSUInteger index,
BOOL *stop)
{
NSInvocationOperation *invokeOp = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(downloadImageForRecord:)
object:imageRecord];
[self.operationQueue addOperation:invokeOp];
}];
}
}
//Performs one image download.
-(void)downloadImageForRecord:(NSDictionary *)imageRecord
{
NSCondition downloadCondition = [[NSCondition alloc] init];
[self.conditions addObject:downloadCondition];
[[self.urlSession downloadTaskWithURL:imageURL
completionHandler:^(NSURL *location,
NSURLResponse *response,
NSError *error)
{
if(error)
{
//Record error below.
}
else
{
//Move the downloaded image to the correct directory.
NSError *moveError;
[[NSFileManager defaultManager] moveItemAtURL:location toURL:finalURL error:&moveError];
//Create a thumbnail version of the image for use in a search grid.
}
//Record the final outcome for this record by updating the database with either an error code, or the file path to where the image was saved.
//Sleep for some time to allow the CPU to rest.
[NSThread sleepForTimeInterval:0.05]; // 0.05 second gap between images.
//Finally, signal our condition.
[downloadCondition signal];
}]
resume];
[downloadCondition lock];
[downloadCondition wait];
[downloadCondition unlock];
}
//If the downloads need to be stopped, for whatever reason (i.e. the user logs out), this function is called to stop the process entirely:
-(void)stopDownloading
{
//Immediately suspend the queue.
[self.operationQueue setSuspended:YES];
//If any conditions remain, signal them, then remove them. This was added to avoid deadlock issues with the user logging out and then logging back in in rapid succession.
[self.conditions enumerateObjectsUsingBlock:^(NSCondition *condition,
NSUInteger idx,
BOOL * _Nonnull stop)
{
[condition signal];
}];
[self setConditions:nil];
[self setConditions:[NSMutableArray array]];
[self.urlSession invalidateAndCancel];
[self setImagesRemaining:0];
[self.operationQueue cancelAllOperations];
[self setOperationQueue:nil];
}
编辑 2:来自 Instruments 的 CPU 使用情况屏幕截图。峰值约为 50%,谷值约为 13% CPU 使用率。
编辑 3:运行应用程序直至控制台失败,观察到内存问题
好吧!下载图像一个多小时后,终于观察到我的 iPhone 11 Pro 发生崩溃,这与我其他测试人员报告的情况相符。
控制台报告我的应用程序因使用过多内存而被专门终止。如果我正确阅读此报告,我的应用程序使用了超过 2 GB 的 RAM。我假设这与 NSURLSESSIOND 的内部管理有更多关系,因为它在使用 Xcode 或 Instruments 进行调试期间没有显示此泄漏。
控制台报告:“内核 232912.788 内存状态:killing_specific_process pid 7075 [PharosSales](每个进程限制 10)2148353KB - memorystatus_available_pages:38718”
幸运的是,我在 1 小时左右开始收到内存警告。我应该能够暂停(挂起)我的操作队列一段时间(假设 30 秒),以便让系统清除其内存。
或者,我可以调用 stop,并在调用重新开始后使用 gcd 调度。
你们觉得这个解决方案怎么样?有没有更优雅的方式来响应内存警告?
您认为这些内存使用量来自哪里?
最佳答案
编辑 4: Eureka !!发现Apple API内部内存泄漏
在挖掘了“杀死特定进程”内存相关的控制台消息后,我发现了以下帖子:
Stack Overflow NSData leak discussion
基于围绕使用 NSData writeToFile:error: 的讨论,我环顾四周,看看我是否以某种方式使用了这个函数。
事实证明,我用来从原始图像生成缩略图的逻辑使用此语句将生成的缩略图写入磁盘。
如果我注释掉这个逻辑,应用程序就不再崩溃(能够毫无故障地拉下所有图像!)。
我已经计划将这个旧版 Core Graphics 代码替换为 WWDC 2018 演示的 ImageIO 用法。
重新编码此函数以使用 ImageIO 后,我很高兴地报告应用程序不再崩溃,并且缩略图逻辑也得到了 super 优化!
感谢您的帮助!
关于ios - iPad Pro 第三代无缘无故杀死前台应用程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58383911/
我正在安装这个程序:THERMUS ,据我所知应该安装正常。我/我通过 ubuntu 控制台安装这个程序。但是当我运行 make all 时,我收到了这条消息: make: ***No rule to
我正在渲染一个简单的 sass 文件并收到以下警告: This selector doesn't have any properties and won't be rendered. ╷ 14
我不明白为什么文本不会与 div 的中间对齐,我认为这是导致页面底部出现空白的原因,我希望文本位于中间(高度)页脚的(两个 div 我都有两个强制文本的每个部分到页面的边缘) HTML:
我正在尝试为 android 创建基本的音乐播放器。对我来说一切似乎都很好,但是当我试图在我的手机上运行应用程序时。它说它停止了。我无法解决那个问题。感谢您的任何帮助。我试图在应用程序停止时查看“Lo
在我的 LoginProvider 中,我使用了一个函数来执行登录并将创建的 session 作为 promise 返回。 @Injectable() export class LoginProvid
我在 Google Cloud Platform 上运行 Dataflow-Jobs,我收到的一个新错误是“Workflow failed”,没有任何解释。我得到的日志如下: 2017-08-25
我已经阅读了无数关于这个错误的主题,但是没有一个和我有同样的问题。 我得到了 E/MediaPlayer: 错误 (-19, 0) E/MediaPlayer: 错误 (-19,0) 错误,然而,音乐
这个错误或我缺乏知识或其他东西真的开始困扰我。我正在开发一个 Grails 应用程序,并且在我的工作过程中随机出现 Grails 提示一些导入,说无法解析类名。它在一个保存前工作!我没有对项目的基础设
我为此失去了头发!我不断收到“发送后无法设置 header ”错误,我确定我没有像在其他问题中看到的那样调用 Next()。我的代码一直在工作,直到我尝试进行一些重构,我没有改变这个类的任何东西,所以
我是一名优秀的程序员,十分优秀!